diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index dcd652498c..e11ec4635a 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -23,67 +23,67 @@ else DBGFLAGS = -g endif -vlanmgrd_SOURCES = vlanmgrd.cpp vlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +vlanmgrd_SOURCES = vlanmgrd.cpp vlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h vlanmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vlanmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vlanmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -teammgrd_SOURCES = teammgrd.cpp teammgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +teammgrd_SOURCES = teammgrd.cpp teammgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h teammgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) teammgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) teammgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -portmgrd_SOURCES = portmgrd.cpp portmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +portmgrd_SOURCES = portmgrd.cpp portmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h portmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) portmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) portmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -intfmgrd_SOURCES = intfmgrd.cpp intfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +intfmgrd_SOURCES = intfmgrd.cpp intfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/lib/subintf.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h intfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) intfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) intfmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -buffermgrd_SOURCES = buffermgrd.cpp buffermgr.cpp buffermgrdyn.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +buffermgrd_SOURCES = buffermgrd.cpp buffermgr.cpp buffermgrdyn.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h buffermgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) buffermgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) buffermgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -vrfmgrd_SOURCES = vrfmgrd.cpp vrfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +vrfmgrd_SOURCES = vrfmgrd.cpp vrfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h vrfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vrfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vrfmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -nbrmgrd_SOURCES = nbrmgrd.cpp nbrmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +nbrmgrd_SOURCES = nbrmgrd.cpp nbrmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h nbrmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(LIBNL_CFLAGS) nbrmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(LIBNL_CPPFLAGS) nbrmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) $(LIBNL_LIBS) -vxlanmgrd_SOURCES = vxlanmgrd.cpp vxlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +vxlanmgrd_SOURCES = vxlanmgrd.cpp vxlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h vxlanmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vxlanmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vxlanmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -sflowmgrd_SOURCES = sflowmgrd.cpp sflowmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +sflowmgrd_SOURCES = sflowmgrd.cpp sflowmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h sflowmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) sflowmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) sflowmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -natmgrd_SOURCES = natmgrd.cpp natmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +natmgrd_SOURCES = natmgrd.cpp natmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h natmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) natmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) natmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -coppmgrd_SOURCES = coppmgrd.cpp coppmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +coppmgrd_SOURCES = coppmgrd.cpp coppmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h coppmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) coppmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) coppmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -tunnelmgrd_SOURCES = tunnelmgrd.cpp tunnelmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +tunnelmgrd_SOURCES = tunnelmgrd.cpp tunnelmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h tunnelmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) tunnelmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) tunnelmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) macsecmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) macsecmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) diff --git a/cfgmgr/buffer_pool_mellanox.lua b/cfgmgr/buffer_pool_mellanox.lua index e49032fdf5..8c51c28706 100644 --- a/cfgmgr/buffer_pool_mellanox.lua +++ b/cfgmgr/buffer_pool_mellanox.lua @@ -28,11 +28,23 @@ local port_set_8lanes = {} local lossless_port_count = 0 local function iterate_all_items(all_items, check_lossless) + -- Iterates all items in all_items, check the buffer profile each item referencing, and update reference count accordingly + -- Arguments: + -- all_items is a list, holding all keys in BUFFER_PORT_INGRESS_PROFILE_LIST or BUFFER_PORT_EGRESS_PROFILE_LIST table + -- format of keys: |, like Ethernet0|3-4 + -- Return: + -- 0 successful + -- 1 failure, typically caused by the items just updated are still pended in orchagent's queue table.sort(all_items) local lossless_ports = {} local port local fvpairs for i = 1, #all_items, 1 do + -- XXX_TABLE_KEY_SET or XXX_TABLE_DEL_SET existing means the orchagent hasn't handled all updates + -- In this case, the pool sizes are not calculated for now and will retry later + if string.sub(all_items[i], -4, -1) == "_SET" then + return 1 + end -- Count the number of priorities or queues in each BUFFER_PG or BUFFER_QUEUE item -- For example, there are: -- 3 queues in 'BUFFER_QUEUE_TABLE:Ethernet0:0-2' @@ -73,6 +85,83 @@ local function iterate_all_items(all_items, check_lossless) return 0 end +local function iterate_profile_list(all_items) + -- Iterates all items in all_items, check the buffer profiles each item referencing, and update reference count accordingly + -- Arguments: + -- all_items is a list, holding all keys in BUFFER_PORT_INGRESS_PROFILE_LIST or BUFFER_PORT_EGRESS_PROFILE_LIST table + -- format of keys: + -- Return: + -- 0 successful + -- 1 failure, typically caused by the items just updated are still pended in orchagent's queue + local port + for i = 1, #all_items, 1 do + -- XXX_TABLE_KEY_SET or XXX_TABLE_DEL_SET existing means the orchagent hasn't handled all updates + -- In this case, the pool sizes are not calculated for now and will retry later + if string.sub(all_items[i], -4, -1) == "_SET" then + return 1 + end + port = string.match(all_items[i], "Ethernet%d+") + local profile_list = redis.call('HGET', all_items[i], 'profile_list') + if not profile_list then + return 0 + end + for profile_name in string.gmatch(profile_list, "([^,]+)") do + -- The format of profile_list is profile_name,profile_name + -- We need to handle each of the profile in the list + -- The ingress_lossy_profile is shared by both BUFFER_PG||0 and BUFFER_PORT_INGRESS_PROFILE_LIST + -- It occupies buffers in BUFFER_PG but not in BUFFER_PORT_INGRESS_PROFILE_LIST + -- To distinguish both cases, a new name "ingress_lossy_profile_list" is introduced to indicate + -- the profile is used by the profile list where its size should be zero. + profile_name = 'BUFFER_PROFILE_TABLE:' .. profile_name + if profile_name == 'BUFFER_PROFILE_TABLE:ingress_lossy_profile' then + profile_name = profile_name .. '_list' + if profiles[profile_name] == nil then + profiles[profile_name] = 0 + end + end + local profile_ref_count = profiles[profile_name] + if profile_ref_count == nil then + return 1 + end + profiles[profile_name] = profile_ref_count + 1 + end + end + + return 0 +end + +local function fetch_buffer_pool_size_from_appldb() + local buffer_pools = {} + redis.call('SELECT', config_db) + local buffer_pool_keys = redis.call('KEYS', 'BUFFER_POOL|*') + local pool_name + for i = 1, #buffer_pool_keys, 1 do + local size = redis.call('HGET', buffer_pool_keys[i], 'size') + if not size then + pool_name = string.match(buffer_pool_keys[i], "BUFFER_POOL|([^%s]+)$") + table.insert(buffer_pools, pool_name) + end + end + + redis.call('SELECT', appl_db) + buffer_pool_keys = redis.call('KEYS', 'BUFFER_POOL_TABLE:*') + local size + local xoff + local output + for i = 1, #buffer_pools, 1 do + size = redis.call('HGET', 'BUFFER_POOL_TABLE:' .. buffer_pools[i], 'size') + if not size then + size = "0" + end + xoff = redis.call('HGET', 'BUFFER_POOL_TABLE:' .. buffer_pools[i], 'xoff') + if not xoff then + table.insert(result, buffer_pools[i] .. ':' .. size) + else + table.insert(result, buffer_pools[i] .. ':' .. size .. ':' .. xoff) + end + end +end + -- Connect to CONFIG_DB redis.call('SELECT', config_db) @@ -82,7 +171,10 @@ total_port = #ports_table -- Initialize the port_set_8lanes set local lanes -local number_of_lanes +local number_of_lanes = 0 +local admin_status +local admin_up_port = 0 +local admin_up_8lanes_port = 0 local port for i = 1, total_port, 1 do -- Load lanes from PORT table @@ -99,13 +191,26 @@ for i = 1, total_port, 1 do port_set_8lanes[port] = false end end + admin_status = redis.call('HGET', ports_table[i], 'admin_status') + if admin_status == 'up' then + admin_up_port = admin_up_port + 1 + if (number_of_lanes == 8) then + admin_up_8lanes_port = admin_up_8lanes_port + 1 + end + end + number_of_lanes = 0 end local egress_lossless_pool_size = redis.call('HGET', 'BUFFER_POOL|egress_lossless_pool', 'size') -- Whether shared headroom pool is enabled? local default_lossless_param_keys = redis.call('KEYS', 'DEFAULT_LOSSLESS_BUFFER_PARAMETER*') -local over_subscribe_ratio = tonumber(redis.call('HGET', default_lossless_param_keys[1], 'over_subscribe_ratio')) +local over_subscribe_ratio +if #default_lossless_param_keys > 0 then + over_subscribe_ratio = tonumber(redis.call('HGET', default_lossless_param_keys[1], 'over_subscribe_ratio')) +else + over_subscribe_ratio = 0 +end -- Fetch the shared headroom pool size local shp_size = tonumber(redis.call('HGET', 'BUFFER_POOL|ingress_lossless_pool', 'xoff')) @@ -161,7 +266,18 @@ local fail_count = 0 fail_count = fail_count + iterate_all_items(all_pgs, true) fail_count = fail_count + iterate_all_items(all_tcs, false) if fail_count > 0 then - return {} + fetch_buffer_pool_size_from_appldb() + return result +end + +local all_ingress_profile_lists = redis.call('KEYS', 'BUFFER_PORT_INGRESS_PROFILE_LIST*') +local all_egress_profile_lists = redis.call('KEYS', 'BUFFER_PORT_EGRESS_PROFILE_LIST*') + +fail_count = fail_count + iterate_profile_list(all_ingress_profile_lists) +fail_count = fail_count + iterate_profile_list(all_egress_profile_lists) +if fail_count > 0 then + fetch_buffer_pool_size_from_appldb() + return result end local statistics = {} @@ -177,9 +293,6 @@ for name in pairs(profiles) do if name == "BUFFER_PROFILE_TABLE:ingress_lossy_profile" then size = size + lossypg_reserved end - if name == "BUFFER_PROFILE_TABLE:egress_lossy_profile" then - profiles[name] = total_port - end if size ~= 0 then if shp_enabled and shp_size == 0 then local xon = tonumber(redis.call('HGET', name, 'xon')) @@ -211,11 +324,11 @@ if shp_enabled then end -- Accumulate sizes for management PGs -local accumulative_management_pg = (total_port - port_count_8lanes) * lossypg_reserved + port_count_8lanes * lossypg_reserved_8lanes +local accumulative_management_pg = (admin_up_port - admin_up_8lanes_port) * lossypg_reserved + admin_up_8lanes_port * lossypg_reserved_8lanes accumulative_occupied_buffer = accumulative_occupied_buffer + accumulative_management_pg -- Accumulate sizes for egress mirror and management pool -local accumulative_egress_mirror_overhead = total_port * egress_mirror_headroom +local accumulative_egress_mirror_overhead = admin_up_port * egress_mirror_headroom accumulative_occupied_buffer = accumulative_occupied_buffer + accumulative_egress_mirror_overhead + mgmt_pool_size -- Switch to CONFIG_DB @@ -295,5 +408,6 @@ table.insert(result, "debug:egress_mirror:" .. accumulative_egress_mirror_overhe table.insert(result, "debug:shp_enabled:" .. tostring(shp_enabled)) table.insert(result, "debug:shp_size:" .. shp_size) table.insert(result, "debug:total port:" .. total_port .. " ports with 8 lanes:" .. port_count_8lanes) +table.insert(result, "debug:admin up port:" .. admin_up_port .. " admin up ports with 8 lanes:" .. admin_up_8lanes_port) return result diff --git a/cfgmgr/buffermgrd.cpp b/cfgmgr/buffermgrd.cpp index 74e88f7337..05932a9e3c 100644 --- a/cfgmgr/buffermgrd.cpp +++ b/cfgmgr/buffermgrd.cpp @@ -12,6 +12,7 @@ #include #include "json.h" #include "json.hpp" +#include "warm_restart.h" using namespace std; using namespace swss; @@ -33,6 +34,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; @@ -181,6 +186,9 @@ int main(int argc, char **argv) if (dynamicMode) { + WarmStart::initialize("buffermgrd", "swss"); + WarmStart::checkWarmStart("buffermgrd", "swss"); + vector buffer_table_connectors = { TableConnector(&cfgDb, CFG_PORT_TABLE_NAME), TableConnector(&cfgDb, CFG_PORT_CABLE_LEN_TABLE_NAME), diff --git a/cfgmgr/buffermgrdyn.cpp b/cfgmgr/buffermgrdyn.cpp index a1c64b7e5e..0888e9e6c6 100644 --- a/cfgmgr/buffermgrdyn.cpp +++ b/cfgmgr/buffermgrdyn.cpp @@ -30,26 +30,26 @@ using namespace swss; BufferMgrDynamic::BufferMgrDynamic(DBConnector *cfgDb, DBConnector *stateDb, DBConnector *applDb, const vector &tables, shared_ptr> gearboxInfo, shared_ptr> zeroProfilesInfo) : Orch(tables), m_platform(), + m_bufferDirections({BUFFER_INGRESS, BUFFER_EGRESS}), + m_bufferObjectNames({"priority group", "queue"}), + m_bufferDirectionNames({"ingress", "egress"}), m_applDb(applDb), - m_cfgPortTable(cfgDb, CFG_PORT_TABLE_NAME), - m_cfgCableLenTable(cfgDb, CFG_PORT_CABLE_LEN_TABLE_NAME), - m_cfgBufferProfileTable(cfgDb, CFG_BUFFER_PROFILE_TABLE_NAME), - m_cfgBufferPgTable(cfgDb, CFG_BUFFER_PG_TABLE_NAME), - m_cfgLosslessPgPoolTable(cfgDb, CFG_BUFFER_POOL_TABLE_NAME), + m_zeroProfilesLoaded(false), + m_supportRemoving(true), m_cfgDefaultLosslessBufferParam(cfgDb, CFG_DEFAULT_LOSSLESS_BUFFER_PARAMETER), m_applBufferPoolTable(applDb, APP_BUFFER_POOL_TABLE_NAME), m_applBufferProfileTable(applDb, APP_BUFFER_PROFILE_TABLE_NAME), - m_applBufferPgTable(applDb, APP_BUFFER_PG_TABLE_NAME), - m_applBufferQueueTable(applDb, APP_BUFFER_QUEUE_TABLE_NAME), - m_applBufferIngressProfileListTable(applDb, APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME), - m_applBufferEgressProfileListTable(applDb, APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME), + m_applBufferObjectTables({ProducerStateTable(applDb, APP_BUFFER_PG_TABLE_NAME), ProducerStateTable(applDb, APP_BUFFER_QUEUE_TABLE_NAME)}), + m_applBufferProfileListTables({ProducerStateTable(applDb, APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME), ProducerStateTable(applDb, APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME)}), m_statePortTable(stateDb, STATE_PORT_TABLE_NAME), m_stateBufferMaximumTable(stateDb, STATE_BUFFER_MAXIMUM_VALUE_TABLE), m_stateBufferPoolTable(stateDb, STATE_BUFFER_POOL_TABLE_NAME), m_stateBufferProfileTable(stateDb, STATE_BUFFER_PROFILE_TABLE_NAME), m_applPortTable(applDb, APP_PORT_TABLE_NAME), m_portInitDone(false), - m_firstTimeCalculateBufferPool(true), + m_bufferPoolReady(false), + m_bufferObjectsPending(true), + m_bufferCompletelyInitialized(false), m_mmuSizeNumber(0) { SWSS_LOG_ENTER(); @@ -57,6 +57,8 @@ BufferMgrDynamic::BufferMgrDynamic(DBConnector *cfgDb, DBConnector *stateDb, DBC // Initialize the handler map initTableHandlerMap(); parseGearboxInfo(gearboxInfo); + if (nullptr != zeroProfilesInfo) + m_zeroPoolAndProfileInfo = *zeroProfilesInfo; string platform = getenv("ASIC_VENDOR") ? getenv("ASIC_VENDOR") : ""; if (platform == "") @@ -118,6 +120,23 @@ BufferMgrDynamic::BufferMgrDynamic(DBConnector *cfgDb, DBConnector *stateDb, DBC { m_cfgDefaultLosslessBufferParam.hget(keys[0], "default_dynamic_th", m_defaultThreshold); } + + // m_waitApplyAdditionalZeroProfiles represents for how long applying additional zero profiles will be deferred + // after normal profiles and profiles for configured items have been applied + // For warm reboot, it is not deferred as the additional zero profiles have been in the APPL_DB + // In this case, they should be replayed as soon as possible + // For fast/cold reboot and other initialization flow, it is defered for 30 seconds. + // This is to accelerate the fast reboot converging time. + if (WarmStart::isWarmStart()) + { + m_waitApplyAdditionalZeroProfiles = 0; + WarmStart::setWarmStartState("buffermgrd", WarmStart::INITIALIZED); + } + else + { + m_waitApplyAdditionalZeroProfiles = 3; + WarmStart::setWarmStartState("buffermgrd", WarmStart::WSDISABLED); + } } void BufferMgrDynamic::parseGearboxInfo(shared_ptr> gearboxInfo) @@ -177,6 +196,214 @@ void BufferMgrDynamic::parseGearboxInfo(shared_ptr|: represents a zero buffer object, like a zero buffer pool or zero buffer profile + * All necessary fields of the object should be provided in the json file according to the type of the object. + * For the buffer profiles, if the buffer pools referenced are the normal pools, like {ingress|egress}_{lossless|lossy}_pool, + * the zero profile name will be stored in the referenced pool's "zero_profile_name" field for the purpose of + * - constructing the zero profile list or providing the zero profiles for PGs or queues + * - fetching the zero profile for a certain pool when applying the zero profile for configured items + * + * - control_fields: represents the ids required for reclaiming unused buffers, including: + * - pgs_to_apply_zero_profile, represents the PGs on which the zero profiles will be applied for reclaiming unused buffers + * If it is not provided, zero profiles will be applied on all PGs. + * - ingress_zero_profile, represents the zero buffer profille which will be applied on PGs for reclaiming unused buffer. + * Typically, it is provided along with pgs_to_apply_zero_profile. + * In case pgs_to_apply_zero_profile is defined but ingress_zero_profile is not provided, the first zero profile on the ingress side in json file will be used. + * - queues_to_apply_zero_profile, represents the queues on which the zero profiles will be applied + * If it is not provided, zero profiles will be applied on all queues. + * - egress_zero_profile, represents the zero buffer profille which will be applied on queues for reclaiming unused buffer. + * Typically, it is provided along with queues_to_apply_zero_profile. + * In case queues_to_apply_zero_profile is defined but egress_zero_profile is not provided, the first zero profile on the egress side in json file will be used. + * - support_removing_buffer_items, represents whether the buffer items are supported to be removed. + * The number of queues and PGs are pushed into BUFFER_MAX_PARAM table in STATE_DB at the beginning of ports orchagent + * and will be learnt by buffer manager when it's starting. + */ +void BufferMgrDynamic::loadZeroPoolAndProfiles() +{ + for (auto &kfv : m_zeroPoolAndProfileInfo) + { + auto &table_key = kfvKey(kfv); + + if (table_key == "control_fields") + { + auto &fvs = kfvFieldsValues(kfv); + for (auto &fv : fvs) + { + if (fvField(fv) == "pgs_to_apply_zero_profile") + { + m_bufferObjectIdsToZero[BUFFER_PG] = fvValue(fv); + } + else if (fvField(fv) == "ingress_zero_profile") + { + m_bufferZeroProfileName[BUFFER_PG] = fvValue(fv); + } + else if (fvField(fv) == "queues_to_apply_zero_profile") + { + m_bufferObjectIdsToZero[BUFFER_QUEUE] = fvValue(fv); + } + else if (fvField(fv) == "egress_zero_profile") + { + m_bufferZeroProfileName[BUFFER_QUEUE] = fvValue(fv); + } + else if (fvField(fv) == "support_removing_buffer_items") + { + m_supportRemoving = (fvValue(fv) == "yes"); + } + } + + continue; + } + + auto const &table = parseObjectNameFromKey(table_key, 0); + auto const &key = parseObjectNameFromKey(table_key, 1); + + if (table.empty() || key.empty()) + { + SWSS_LOG_ERROR("Invalid format of key %s for zero profile info, won't initialize it", + kfvKey(kfv).c_str()); + return; + } + + if (table == APP_BUFFER_POOL_TABLE_NAME) + { + m_applBufferPoolTable.set(key, kfvFieldsValues(kfv)); + m_stateBufferPoolTable.set(key, kfvFieldsValues(kfv)); + SWSS_LOG_NOTICE("Loaded zero buffer pool %s", key.c_str()); + m_zeroPoolNameSet.insert(key); + } + else if (table == APP_BUFFER_PROFILE_TABLE_NAME) + { + auto &fvs = kfvFieldsValues(kfv); + bool poolNotFound = false; + for (auto &fv : fvs) + { + if (fvField(fv) == "pool") + { + const auto &poolName = fvValue(fv); + auto poolSearchRef = m_bufferPoolLookup.find(poolName); + if (poolSearchRef != m_bufferPoolLookup.end()) + { + auto &poolObj = poolSearchRef->second; + if (poolObj.zero_profile_name.empty()) + { + poolObj.zero_profile_name = key; + if (m_bufferZeroProfileName[poolObj.direction].empty()) + m_bufferZeroProfileName[poolObj.direction] = poolObj.zero_profile_name; + } + else + { + SWSS_LOG_ERROR("Multiple zero profiles (%s, %s) detected for pool %s, takes the former and ignores the latter", + poolObj.zero_profile_name.c_str(), + key.c_str(), + fvValue(fv).c_str()); + } + } + else if (m_zeroPoolNameSet.find(poolName) == m_zeroPoolNameSet.end()) + { + SWSS_LOG_WARN("Profile %s is not loaded as the referenced pool %s is not defined", + key.c_str(), + fvValue(fv).c_str()); + poolNotFound = true; + break; + } + + m_zeroProfiles.emplace_back(key, poolName); + } + } + if (poolNotFound) + { + continue; + } + m_applBufferProfileTable.set(key, fvs); + m_stateBufferProfileTable.set(key, fvs); + SWSS_LOG_NOTICE("Loaded zero buffer profile %s", key.c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown keys %s with zero table name %s isn't loaded to APPL_DB", key.c_str(), table.c_str()); + continue; + } + } + + // Consistency checking + // 1. For any buffer pool, if there is no zero profile provided, removing buffer items must be supported + // because the reserved buffer will be reclaimed by removing buffer items + // 2. If pgs_to_apply_zero_profile or queues_to_apply_zero_profile is provided, removing buffer items must be supported + // because the PGs or queues that are not in the ID list will be removed + bool noReclaiming = false; + if (!m_supportRemoving) + { + for (auto &poolRef: m_bufferPoolLookup) + { + if (poolRef.second.zero_profile_name.empty()) + { + // For any buffer pool, zero profile must be provided + SWSS_LOG_ERROR("Zero profile is not provided for pool %s while removing buffer items is not supported, reserved buffer can not be reclaimed correctly", poolRef.first.c_str()); + noReclaiming = true; + } + } + + if (!m_bufferObjectIdsToZero[BUFFER_PG].empty() || !m_bufferObjectIdsToZero[BUFFER_QUEUE].empty()) + { + SWSS_LOG_ERROR("Unified IDs of queues or priority groups specified while removing buffer items is not supported, reserved buffer can not be reclaimed correctly"); + noReclaiming = true; + } + } + + if (noReclaiming) + { + unloadZeroPoolAndProfiles(); + m_zeroPoolAndProfileInfo.clear(); + } + else + { + m_zeroProfilesLoaded = true; + } +} + +void BufferMgrDynamic::unloadZeroPoolAndProfiles() +{ + for (auto &zeroProfile : m_zeroProfiles) + { + auto &zeroProfileName = zeroProfile.first; + auto &poolReferenced = zeroProfile.second; + + auto poolSearchRef = m_bufferPoolLookup.find(poolReferenced); + if (poolSearchRef != m_bufferPoolLookup.end()) + { + auto &poolObj = poolSearchRef->second; + poolObj.zero_profile_name.clear(); + } + m_applBufferProfileTable.del(zeroProfileName); + m_stateBufferProfileTable.del(zeroProfileName); + SWSS_LOG_NOTICE("Unloaded zero buffer profile %s", zeroProfileName.c_str()); + } + + m_zeroProfiles.clear(); + + for (auto &zeroPool : m_zeroPoolNameSet) + { + m_applBufferPoolTable.del(zeroPool); + m_stateBufferPoolTable.del(zeroPool); + SWSS_LOG_NOTICE("Unloaded zero buffer pool %s", zeroPool.c_str()); + } + + m_zeroPoolNameSet.clear(); + + m_zeroProfilesLoaded = false; +} + void BufferMgrDynamic::initTableHandlerMap() { m_bufferTableHandlerMap.insert(buffer_handler_pair(STATE_BUFFER_MAXIMUM_VALUE_TABLE, &BufferMgrDynamic::handleBufferMaxParam)); @@ -190,6 +417,11 @@ void BufferMgrDynamic::initTableHandlerMap() m_bufferTableHandlerMap.insert(buffer_handler_pair(CFG_PORT_TABLE_NAME, &BufferMgrDynamic::handlePortTable)); m_bufferTableHandlerMap.insert(buffer_handler_pair(CFG_PORT_CABLE_LEN_TABLE_NAME, &BufferMgrDynamic::handleCableLenTable)); m_bufferTableHandlerMap.insert(buffer_handler_pair(STATE_PORT_TABLE_NAME, &BufferMgrDynamic::handlePortStateTable)); + + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_QUEUE_TABLE_NAME, &BufferMgrDynamic::handleSingleBufferQueueEntry)); + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_PG_TABLE_NAME, &BufferMgrDynamic::handleSingleBufferPgEntry)); + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, &BufferMgrDynamic::handleSingleBufferPortIngressProfileListEntry)); + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME, &BufferMgrDynamic::handleSingleBufferPortEgressProfileListEntry)); } // APIs to handle variant kinds of keys @@ -397,6 +629,34 @@ void BufferMgrDynamic::recalculateSharedBufferPool() vector keys = {}; vector argv = {}; + if (!m_bufferPoolReady) + { + // In case all buffer pools have a configured size, + // The m_bufferPoolReady will be set to true + // It can happen on vs. + bool hasDynamicSizePool = false, hasBufferPool = false; + for (auto &poolRef : m_bufferPoolLookup) + { + hasBufferPool = true; + if (poolRef.second.dynamic_size) + { + hasDynamicSizePool = true; + } + } + + if (!hasBufferPool) + { + SWSS_LOG_INFO("No shared buffer pool configured, skip calculating shared buffer pool size"); + return; + } + + if (hasBufferPool && !hasDynamicSizePool) + { + m_bufferPoolReady = true; + SWSS_LOG_NOTICE("No pool requires calculating size dynamically. All buffer pools are ready"); + } + } + auto ret = runRedisScript(*m_applDb, m_bufferpoolSha, keys, argv); // The format of the result: @@ -513,6 +773,9 @@ void BufferMgrDynamic::recalculateSharedBufferPool() { SWSS_LOG_WARN("Lua scripts for buffer calculation were not executed successfully"); } + + if (!m_bufferPoolReady) + m_bufferPoolReady = true; } void BufferMgrDynamic::checkSharedBufferPoolSize(bool force_update_during_initialization = false) @@ -535,7 +798,7 @@ void BufferMgrDynamic::checkSharedBufferPoolSize(bool force_update_during_initia } else { - if (m_firstTimeCalculateBufferPool) + if (!m_bufferPoolReady) { // It's something like a placeholder especially for warm reboot flow // without all buffer pools created, buffer profiles are unable to be created, @@ -545,7 +808,6 @@ void BufferMgrDynamic::checkSharedBufferPoolSize(bool force_update_during_initia // until portInitDone // Eventually, the correct values will pushed to APPL_DB and then ASIC_DB recalculateSharedBufferPool(); - m_firstTimeCalculateBufferPool = false; SWSS_LOG_NOTICE("Buffer pool update deferred because port is still under initialization, start polling timer"); } @@ -562,10 +824,7 @@ void BufferMgrDynamic::updateBufferPoolToDb(const string &name, const buffer_poo { vector fvVector; - if (pool.ingress) - fvVector.emplace_back("type", "ingress"); - else - fvVector.emplace_back("type", "egress"); + fvVector.emplace_back("type", m_bufferDirectionNames[pool.direction]); if (!pool.xoff.empty()) fvVector.emplace_back("xoff", pool.xoff); @@ -581,19 +840,29 @@ void BufferMgrDynamic::updateBufferPoolToDb(const string &name, const buffer_poo void BufferMgrDynamic::updateBufferProfileToDb(const string &name, const buffer_profile_t &profile) { + if (!m_bufferPoolReady) + { + SWSS_LOG_NOTICE("Buffer pools are not ready when configuring buffer profile %s, pending", name.c_str()); + m_bufferObjectsPending = true; + return; + } + vector fvVector; string mode = getPgPoolMode(); // profile threshold field name mode += "_th"; - fvVector.emplace_back("xon", profile.xon); - if (!profile.xon_offset.empty()) { - fvVector.emplace_back("xon_offset", profile.xon_offset); + if (profile.lossless) + { + fvVector.emplace_back("xon", profile.xon); + if (!profile.xon_offset.empty()) { + fvVector.emplace_back("xon_offset", profile.xon_offset); + } + fvVector.emplace_back("xoff", profile.xoff); } - fvVector.emplace_back("xoff", profile.xoff); fvVector.emplace_back("size", profile.size); - fvVector.emplace_back("pool", INGRESS_LOSSLESS_PG_POOL_NAME); + fvVector.emplace_back("pool", profile.pool_name); fvVector.emplace_back(mode, profile.threshold); m_applBufferProfileTable.set(name, fvVector); @@ -601,22 +870,49 @@ void BufferMgrDynamic::updateBufferProfileToDb(const string &name, const buffer_ } // Database operation -// Set/remove BUFFER_PG table entry -void BufferMgrDynamic::updateBufferPgToDb(const string &key, const string &profile, bool add) +// Set/remove BUFFER_PG or BUFFER_QUEUE table entry +void BufferMgrDynamic::updateBufferObjectToDb(const string &key, const string &profile, bool add, buffer_direction_t dir=BUFFER_PG) { + auto &table = m_applBufferObjectTables[dir]; + const auto &objType = m_bufferObjectNames[dir]; + if (add) { - vector fvVector; + if (!m_bufferPoolReady) + { + SWSS_LOG_NOTICE("Buffer pools are not ready when configuring buffer %s %s, pending", objType.c_str(), key.c_str()); + m_bufferObjectsPending = true; + return; + } - fvVector.clear(); + vector fvVector; + fvVector.emplace_back(buffer_profile_field_name, profile); - fvVector.push_back(make_pair("profile", profile)); - m_applBufferPgTable.set(key, fvVector); + table.set(key, fvVector); } else { - m_applBufferPgTable.del(key); + table.del(key); + } +} + +void BufferMgrDynamic::updateBufferObjectListToDb(const string &key, const string &profileList, buffer_direction_t dir) +{ + auto &table = m_applBufferProfileListTables[dir]; + const auto &direction = m_bufferDirectionNames[dir]; + + if (!m_bufferPoolReady) + { + SWSS_LOG_NOTICE("Buffer pools are not ready when configuring buffer %s profile list %s, pending", direction.c_str(), key.c_str()); + m_bufferObjectsPending = true; + return; } + + vector fvVector; + + fvVector.emplace_back(buffer_profile_list_field_name, profileList); + + table.set(key, fvVector); } // We have to check the headroom ahead of applying them @@ -645,6 +941,7 @@ task_process_status BufferMgrDynamic::allocateProfile(const string &speed, const profile.port_mtu = mtu; profile.gearbox_model = gearbox_model; profile.lane_count = lane_count; + profile.pool_name = INGRESS_LOSSLESS_PG_POOL_NAME; // Call vendor-specific lua plugin to calculate the xon, xoff, xon_offset, size // Pay attention, the threshold can contain valid value @@ -785,30 +1082,268 @@ bool BufferMgrDynamic::isHeadroomResourceValid(const string &port, const buffer_ return result; } -task_process_status BufferMgrDynamic::removeAllPgsFromPort(const string &port) +/* + * The following functions are defnied for reclaiming reserved buffers + * - constructZeroProfileListFromNormalProfileList + * - reclaimReservedBufferForPort + * - removeSupportedButNotConfiguredItemsOnPort + * - applyNormalBufferObjectsOnPort, handling queues and profile lists. + * The priority groups are handle by refreshPgsForPort + * + * The overall logic of reclaiming reserved buffers is handled in handlePortTable: + * Shutdown flow (to reclaim unused buffer): + * 1. reclaimReservedBufferForPort + * Start up flow (to reserved buffer for the port) + * 1. removeSupportedButNotConfiguredItemsOnPort + * 2. applyNormalBufferObjectsOnPort + * 3. refreshPgsForPort + */ + +/* + * constructZeroProfileListFromNormalProfileList + * Tool function for constructing zero profile list from normal profile list. + * There should be one buffer profile for each buffer pool on each side (ingress/egress) in the profile_list + * in BUFFER_PORT_INGRESS_PROFILE_LIST and BUFFER_PORT_EGRESS_PROFILE_LIST table. + * For example, on ingress side, there are ingress_lossless_profile and ingress_lossy_profile in the profile list + * for buffer pools ingress_lossless_pool and ingress_lossy_pool respectively. + * There should be one zero profile for each pool in the profile_list when reclaiming buffer on a port as well. + * The arguments is the normal profile list and the port. + * The logic is: + * For each buffer profile in the list + * 1. Fetch the buffer pool referenced by the profile + * 2. Fetch the zero buffer profile of the buffer pool. The pool is skipped in case there is no zero buffer profile defined for it. + * 3. Construct the zero buffer profile by joining all zero buffer profiles, separating by "," + */ +string BufferMgrDynamic::constructZeroProfileListFromNormalProfileList(const string &normalProfileList, const string &port) { - buffer_pg_lookup_t &portPgs = m_portPgLookup[port]; + string zeroProfileNameList; + + auto profileRefs = tokenize(normalProfileList, ','); + for (auto &profileName : profileRefs) + { + const auto &zeroProfile = fetchZeroProfileFromNormalProfile(profileName); + if (!zeroProfile.empty()) + { + zeroProfileNameList += zeroProfile + ","; + } + else + { + SWSS_LOG_WARN("Unable to apply zero profile for profile %s on port %s because no zero profile configured for the pool", + profileName.c_str(), + port.c_str()); + } + } + + if (!zeroProfileNameList.empty()) + zeroProfileNameList.pop_back(); + + return zeroProfileNameList; +} + +/* + * removeSupportedButNotConfiguredItemsOnPort + * Remove the supported-but-not-configured buffer items from the port. + * They were applied when the port was shut down. + * Called when a port is started up. + * + * Arguments: + * portInfo, represents the port information of the port + * portName, represents the name of the port + * Return: + * None. + */ +void BufferMgrDynamic::removeSupportedButNotConfiguredItemsOnPort(port_info_t &portInfo, const string &portName) +{ + auto const &portPrefix = portName + delimiter; + + for (auto dir : m_bufferDirections) + { + if (!portInfo.supported_but_not_configured_buffer_objects[dir].empty()) + { + for (auto &it: portInfo.supported_but_not_configured_buffer_objects[dir]) + { + m_applBufferObjectTables[dir].del(portPrefix + it); + } + portInfo.supported_but_not_configured_buffer_objects[dir].clear(); + } + } +} + +/* + * applyNormalBufferObjectsOnPort + * Apply normal buffer profiles on buffer queues, and buffer profile lists. + * The buffer priority group will be handled by refreshPgsForPort + * Called when a port is started up. + * + * Arguments: + * port, represents the name of the port + * Return: + * None. + */ +void BufferMgrDynamic::applyNormalBufferObjectsOnPort(const string &port) +{ + vector fvVector; + auto &portQueues = m_portQueueLookup[port]; + + for (auto &queue : portQueues) + { + SWSS_LOG_NOTICE("Profile %s has been applied on queue %s", queue.second.running_profile_name.c_str(), queue.first.c_str()); + updateBufferObjectToDb(queue.first, queue.second.running_profile_name, true, BUFFER_QUEUE); + } + + for (auto dir : m_bufferDirections) + { + auto &profileList = m_portProfileListLookups[dir][port]; + if (!profileList.empty()) + { + fvVector.emplace_back(buffer_profile_list_field_name, profileList); + m_applBufferProfileListTables[dir].set(port, fvVector); + fvVector.clear(); + } + } +} + +string &BufferMgrDynamic::fetchZeroProfileFromNormalProfile(const string &profile) +{ + auto const &profileRef = m_bufferProfileLookup.find(profile); + auto const &poolName = (profileRef == m_bufferProfileLookup.end() ? INGRESS_LOSSLESS_PG_POOL_NAME : profileRef->second.pool_name); + auto &poolInfo = m_bufferPoolLookup[poolName]; + + return poolInfo.zero_profile_name; +} + +bool BufferMgrDynamic::isReadyToReclaimBufferOnPort(const string &port) +{ + auto &portInfo = m_portInfoLookup[port]; + + for (auto dir : m_bufferDirections) + { + if (m_bufferObjectIdsToZero[dir].empty() && 0 == portInfo.maximum_buffer_objects[dir]) + { + SWSS_LOG_NOTICE("Maximum supported priority groups and queues have not been populated in STATE_DB for port %s, reclaiming reserved buffer deferred", port.c_str()); + return false; + } + } + + return true; +} + +/* + * reclaimReservedBufferForPort + * Called when a port is admin down + * Reserved buffer is reclaimed by applying zero profiles on the buffer item or just removing them. + * + * Parameters: + * - port: the name of the port + * Return: + * - task_process_status + * + * Purpose: + * 1. Reclaim all the priority groups and queues of the port from APPL_DB + * 2. Remove all the buffer profiles that are dynamically calculated and no longer referenced + * 3. Reclaim the ingress/egress profile list of the port form APPL_DB + * + * The flow: + * 1. Load the zero pools and profiles into APPL_DB if they have been provided but not loaded. + * 2. Handle priority group, and queues: + * Two modes for them to be reclaimed: + * - Apply zero buffer profiles on all configured and supported-but-not-configured items. + * Eg. + * - 16 queues supported, 0-2, 5-6 are configured as lossy and 3-4 are configured as lossless + * - Zero profiles will be applied on + * - 0-2, 5-6, 7-15: egress_lossy_zero_profile + * - 3-4: egress_lossless_zero_profile + * - Apply zero buffer profiles on items specified by vendor and remove all other items. + * Eg. + * - 8 PGs supported, 0 is configured as lossy and 3-4 are configured as lossless + * - PGs to be applied zero profile on is 0, 3-4 will be removed + * Queues and priority groups share the common logic. + * 3. Handle ingress and egress buffer profile list, which shared the common logic: + * - Construct the zero buffer profile list from the normal buffer profile list. + * - Apply the zero buffer profile list on the port. + */ +template task_process_status BufferMgrDynamic::reclaimReservedBufferForPort(const string &port, T &portObjectLookup, buffer_direction_t dir) +{ + auto &portBufferObjects = portObjectLookup[port]; + auto &portInfo = m_portInfoLookup[port]; set profilesToBeReleased; + const string &portKeyPrefix = port + delimiter; + const string &objectTypeName = m_bufferObjectNames[dir]; + + if (!m_zeroPoolAndProfileInfo.empty() && (!m_zeroProfilesLoaded)) + { + loadZeroPoolAndProfiles(); + } - SWSS_LOG_INFO("Removing all PGs from port %s", port.c_str()); + bool applyZeroProfileOnSpecifiedObjects = !m_bufferObjectIdsToZero[dir].empty(); - for (auto it = portPgs.begin(); it != portPgs.end(); ++it) + SWSS_LOG_NOTICE("Reclaiming buffer reserved for all %s(s) from port %s", objectTypeName.c_str(), port.c_str()); + + unsigned long objectsMap = (1 << portInfo.maximum_buffer_objects[dir]) - 1; + for (auto &it : portBufferObjects) { - auto &key = it->first; - auto &portPg = it->second; + auto &key = it.first; + auto &portObject = it.second; + const string &runningProfile = portObject.running_profile_name; + const string &zeroProfile = fetchZeroProfileFromNormalProfile(runningProfile); - SWSS_LOG_INFO("Removing PG %s from port %s", key.c_str(), port.c_str()); + SWSS_LOG_INFO("Reclaiming buffer reserved for %s %s", objectTypeName.c_str(), key.c_str()); - if (portPg.running_profile_name.empty()) - continue; + if (!applyZeroProfileOnSpecifiedObjects) + { + // Apply zero profiles on each configured buffer objects and supported-but-not-configured buffer objects + // Fetch the zero profile of the pool referenced by the configured profile + if (!zeroProfile.empty()) + { + SWSS_LOG_INFO("Applying zero profile %s on %s %s", zeroProfile.c_str(), objectTypeName.c_str(), key.c_str()); + updateBufferObjectToDb(key, zeroProfile, true, dir); + } + else + { + // No zero profile defined for the pool. Reclaim buffer by removing the buffer object + SWSS_LOG_INFO("Zero profile is not defined for profile %s, reclaim reserved buffer by removing %s %s", runningProfile.c_str(), objectTypeName.c_str(), key.c_str()); + updateBufferObjectToDb(key, runningProfile, false, dir); + } + + objectsMap ^= generateBitMapFromIdsStr(parseObjectNameFromKey(key, 1)); + } + else + { + // Apply the zero profile on specified buffer objects only. + // Remove each buffer object first + // In this case, removing must be supported (checked when the json was loaded) + SWSS_LOG_INFO("Zero profile will be applied on %s. Remove %s %s first", m_bufferObjectIdsToZero[dir].c_str(), objectTypeName.c_str(), key.c_str()); + updateBufferObjectToDb(key, runningProfile, false, dir); + } - m_bufferProfileLookup[portPg.running_profile_name].port_pgs.erase(key); - updateBufferPgToDb(key, portPg.running_profile_name, false); - profilesToBeReleased.insert(portPg.running_profile_name); - portPg.running_profile_name.clear(); + if (dir == BUFFER_PG && !runningProfile.empty()) + { + m_bufferProfileLookup[runningProfile].port_pgs.erase(key); + profilesToBeReleased.insert(runningProfile); + portObject.running_profile_name.clear(); + } } - checkSharedBufferPoolSize(); + if (applyZeroProfileOnSpecifiedObjects) + { + updateBufferObjectToDb(portKeyPrefix + m_bufferObjectIdsToZero[dir], m_bufferZeroProfileName[dir], true, dir); + } + else if (!m_bufferZeroProfileName[dir].empty()) + { + // Apply zero profiles on supported-but-not-configured buffer objects + portInfo.supported_but_not_configured_buffer_objects[dir] = generateIdListFromMap(objectsMap, portInfo.maximum_buffer_objects[dir]); + if (m_waitApplyAdditionalZeroProfiles == 0) + { + for(auto &it: portInfo.supported_but_not_configured_buffer_objects[dir]) + { + updateBufferObjectToDb(portKeyPrefix + it, m_bufferZeroProfileName[dir], true, dir); + } + } + else + { + m_pendingSupportedButNotConfiguredPorts[dir].insert(port); + } + } // Remove the old profile which is probably not referenced anymore. if (!profilesToBeReleased.empty()) @@ -819,6 +1354,17 @@ task_process_status BufferMgrDynamic::removeAllPgsFromPort(const string &port) } } + vector fvVector; + + SWSS_LOG_NOTICE("Reclaiming buffer reserved for ingress profile list from port %s", port.c_str()); + const auto &profileList = m_portProfileListLookups[dir][port]; + if (!profileList.empty()) + { + const string &zeroIngressProfileNameList = constructZeroProfileListFromNormalProfileList(profileList, port); + fvVector.emplace_back(buffer_profile_list_field_name, zeroIngressProfileNameList); + m_applBufferProfileListTables[dir].set(port, fvVector); + } + return task_process_status::task_success; } @@ -857,6 +1403,14 @@ task_process_status BufferMgrDynamic::refreshPgsForPort(const string &port, cons return task_process_status::task_success; } + if (!m_bufferPoolReady) + { + SWSS_LOG_INFO("Nothing to be done since the buffer pool is not ready"); + return task_process_status::task_success; + } + + SWSS_LOG_NOTICE("Refresh priority groups for port %s", port.c_str()); + // Iterate all the lossless PGs configured on this port for (auto it = portPgs.begin(); it != portPgs.end(); ++it) { @@ -930,7 +1484,7 @@ task_process_status BufferMgrDynamic::refreshPgsForPort(const string &port, cons } // appl_db Database operation: set item BUFFER_PG|| - updateBufferPgToDb(key, newProfile, true); + updateBufferObjectToDb(key, newProfile, true); isHeadroomUpdated = true; } @@ -1097,12 +1651,6 @@ task_process_status BufferMgrDynamic::doUpdatePgTask(const string &pg_key, const case PORT_ADMIN_DOWN: SWSS_LOG_NOTICE("Skip setting BUFFER_PG for %s because the port is administratively down", port.c_str()); - if (!m_portInitDone) - { - // In case the port is admin down during initialization, the PG will be removed from the port, - // which effectively notifies bufferOrch to add the item to the m_ready_list - m_applBufferPgTable.del(pg_key); - } break; default: @@ -1125,7 +1673,7 @@ task_process_status BufferMgrDynamic::doRemovePgTask(const string &pg_key, const // Remove the PG from APPL_DB string null_str(""); - updateBufferPgToDb(pg_key, null_str, false); + updateBufferObjectToDb(pg_key, null_str, false); SWSS_LOG_NOTICE("Remove BUFFER_PG %s (profile %s, %s)", pg_key.c_str(), bufferPg.running_profile_name.c_str(), bufferPg.configured_profile_name.c_str()); @@ -1224,32 +1772,94 @@ task_process_status BufferMgrDynamic::doUpdateBufferProfileForSize(buffer_profil return task_process_status::task_success; } +/* + * handleBufferMaxParam, handles the BUFFER_MAX_PARAMETER table which contains some thresholds related to buffer. + * The available fields depend on the key of the item: + * 1. "global", available keys: + * - mmu_size, represents the maximum buffer size of the chip + * 2. "", available keys: + * - max_priority_groups, represents the maximum number of priority groups of the port. + * It is pushed into STATE_DB by ports orchagent when it starts and will be used to generate the default priority groups to reclaim. + * When reserved buffer is reclaimed for a port, and no priority group IDs specified in the "control_fields" in json file, + * the default priority groups will be used to apply zero buffer profiles on all priority groups. + * - max_queues, represents the maximum number of queues of the port. + * It is used in the same way as max_priority_groups. + * - max_headroom_size, represents the maximum headroom size of the port. + * It is used for checking whether the accumulative headroom of the port exceeds the port's threshold + * before applying a new priority group on a port or changing an existing buffer profile. + * It is referenced by lua plugin "check headroom size" only and will not be handled in this function. + */ task_process_status BufferMgrDynamic::handleBufferMaxParam(KeyOpFieldsValuesTuple &tuple) { - string op = kfvOp(tuple); + string &op = kfvOp(tuple); + string &key = kfvKey(tuple); if (op == SET_COMMAND) { - for (auto i : kfvFieldsValues(tuple)) + if (key != "global") { - if (fvField(i) == "mmu_size") + const auto &portSearchRef = m_portInfoLookup.find(key); + if (portSearchRef == m_portInfoLookup.end()) + { + SWSS_LOG_INFO("BUFFER_MAX_PARAM: Port %s is not configured, need retry", key.c_str()); + return task_process_status::task_need_retry; + } + + auto &portInfo = portSearchRef->second; + for (auto i : kfvFieldsValues(tuple)) { - m_mmuSize = fvValue(i); - if (!m_mmuSize.empty()) + auto &value = fvValue(i); + if (fvField(i) == "max_priority_groups") { - m_mmuSizeNumber = atol(m_mmuSize.c_str()); + auto pgCount = atol(value.c_str()); + if (pgCount <= 0) + { + SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Invaild priority group count %s of port %s", value.c_str(), key.c_str()); + return task_process_status::task_failed; + } + + SWSS_LOG_INFO("BUFFER_MAX_PARAM: Got port %s's max priority group %s", key.c_str(), value.c_str()); + + portInfo.maximum_buffer_objects[BUFFER_PG] = (sai_uint32_t)pgCount; } - if (0 == m_mmuSizeNumber) + else if (fvField(i) == "max_queues") { - SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Got invalid mmu size %s", m_mmuSize.c_str()); - return task_process_status::task_failed; + auto queueCount = atol(value.c_str()); + if (queueCount <= 0) + { + SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Invaild queue count %s of port %s", value.c_str(), key.c_str()); + return task_process_status::task_failed; + } + + SWSS_LOG_INFO("BUFFER_MAX_PARAM: Got port %s's max queue %s", key.c_str(), value.c_str()); + + portInfo.maximum_buffer_objects[BUFFER_QUEUE] = (sai_uint32_t)queueCount; } - SWSS_LOG_DEBUG("Handling Default Lossless Buffer Param table field mmu_size %s", m_mmuSize.c_str()); } } - } - - return task_process_status::task_success; + else + { + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == "mmu_size") + { + m_mmuSize = fvValue(i); + if (!m_mmuSize.empty()) + { + m_mmuSizeNumber = atol(m_mmuSize.c_str()); + } + if (0 == m_mmuSizeNumber) + { + SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Got invalid mmu size %s", m_mmuSize.c_str()); + return task_process_status::task_failed; + } + SWSS_LOG_DEBUG("Handling Default Lossless Buffer Param table field mmu_size %s", m_mmuSize.c_str()); + } + } + } + } + + return task_process_status::task_success; } task_process_status BufferMgrDynamic::handleDefaultLossLessBufferParam(KeyOpFieldsValuesTuple &tuple) @@ -1437,6 +2047,7 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu string op = kfvOp(tuple); bool effective_speed_updated = false, mtu_updated = false, admin_status_updated = false, admin_up = false; bool need_check_speed = false; + bool first_time_create = (m_portInfoLookup.find(port) == m_portInfoLookup.end()); SWSS_LOG_DEBUG("Processing command:%s PORT table key %s", op.c_str(), port.c_str()); @@ -1521,7 +2132,7 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu string &mtu = portInfo.mtu; string &effective_speed = portInfo.effective_speed; - bool need_refresh_all_pgs = false, need_remove_all_pgs = false; + bool need_refresh_all_buffer_objects = false, need_handle_admin_down = false, was_admin_down = false; if (effective_speed_updated || mtu_updated) { @@ -1539,11 +2150,11 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu // It's the same case as that in handleCableLenTable mtu = DEFAULT_MTU_STR; } - need_refresh_all_pgs = true; + need_refresh_all_buffer_objects = true; break; case PORT_READY: - need_refresh_all_pgs = true; + need_refresh_all_buffer_objects = true; break; case PORT_ADMIN_DOWN: @@ -1575,28 +2186,70 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu else portInfo.state = PORT_INITIALIZING; - need_refresh_all_pgs = true; + need_refresh_all_buffer_objects = true; + was_admin_down = true; } else { portInfo.state = PORT_ADMIN_DOWN; - need_remove_all_pgs = true; + need_handle_admin_down = true; } SWSS_LOG_INFO("Recalculate shared buffer pool size due to port %s's admin_status updated to %s", port.c_str(), (admin_up ? "up" : "down")); } + else if (first_time_create) + { + // The port is initialized as PORT_ADMIN_DOWN state. + // The admin status not updated after port created means the port is still in admin down state + // We need to apply zero buffers accordingly + need_handle_admin_down = true; + } - // In case both need_remove_all_pgs and need_refresh_all_pgs are true, the need_remove_all_pgs will take effect. + // In case both need_handle_admin_down and need_refresh_all_buffer_objects are true, the need_handle_admin_down will take effect. // This can happen when both effective speed (or mtu) is changed and the admin_status is down. // In this case, we just need record the new effective speed (or mtu) but don't need to refresh all PGs on the port since the port is administratively down - if (need_remove_all_pgs) + if (need_handle_admin_down) { - task_status = removeAllPgsFromPort(port); + m_adminDownPorts.insert(port); + + if (!m_bufferPoolReady) + { + // Applying zero profiles requires all the buffer pools referenced by zero profiles to be ready. + // In case buffer pools haven't been ready yet, the procedure has to be deferred. + m_pendingApplyZeroProfilePorts.insert(port); + SWSS_LOG_NOTICE("Admin-down port %s is not handled for now because buffer pools are not configured yet", port.c_str()); + } + else + { + if (isReadyToReclaimBufferOnPort(port)) + { + reclaimReservedBufferForPort(port, m_portPgLookup, BUFFER_PG); + reclaimReservedBufferForPort(port, m_portQueueLookup, BUFFER_QUEUE); + checkSharedBufferPoolSize(); + } + else + { + m_pendingApplyZeroProfilePorts.insert(port); + SWSS_LOG_NOTICE("Admin-down port %s is not handled for now because maximum numbers of queues and PGs have not been populated into STATE_DB", port.c_str()); + // Return task_success as the port has been inserted into m_pendingApplyZeroProfilePorts + } + } + + task_status = task_process_status::task_success; } - else if (need_refresh_all_pgs) + else if (need_refresh_all_buffer_objects) { + if (was_admin_down) + { + removeSupportedButNotConfiguredItemsOnPort(portInfo, port); + applyNormalBufferObjectsOnPort(port); + m_adminDownPorts.erase(port); + m_pendingApplyZeroProfilePorts.erase(port); + if (m_adminDownPorts.empty()) + unloadZeroPoolAndProfiles(); + } task_status = refreshPgsForPort(port, portInfo.effective_speed, portInfo.cable_length, portInfo.mtu); } } @@ -1641,7 +2294,10 @@ task_process_status BufferMgrDynamic::handleBufferPoolTable(KeyOpFieldsValuesTup } else if (field == buffer_pool_type_field_name) { - bufferPool.ingress = (value == buffer_value_ingress); + if (value == buffer_value_ingress) + bufferPool.direction = BUFFER_INGRESS; + else + bufferPool.direction = BUFFER_EGRESS; } fvVector.emplace_back(field, value); SWSS_LOG_INFO("Inserting BUFFER_POOL table field %s value %s", field.c_str(), value.c_str()); @@ -1708,7 +2364,6 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues SWSS_LOG_ENTER(); string profileName = kfvKey(tuple); string op = kfvOp(tuple); - vector fvVector; SWSS_LOG_DEBUG("Processing command:%s BUFFER_PROFILE table key %s", op.c_str(), profileName.c_str()); if (op == SET_COMMAND) @@ -1735,7 +2390,7 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues { if (!value.empty()) { - auto poolName = value; + auto &poolName = value; if (poolName.empty()) { SWSS_LOG_ERROR("BUFFER_PROFILE: Invalid format of reference to pool: %s", value.c_str()); @@ -1745,11 +2400,11 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues auto poolRef = m_bufferPoolLookup.find(poolName); if (poolRef == m_bufferPoolLookup.end()) { - SWSS_LOG_WARN("Pool %s hasn't been configured yet, skip", poolName.c_str()); + SWSS_LOG_WARN("Pool %s hasn't been configured yet, need retry", poolName.c_str()); return task_process_status::task_need_retry; } profileApp.pool_name = poolName; - profileApp.ingress = poolRef->second.ingress; + profileApp.direction = poolRef->second.direction; } else { @@ -1790,15 +2445,21 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues // For dynamic calculated headroom, user can provide this field only // We need to supply lossless and ingress profileApp.lossless = true; - profileApp.ingress = true; + profileApp.direction = BUFFER_INGRESS; } } - fvVector.emplace_back(field, value); SWSS_LOG_INFO("Inserting BUFFER_PROFILE table field %s value %s", field.c_str(), value.c_str()); } - // don't insert dynamically calculated profiles into APPL_DB - if (profileApp.lossless && profileApp.ingress) + + // Don't insert dynamically calculated profiles into APPL_DB + if (profileApp.lossless) { + if (profileApp.direction != BUFFER_INGRESS) + { + SWSS_LOG_ERROR("BUFFER_PROFILE %s is ingress but referencing an egress pool %s", profileName.c_str(), profileApp.pool_name.c_str()); + return task_process_status::task_success; + } + if (profileApp.dynamic_calculated) { profileApp.state = PROFILE_NORMAL; @@ -1818,11 +2479,8 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues } else { - m_applBufferProfileTable.set(profileName, fvVector); SWSS_LOG_NOTICE("BUFFER_PROFILE %s has been inserted into APPL_DB directly", profileName.c_str()); - - m_stateBufferProfileTable.set(profileName, fvVector); - m_bufferProfileIgnored.insert(profileName); + updateBufferProfileToDb(profileName, profileApp); } } else if (op == DEL_COMMAND) @@ -1859,7 +2517,6 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues } m_bufferProfileLookup.erase(profileName); - m_bufferProfileIgnored.erase(profileName); } else { @@ -1874,15 +2531,206 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, const string &port, const string &op, const KeyOpFieldsValuesTuple &tuple) +void BufferMgrDynamic::handleSetSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const string &port, const string &key, const string &profile) { - vector fvVector; + auto &idsToZero = m_bufferObjectIdsToZero[direction]; + auto &table = m_applBufferObjectTables[direction]; + auto const &objectName = m_bufferObjectNames[direction]; + auto &portInfo = m_portInfoLookup[port]; + + if (m_portInitDone) + { + if (idsToZero.empty()) + { + // If initialization finished, no extra handle required. + // Check whether the key overlaps with supported but not configured map + auto const &idsToAdd = parseObjectNameFromKey(key, 1); + auto idsToAddBitmap = generateBitMapFromIdsStr(idsToAdd); + auto &supportedButNotConfiguredItems = portInfo.supported_but_not_configured_buffer_objects[direction]; + unsigned long overlappedUnconfiguredIdsMap = 0; + string overlappedUnconfiguredIdsStr; + const string &portPrefix = port + delimiter; + for (auto const &ids : supportedButNotConfiguredItems) + { + /* + * An exmaple to explain the logic + * Item no 01234567 + * Supported but not + * configured items: -**-**** + * Items to be added: -----**- + * Slice to be removed + * in the next iternation: ----**** + * Legend: *: valid -: empty + * Currently, supported but not configured items: 1-2, 4-7 + * Items to be added: 5-6 + * It will result in + * - items 4-7 to be removed from APPL_DB + * - items 4, 5-6, 7 to be added to APPL_DB + */ + auto idsBitmap = generateBitMapFromIdsStr(ids); + if ((idsToAddBitmap & idsBitmap) == idsToAddBitmap) + { + // Ids to add overlaps with this slice of unconfigured IDs + // Remove the slice and regenerate them + // It should be slice 4-7 in the above example + auto const &keyToRemove = portPrefix + ids; + SWSS_LOG_INFO("Buffer %s %s overlapped with existing zero item %s, remove the latter first", + objectName.c_str(), key.c_str(), keyToRemove.c_str()); + table.del(keyToRemove); + overlappedUnconfiguredIdsMap = (idsBitmap ^ idsToAddBitmap); + overlappedUnconfiguredIdsStr = ids; + break; + } + /* + * Result after iteration: + * overlappedUnconfiguredIdsMap: ----*--* (Items to be added has been removed from this map) + * overlappedUnconfiguredIdsStr: ----**** + */ + } + // Regenerate unconfigured IDs + supportedButNotConfiguredItems.erase(overlappedUnconfiguredIdsStr); + auto const &splitIds = generateIdListFromMap(overlappedUnconfiguredIdsMap, portInfo.maximum_buffer_objects[direction]); + /* + * Slices returned: ----*--* + * Readd zero profiles for each slice in the set + */ + for (auto const &splitId : splitIds) + { + auto const &keyToAdd = portPrefix + splitId; + updateBufferObjectToDb(keyToAdd, m_bufferZeroProfileName[direction], true, direction); + supportedButNotConfiguredItems.insert(splitId); + SWSS_LOG_INFO("Add %s zero item %s", objectName.c_str(), keyToAdd.c_str()); + } + + auto const &zeroProfile = fetchZeroProfileFromNormalProfile(profile); + if (!zeroProfile.empty()) + { + updateBufferObjectToDb(key, zeroProfile, true, direction); + SWSS_LOG_INFO("Add configured %s item %s as zero profile", objectName.c_str(), key.c_str()); + } + else + { + updateBufferObjectToDb(key, zeroProfile, false, direction); + SWSS_LOG_INFO("No zero profile defined for %s, reclaiming buffer by removing buffer %s %s", + profile.c_str(), objectName.c_str(), key.c_str()); + } + } + return; + } + + if (!idsToZero.empty()) + { + if (port + delimiter + idsToZero != key) + { + // For admin-down ports, if zero profiles have been applied on specified items, + // notify APPL_DB to remove only if the item ID doesn't equal specified items + // and the system is during initialization + // This is to guarantee the port can be "ready" in orchagent + // In case the port is admin down during initialization, the PG will be removed from the port, + // which effectively notifies bufferOrch to add the item to the m_ready_list + table.del(key); + } + } + else + { + // Notify APPL_DB to add zero profile to items + // An side effect is the items will be ready in orchagent (added to m_ready_list) + string &zeroProfile = fetchZeroProfileFromNormalProfile(profile); + if (!zeroProfile.empty()) + { + SWSS_LOG_INFO("Reclaim reserved buffer for %s %s by applying zero profile %s", objectName.c_str(), key.c_str(), zeroProfile.c_str()); + updateBufferObjectToDb(key, zeroProfile, true, direction); + } + else + { + SWSS_LOG_INFO("Reclaim reserved buffer for %s %s by removing it due to empty zero profile", objectName.c_str(), key.c_str()); + updateBufferObjectToDb(key, zeroProfile, false, direction); + } + } +} + +void BufferMgrDynamic::handleDelSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const string &port, const string &key, port_info_t &portInfo) +{ + auto &idsToZero = m_bufferObjectIdsToZero[direction]; + auto &supportedNotConfiguredItems = portInfo.supported_but_not_configured_buffer_objects[direction]; + auto const &objectName = m_bufferObjectNames[direction]; + + if (idsToZero.empty()) + { + // For admin down ports, if zero profiles have been applied to all configured items + // do NOT remove it otherwise SDK default value will be set for the items + // Move the key to supported_but_not_configured_items so that the slice of items + // can be removed when the port is started up + auto const &idsToDelStr = parseObjectNameFromKey(key, 1); + auto const &keyPrefix = port + delimiter; + auto idsToDelMap = generateBitMapFromIdsStr(idsToDelStr); + // If this item can be combined with any existing items, let's do. + // adjancentItemsSet will hold all adjancent slices. + // Otherwise, just add it to the list. + set adjancentItemsSet; + for (auto const &idStr : supportedNotConfiguredItems) + { + unsigned long idsMap = generateBitMapFromIdsStr(idStr); + // Make sure there is no overlap + if ((idsMap & idsToDelMap) == 0) + { + if (isItemIdsMapContinuous(idsMap|idsToDelMap, portInfo.maximum_buffer_objects[direction])) + { + SWSS_LOG_INFO("Removing buffer %s item %s, found adjancent item %s, remove the later first", + objectName.c_str(), key.c_str(), idStr.c_str()); + adjancentItemsSet.insert(idStr); + updateBufferObjectToDb(keyPrefix + idStr, "", false, direction); + idsToDelMap |= idsMap; + } + } + } + + if (!adjancentItemsSet.empty()) + { + SWSS_LOG_INFO("Removing buffer %s item %s from APPL_DB because it will be combined", objectName.c_str(), key.c_str()); + updateBufferObjectToDb(key, "", false, direction); + + for (auto const &idStr : adjancentItemsSet) + { + supportedNotConfiguredItems.erase(idStr); + } + + auto const combinedIdsStr = generateIdListFromMap(idsToDelMap, portInfo.maximum_buffer_objects[direction]); + // combinedIdsStr should contain only one id string. + for (auto const &idStr : combinedIdsStr) + { + SWSS_LOG_INFO("Removing buffer %s item %s, got combined item %s", + objectName.c_str(), key.c_str(), idStr.c_str()); + supportedNotConfiguredItems.insert(idStr); + // According to the logic in reclaimReservedBufferForPort, + // only if the m_bufferZeroProfileName is not empty will supported_but_not_configured list be generated. + // Now that adjancentItemsSet, which is a sub set of supported_but_not_configured, is NOT empty, + // m_bufferZeroProfileName must NOT be empty. + updateBufferObjectToDb(keyPrefix + idStr, m_bufferZeroProfileName[direction], true, direction); + } + } + else if (!m_bufferZeroProfileName[direction].empty()) + { + SWSS_LOG_INFO("Removing buffer %s item %s", objectName.c_str(), key.c_str()); + supportedNotConfiguredItems.insert(idsToDelStr); + } + } + + // For admin down ports, if zero profiles configured on specified items only + // it has been removed from APPL_DB when the port is admin down + // Just removing it from m_portQueueLookup or m_portPgLookup should suffice. + // Do NOT touch APPL_DB +} + +task_process_status BufferMgrDynamic::handleSingleBufferPgEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) +{ + string op = kfvOp(tuple); buffer_pg_t &bufferPg = m_portPgLookup[port][key]; + port_info_t &portInfo = m_portInfoLookup[port]; SWSS_LOG_DEBUG("Processing command:%s table BUFFER_PG key %s", op.c_str(), key.c_str()); if (op == SET_COMMAND) { - bool ignored = false; bool pureDynamic = true; // For set command: // 1. Create the corresponding table entries in APPL_DB @@ -1895,7 +2743,7 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, if (!bufferPg.configured_profile_name.empty()) { m_bufferProfileLookup[bufferPg.configured_profile_name].port_pgs.erase(key); - bufferPg.configured_profile_name = ""; + bufferPg.configured_profile_name.clear(); } for (auto i = kfvFieldsValues(tuple).begin(); i != kfvFieldsValues(tuple).end(); i++) @@ -1908,7 +2756,7 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, { // Headroom override pureDynamic = false; - string profileName = value; + const string &profileName = value; if (profileName.empty()) { SWSS_LOG_ERROR("BUFFER_PG: Invalid format of reference to profile: %s", value.c_str()); @@ -1918,22 +2766,11 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, auto searchRef = m_bufferProfileLookup.find(profileName); if (searchRef == m_bufferProfileLookup.end()) { - if (m_bufferProfileIgnored.find(profileName) != m_bufferProfileIgnored.end()) - { - // Referencing an ignored profile, the PG should be ignored as well - ignored = true; - bufferPg.dynamic_calculated = false; - bufferPg.lossless = false; - bufferPg.configured_profile_name = profileName; - } - else - { - // In this case, we shouldn't set the dynamic calculated flag to true - // It will be updated when its profile configured. - bufferPg.dynamic_calculated = false; - SWSS_LOG_WARN("Profile %s hasn't been configured yet, skip", profileName.c_str()); - return task_process_status::task_need_retry; - } + // In this case, we shouldn't set the dynamic calculated flag to true + // It will be updated when its profile configured. + bufferPg.dynamic_calculated = false; + SWSS_LOG_WARN("Profile %s hasn't been configured yet, skip", profileName.c_str()); + return task_process_status::task_need_retry; } else { @@ -1952,7 +2789,6 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, return task_process_status::task_invalid_entry; } - fvVector.emplace_back(field, value); SWSS_LOG_INFO("Inserting BUFFER_PG table field %s value %s", field.c_str(), value.c_str()); } @@ -1963,25 +2799,21 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, bufferPg.lossless = true; } - if (!ignored && bufferPg.lossless) + if (PORT_ADMIN_DOWN == portInfo.state) + { + // In case the port is admin down during initialization, the PG will be removed from the port, + // which effectively notifies bufferOrch to add the item to the m_ready_list + handleSetSingleBufferObjectOnAdminDownPort(BUFFER_PG, port, key, bufferPg.configured_profile_name); + } + else if (bufferPg.lossless) { doUpdatePgTask(key, port); } else { - port_info_t &portInfo = m_portInfoLookup[port]; - if (PORT_ADMIN_DOWN != portInfo.state) - { - SWSS_LOG_NOTICE("Inserting BUFFER_PG table entry %s into APPL_DB directly", key.c_str()); - m_applBufferPgTable.set(key, fvVector); - bufferPg.running_profile_name = bufferPg.configured_profile_name; - } - else if (!m_portInitDone) - { - // In case the port is admin down during initialization, the PG will be removed from the port, - // which effectively notifies bufferOrch to add the item to the m_ready_list - m_applBufferPgTable.del(key); - } + SWSS_LOG_NOTICE("Inserting BUFFER_PG table entry %s into APPL_DB directly", key.c_str()); + bufferPg.running_profile_name = bufferPg.configured_profile_name; + updateBufferObjectToDb(key, bufferPg.running_profile_name, true); } if (!bufferPg.configured_profile_name.empty()) @@ -1997,6 +2829,12 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, string &runningProfileName = bufferPg.running_profile_name; string &configProfileName = bufferPg.configured_profile_name; + if (!m_supportRemoving) + { + SWSS_LOG_ERROR("Removing priority group %s is not supported on the platform", key.c_str()); + return task_process_status::task_failed; + } + if (!runningProfileName.empty()) { m_bufferProfileLookup[runningProfileName].port_pgs.erase(key); @@ -2006,14 +2844,18 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, m_bufferProfileLookup[configProfileName].port_pgs.erase(key); } - if (bufferPg.lossless) + if (portInfo.state == PORT_ADMIN_DOWN) + { + handleDelSingleBufferObjectOnAdminDownPort(BUFFER_PG, port, key, portInfo); + } + else if (bufferPg.lossless) { doRemovePgTask(key, port); } else { SWSS_LOG_NOTICE("Removing BUFFER_PG table entry %s from APPL_DB directly", key.c_str()); - m_applBufferPgTable.del(key); + m_applBufferObjectTables[BUFFER_PG].del(key); } m_portPgLookup[port].erase(key); @@ -2033,109 +2875,270 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleBufferPgTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::checkBufferProfileDirection(const string &profiles, buffer_direction_t dir) { - SWSS_LOG_ENTER(); - string key = kfvKey(tuple); - string op = kfvOp(tuple); - - transformSeperator(key); - string ports = parseObjectNameFromKey(key); - string pgs = parseObjectNameFromKey(key, 1); - if (ports.empty() || pgs.empty()) + // Fetch profile and check whether it's an egress profile + vector profileRefList = tokenize(profiles, ','); + for (auto &profileName : profileRefList) { - SWSS_LOG_ERROR("Invalid key format %s for BUFFER_PG table", key.c_str()); - return task_process_status::task_invalid_entry; + auto profileSearchRef = m_bufferProfileLookup.find(profileName); + if (profileSearchRef == m_bufferProfileLookup.end()) + { + SWSS_LOG_NOTICE("Profile %s doesn't exist, need retry", profileName.c_str()); + return task_process_status::task_need_retry; + } + + auto &profileObj = profileSearchRef->second; + if (dir != profileObj.direction) + { + SWSS_LOG_ERROR("Profile %s's direction is %s but %s is expected, applying profile failed", + profileName.c_str(), + m_bufferDirectionNames[profileObj.direction].c_str(), + m_bufferDirectionNames[dir].c_str()); + return task_process_status::task_failed; + } } - auto portsList = tokenize(ports, ','); + return task_process_status::task_success; +} - task_process_status rc = task_process_status::task_success; +task_process_status BufferMgrDynamic::handleSingleBufferQueueEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) +{ + SWSS_LOG_ENTER(); - if (portsList.size() == 1) + const auto &op = kfvOp(tuple); + const auto &queues = key; + auto &portInfo = m_portInfoLookup[port]; + + if (op == SET_COMMAND) { - rc = handleOneBufferPgEntry(key, ports, op, tuple); + auto &portQueue = m_portQueueLookup[port][queues]; + + SWSS_LOG_INFO("Inserting entry BUFFER_QUEUE_TABLE:%s to APPL_DB", key.c_str()); + + for (auto i : kfvFieldsValues(tuple)) + { + // Transform the separator in values from "|" to ":" + if (fvField(i) == buffer_profile_field_name) + { + auto rc = checkBufferProfileDirection(fvValue(i), BUFFER_EGRESS); + if (rc != task_process_status::task_success) + return rc; + portQueue.running_profile_name = fvValue(i); + SWSS_LOG_NOTICE("Queue %s has been configured on the system, referencing profile %s", key.c_str(), fvValue(i).c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown field %s in BUFFER_QUEUE|%s", fvField(i).c_str(), key.c_str()); + continue; + } + + SWSS_LOG_INFO("Inserting field %s value %s", fvField(i).c_str(), fvValue(i).c_str()); + } + + // TODO: check overlap. Currently, assume there is no overlap + + if (PORT_ADMIN_DOWN == portInfo.state) + { + handleSetSingleBufferObjectOnAdminDownPort(BUFFER_QUEUE, port, key, portQueue.running_profile_name); + } + else + { + updateBufferObjectToDb(key, portQueue.running_profile_name, true, BUFFER_QUEUE); + } } - else + else if (op == DEL_COMMAND) { - for (auto port : portsList) + if (!m_supportRemoving) { - string singleKey = port + ':' + pgs; - rc = handleOneBufferPgEntry(singleKey, port, op, tuple); - if (rc == task_process_status::task_need_retry) - return rc; + SWSS_LOG_ERROR("Removing queue %s is not supported on the platform", key.c_str()); + return task_process_status::task_failed; + } + SWSS_LOG_INFO("Removing entry %s from APPL_DB", key.c_str()); + m_portQueueLookup[port].erase(queues); + if (PORT_ADMIN_DOWN == portInfo.state) + { + handleDelSingleBufferObjectOnAdminDownPort(BUFFER_QUEUE, port, key, portInfo); + } + else + { + m_applBufferObjectTables[BUFFER_QUEUE].del(key); } } - return rc; + return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleBufferQueueTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::handleSingleBufferPortProfileListEntry(const string &key, buffer_direction_t dir, const KeyOpFieldsValuesTuple &tuple) { - return doBufferTableTask(tuple, m_applBufferQueueTable); + SWSS_LOG_ENTER(); + + const string &port = key; + const string &op = kfvOp(tuple); + const string &tableName = dir == BUFFER_INGRESS ? APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME : APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME; + ProducerStateTable &appTable = m_applBufferProfileListTables[dir]; + port_profile_list_lookup_t &profileListLookup = m_portProfileListLookups[dir]; + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Inserting entry %s:%s to APPL_DB", tableName.c_str(), key.c_str()); + + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == buffer_profile_list_field_name) + { + auto rc = checkBufferProfileDirection(fvValue(i), dir); + if (rc != task_process_status::task_success) + return rc; + profileListLookup[port] = fvValue(i); + SWSS_LOG_NOTICE("%s %s has been configured on the system, referencing profile list %s", tableName.c_str(), key.c_str(), fvValue(i).c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown field %s in %s", fvField(i).c_str(), key.c_str()); + continue; + } + } + + auto &portInfo = m_portInfoLookup[port]; + if (PORT_ADMIN_DOWN != portInfo.state) + { + // Only apply profile list on admin up port + // For admin-down ports, zero profile list has been applied on the port when it entered admin-down state + updateBufferObjectListToDb(key, profileListLookup[port], dir); + } + } + else if (op == DEL_COMMAND) + { + // Not supported on Mellanox platform for now. + SWSS_LOG_INFO("Removing entry %s:%s from APPL_DB", tableName.c_str(), key.c_str()); + profileListLookup.erase(port); + appTable.del(key); + } + + return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::handleSingleBufferPortIngressProfileListEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) { - return doBufferTableTask(tuple, m_applBufferIngressProfileListTable); + return handleSingleBufferPortProfileListEntry(key, BUFFER_INGRESS, tuple); } -task_process_status BufferMgrDynamic::handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::handleSingleBufferPortEgressProfileListEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) { - return doBufferTableTask(tuple, m_applBufferEgressProfileListTable); + return handleSingleBufferPortProfileListEntry(key, BUFFER_EGRESS, tuple); } /* - * This function copies the data from tables in CONFIG_DB to APPL_DB. - * With dynamically buffer calculation supported, the following tables - * will be moved to APPL_DB from CONFIG_DB because the CONFIG_DB contains - * configured entries only while APPL_DB contains dynamically generated entries - * - BUFFER_POOL - * - BUFFER_PROFILE - * - BUFFER_PG - * The following tables have to be moved to APPL_DB because they reference - * some entries that have been moved to APPL_DB - * - BUFFER_QUEUE - * - BUFFER_PORT_INGRESS_PROFILE_LIST - * - BUFFER_PORT_EGRESS_PROFILE_LIST - * One thing we need to handle is to transform the separator from | to : - * The following items contain separator: - * - keys of each item - * - pool in BUFFER_POOL - * - profile in BUFFER_PG + * handleBufferObjectTables + * + * For the buffer object tables, like BUFFER_PG, BUFFER_QUEUE, BUFFER_PORT_INGRESS_PROFILE_LIST, and BUFFER_PORT_EGRESS_PROFILE_LIST, + * their keys can be either a single port or a port list separated by a comma, which can be handled by a common logic. + * So the logic to parse the keys is common and the logic to handle each table is different. + * This function is introduced to handle the common logic and a handler map (buffer_single_item_handler_map) is introduced + * to dispatch to different handler according to the table name. + * + * Arguments: + * tuple, the KeyOpFieldsValuesTuple, containing key, op, list of tuples consisting of field and value pair + * table, the name of the table + * keyWithIds, whether the keys contains sublevel IDs of objects. + * - For queue and priority groups, it is true since the keys format is like |IDs + * Eg: input key "Ethernet0,Ethernet4,Ethernet8,Ethernet12|3-4" will be decomposed to the following list + * [Ethernet0|3-4, Ethernet4|3-4, Ethernet8|3-4, Ethernet12|3-4] + * - For profile list tables, it is false since the keys format is + * Eg: input key "Ethernet0,Ethernet4,Ethernet8,Ethernet12" will be decomposed to the following list + * [Ethernet0, Ethernet4, Ethernet8, Ethernet12] + * + * It is wrapped by the following functions which are the table handler of the above tables: + * - handleBufferPgTable + * - handleBufferQueueTable + * - handleBufferPortIngressProfileListTable + * - handleBufferPortEgressProfileListTable + * and will call the following table handlers which handles table update for a single port: + * - handleSingleBufferPgEntry + * - handleSingleBufferQueueEntry + * - handleSingleBufferPortIngressProfileListEntry + * - handleSingleBufferPortEgressProfileListEntry + * + * The flow: + * 1. Parse the key. + * 2. Fetch the handler according to the table name + * 3. For each port in the key list, + * - Construct a new key as + * - A single port + IDs if keyWidthIds is true + * - Or a single port only if keyWidthIds is false + * - Call the corresponding handler. */ -task_process_status BufferMgrDynamic::doBufferTableTask(KeyOpFieldsValuesTuple &tuple, ProducerStateTable &applTable) +task_process_status BufferMgrDynamic::handleBufferObjectTables(KeyOpFieldsValuesTuple &tuple, const string &table, bool keyWithIds) { SWSS_LOG_ENTER(); - string key = kfvKey(tuple); - const string &name = applTable.getTableName(); - // Transform the separator in key from "|" to ":" transformSeperator(key); - string op = kfvOp(tuple); - if (op == SET_COMMAND) + string ports = parseObjectNameFromKey(key); + if (ports.empty()) { - vector fvVector; - - SWSS_LOG_INFO("Inserting entry %s|%s from CONFIG_DB to APPL_DB", name.c_str(), key.c_str()); + SWSS_LOG_ERROR("Invalid key format %s for %s table", key.c_str(), table.c_str()); + return task_process_status::task_invalid_entry; + } - for (auto i : kfvFieldsValues(tuple)) + string ids; + if (keyWithIds) + { + ids = parseObjectNameFromKey(key, 1); + if (ids.empty()) { - // Transform the separator in values from "|" to ":" - fvVector.emplace_back(fvField(i), fvValue(i)); - SWSS_LOG_INFO("Inserting field %s value %s", fvField(i).c_str(), fvValue(i).c_str()); + SWSS_LOG_ERROR("Invalid key format %s for %s table", key.c_str(), table.c_str()); + return task_process_status::task_invalid_entry; } - applTable.set(key, fvVector); } - else if (op == DEL_COMMAND) + + auto portsList = tokenize(ports, ','); + + task_process_status rc = task_process_status::task_success; + auto &handler = m_bufferSingleItemHandlerMap[table]; + + if (portsList.size() == 1) { - SWSS_LOG_INFO("Removing entry %s from APPL_DB", key.c_str()); - applTable.del(key); + rc = (this->*handler)(key, ports, tuple); + } + else + { + for (auto port : portsList) + { + string singleKey; + if (keyWithIds) + singleKey = port + ':' + ids; + else + singleKey = port; + rc = (this->*handler)(singleKey, port, tuple); + if (rc == task_process_status::task_need_retry) + return rc; + } } - return task_process_status::task_success; + return rc; +} + +task_process_status BufferMgrDynamic::handleBufferPgTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_PG_TABLE_NAME, true); +} + +task_process_status BufferMgrDynamic::handleBufferQueueTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_QUEUE_TABLE_NAME, true); +} + +task_process_status BufferMgrDynamic::handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, false); +} + +task_process_status BufferMgrDynamic::handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME, false); } void BufferMgrDynamic::doTask(Consumer &consumer) @@ -2175,7 +3178,174 @@ void BufferMgrDynamic::doTask(Consumer &consumer) } } +/* + * We do not apply any buffer profiles and objects until the buffer pools are ready. + * This is to prevent buffer orchagent from keeping retrying if buffer profiles and objects are inserted to APPL_DB while buffer pools are not. + * + * Originally, all buffer profiles and objects were applied to APPL_DB as soon as they were learnt from CONFIG_DB. + * The buffer profiles, and objects will be available as soon as they are received from CONFIG_DB when the system is starting + * but it takes more seconds for the buffer pools to be applied to APPL_DB + * because the sizes need to be calculated first and can be calculated only after some other data available. + * This causes buffer orchagent receives buffer profiles and objects first and then buffer pools. + * The buffer orchagent has to keep retrying in such case, which wastes time and incurs error messages. + * + * Now, we will guarantee the buffer profiles and objects to be applied only after buffer pools are ready. + * This is achieved by the following steps: + * - m_bufferPoolReady indicates whether the buffer pools are ready. + * - When the buffer profiles and objects are received from CONFIG_DB, + * they will be handled and stored in internal data struct + * (but not applied to APPL_DB until the buffer pools are ready, AKA. m_bufferPoolReady == true) + * - Once buffer pools are ready, all stored buffer profiles and objects will be applied to APPL_DB by this function. + * + * Applying additional zero profiles, which is supported but not configured profiles, if any, will be deferred in non-warmreboot. + * This is to accelerate the start flow especially for fast reboot. + * + * The progress of pushing buffer pools into APPL_DB has also been accelerated by the lua plugin for calculating buffer sizes. + * Originally, if the data are not available for calculating buffer sizes, it just return an empty vectors and buffer manager will retry + * Now, the lua plugin will return the sizes calculated by the following logic: + * - If there are buffer sizes in APPL_DB, return the sizes in APPL_DB. + * - Otherwise, return all 0 as sizes. + * This makes sure there will be available sizes of buffer pools as sonn as possible when the system is starting + * The sizes will be updated to non zero when the data for calculating it are ready. + * Even this is Mellanox specific, other vendors can also take advantage of this logic. + */ +void BufferMgrDynamic::handlePendingBufferObjects() +{ + if (m_bufferPoolReady) + { + if (!m_pendingApplyZeroProfilePorts.empty()) + { + // No action as no zero profiles defined. + if (!m_zeroPoolAndProfileInfo.empty() && !m_zeroProfilesLoaded) + { + loadZeroPoolAndProfiles(); + } + } + + if (m_bufferObjectsPending) + { + // Apply all pending buffer profiles + for (auto &profile : m_bufferProfileLookup) + { + updateBufferProfileToDb(profile.first, profile.second); + } + + for (auto &port : m_portInfoLookup) + { + if (port.second.state != PORT_ADMIN_DOWN) + { + // The admin-down ports should not be touched here. Two scenarios + // 1. If admin-status is handled ahead of m_bufferPoolReady being true, + // They should be in m_pendingApplyZeroProfilePorts and will be handled later in this function + // 2. Otherwise, they should have been handled and zero profiles have been applied. + refreshPgsForPort(port.first, port.second.effective_speed, port.second.cable_length, port.second.mtu); + + // Apply pending buffer queues + auto &queues = m_portQueueLookup[port.first]; + for (auto &queue : queues) + { + updateBufferObjectToDb(queue.first, queue.second.running_profile_name, true, BUFFER_QUEUE); + } + + // Apply all pending buffer profile lists + for (auto dir : m_bufferDirections) + { + auto &profileList = m_portProfileListLookups[dir][port.first]; + if (!profileList.empty()) + updateBufferObjectListToDb(port.first, profileList, dir); + } + } + } + + m_bufferObjectsPending = false; + } + + if (!m_pendingApplyZeroProfilePorts.empty()) + { + // m_pendingApplyZeroProfilePorts contains all the admin down ports + // We do NOT iterate all admin down ports but the set containing ports need to be applied zero profiles + set portsNeedRetry; + for ( auto &port : m_pendingApplyZeroProfilePorts) + { + if (isReadyToReclaimBufferOnPort(port)) + { + reclaimReservedBufferForPort(port, m_portPgLookup, BUFFER_PG); + reclaimReservedBufferForPort(port, m_portQueueLookup, BUFFER_QUEUE); + SWSS_LOG_NOTICE("Admin-down port %s is handled after buffer pools have been configured", port.c_str()); + } + else + { + SWSS_LOG_NOTICE("Admin-down port %s is still not ready to reclaim after buffer pools have been configured, need retry", port.c_str()); + portsNeedRetry.insert(port); + } + } + m_pendingApplyZeroProfilePorts = move(portsNeedRetry); + } + + // configuredItemsDone means all buffer objects (profiles, priority groups, queues, profile lists) in the CONFIG_DB have been applied to APPL_DB + // - on admin up ports, normal profiles are applied + // - on admin down ports, zero profiles are applied + bool configuredItemsDone = !m_bufferObjectsPending && m_pendingApplyZeroProfilePorts.empty(); + + if (WarmStart::isWarmStart()) + { + // For warm restart, all buffer items have been applied now + if (configuredItemsDone) + { + WarmStart::setWarmStartState("buffermgrd", WarmStart::REPLAYED); + // There is no operation to be performed for buffermgrd reconcillation + // Hence mark it reconciled right away + WarmStart::setWarmStartState("buffermgrd", WarmStart::RECONCILED); + m_bufferCompletelyInitialized = true; + SWSS_LOG_NOTICE("All bufer configuration has been applied. Buffer initialization done"); + } + } + else + { + // For fast reboot, cold reboot and other initialization flow, the additional zero profiles will be applied + // 30 seconds later once normal profiles and zero profiles of configured items have been applied + // This is to accelerate fast reboot flow. + // m_waitApplyAdditionalZeroProfiles is initialized as 3 during buffer manager initialization + // Timer's period is 10. So total deferred time is 3*10 = 30 seconds + if (m_waitApplyAdditionalZeroProfiles > 0) + { + if (m_portInitDone && configuredItemsDone) + { + SWSS_LOG_NOTICE("Additional zero profiles will be appied after %d seconds", m_waitApplyAdditionalZeroProfiles * 10); + m_waitApplyAdditionalZeroProfiles --; + } + } + else + { + // For admin down ports, apply supported but not configured items + for (auto dir : m_bufferDirections) + { + for ( auto &port : m_pendingSupportedButNotConfiguredPorts[dir]) + { + auto &portInfo = m_portInfoLookup[port]; + for(auto &it: portInfo.supported_but_not_configured_buffer_objects[dir]) + { + auto const &key = port + delimiter + it; + SWSS_LOG_INFO("Applying additional zero profiles on port %s", key.c_str()); + updateBufferObjectToDb(key, m_bufferZeroProfileName[dir], true, dir); + } + } + m_pendingSupportedButNotConfiguredPorts[dir].clear(); + } + + if (configuredItemsDone) + { + m_bufferCompletelyInitialized = true; + SWSS_LOG_NOTICE("All bufer configuration has been applied. Buffer initialization done"); + } + } + } + } +} + void BufferMgrDynamic::doTask(SelectableTimer &timer) { checkSharedBufferPoolSize(true); + if (!m_bufferCompletelyInitialized) + handlePendingBufferObjects(); } diff --git a/cfgmgr/buffermgrdyn.h b/cfgmgr/buffermgrdyn.h index fa1c69dbc2..d316aee73c 100644 --- a/cfgmgr/buffermgrdyn.h +++ b/cfgmgr/buffermgrdyn.h @@ -16,12 +16,21 @@ namespace swss { #define BUFFERMGR_TIMER_PERIOD 10 +typedef enum { + BUFFER_INGRESS = 0, + BUFFER_PG = BUFFER_INGRESS, + BUFFER_EGRESS = 1, + BUFFER_QUEUE = BUFFER_EGRESS, + BUFFER_DIR_MAX +} buffer_direction_t; + typedef struct { - bool ingress; + buffer_direction_t direction; bool dynamic_size; std::string total_size; std::string mode; std::string xoff; + std::string zero_profile_name; } buffer_pool_t; // State of the profile. @@ -45,8 +54,8 @@ typedef struct { profile_state_t state; bool dynamic_calculated; bool static_configured; - bool ingress; bool lossless; + buffer_direction_t direction; // fields representing parameters by which the headroom is calculated std::string speed; @@ -69,6 +78,10 @@ typedef struct { } buffer_profile_t; typedef struct { + std::string running_profile_name; +} buffer_object_t; + +typedef struct : public buffer_object_t{ bool lossless; bool dynamic_calculated; bool static_configured; @@ -76,7 +89,6 @@ typedef struct { // - the configured profile for static one, // - the dynamically generated profile otherwise. std::string configured_profile_name; - std::string running_profile_name; } buffer_pg_t; typedef enum { @@ -101,6 +113,8 @@ typedef struct { std::string supported_speeds; long lane_count; + sai_uint32_t maximum_buffer_objects[BUFFER_DIR_MAX]; + std::set supported_but_not_configured_buffer_objects[BUFFER_DIR_MAX]; } port_info_t; //TODO: @@ -116,12 +130,16 @@ typedef std::map port_info_lookup_t; typedef std::map buffer_profile_lookup_t; //map from name to pool typedef std::map buffer_pool_lookup_t; -//port -> headroom override -typedef std::map headroom_override_t; //map from pg to info typedef std::map buffer_pg_lookup_t; //map from port to all its pgs typedef std::map port_pg_lookup_t; +//map from object name to its profile +typedef std::map buffer_object_lookup_t; +//map from port to all its objects +typedef std::map port_object_lookup_t; +//map from port to profile_list +typedef std::map port_profile_list_lookup_t; //map from gearbox model to gearbox delay typedef std::map gearbox_delay_t; @@ -132,30 +150,52 @@ class BufferMgrDynamic : public Orch using Orch::doTask; private: + std::string m_platform; + std::vector m_bufferDirections; + const std::string m_bufferObjectNames[BUFFER_DIR_MAX]; + const std::string m_bufferDirectionNames[BUFFER_DIR_MAX]; + typedef task_process_status (BufferMgrDynamic::*buffer_table_handler)(KeyOpFieldsValuesTuple &t); typedef std::map buffer_table_handler_map; typedef std::pair buffer_handler_pair; - std::string m_platform; - buffer_table_handler_map m_bufferTableHandlerMap; + typedef task_process_status (BufferMgrDynamic::*buffer_single_item_handler)(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + typedef std::map buffer_single_item_handler_map; + typedef std::pair buffer_single_item_handler_pair; + + buffer_single_item_handler_map m_bufferSingleItemHandlerMap; + bool m_portInitDone; - bool m_firstTimeCalculateBufferPool; + bool m_bufferPoolReady; + bool m_bufferObjectsPending; + bool m_bufferCompletelyInitialized; std::string m_configuredSharedHeadroomPoolSize; std::shared_ptr m_applDb = nullptr; SelectableTimer *m_buffermgrPeriodtimer = nullptr; - // PORT and CABLE_LENGTH table and caches - Table m_cfgPortTable; - Table m_cfgCableLenTable; + // Fields for zero pool and profiles + std::vector m_zeroPoolAndProfileInfo; + std::set m_zeroPoolNameSet; + std::vector> m_zeroProfiles; + bool m_zeroProfilesLoaded; + bool m_supportRemoving; + std::string m_bufferZeroProfileName[BUFFER_DIR_MAX]; + std::string m_bufferObjectIdsToZero[BUFFER_DIR_MAX]; + + // PORT table and caches Table m_statePortTable; // m_portInfoLookup // key: port name // updated only when a port's speed and cable length updated port_info_lookup_t m_portInfoLookup; + std::set m_adminDownPorts; + std::set m_pendingApplyZeroProfilePorts; + std::set m_pendingSupportedButNotConfiguredPorts[BUFFER_DIR_MAX]; + int m_waitApplyAdditionalZeroProfiles; // BUFFER_POOL table and cache ProducerStateTable m_applBufferPoolTable; @@ -164,20 +204,15 @@ class BufferMgrDynamic : public Orch // BUFFER_PROFILE table and caches ProducerStateTable m_applBufferProfileTable; - Table m_cfgBufferProfileTable; Table m_stateBufferProfileTable; // m_bufferProfileLookup - the cache for the following set: // 1. CFG_BUFFER_PROFILE // 2. Dynamically calculated headroom info stored in APPL_BUFFER_PROFILE // key: profile name buffer_profile_lookup_t m_bufferProfileLookup; - // A set where the ignored profiles are stored. - // A PG that reference an ignored profile should also be ignored. - std::set m_bufferProfileIgnored; // BUFFER_PG table and caches - ProducerStateTable m_applBufferPgTable; - Table m_cfgBufferPgTable; + ProducerStateTable m_applBufferObjectTables[BUFFER_DIR_MAX]; // m_portPgLookup - the cache for CFG_BUFFER_PG and APPL_BUFFER_PG // 1st level key: port name, 2nd level key: PGs // Updated in: @@ -185,16 +220,23 @@ class BufferMgrDynamic : public Orch // 2. refreshPriorityGroupsForPort, speed/cable length updated port_pg_lookup_t m_portPgLookup; + // BUFFER_QUEUE table and caches + // m_portQueueLookup - the cache for BUFFER_QUEUE table + // 1st level key: port name, 2nd level key: queues + port_object_lookup_t m_portQueueLookup; + + // BUFFER_INGRESS_PROFILE_LIST/BUFFER_EGRESS_PROFILE_LIST table and caches + ProducerStateTable m_applBufferProfileListTables[BUFFER_DIR_MAX]; + port_profile_list_lookup_t m_portProfileListLookups[BUFFER_DIR_MAX]; + + // table and caches + port_profile_list_lookup_t m_portEgressProfileListLookup; + // Other tables - Table m_cfgLosslessPgPoolTable; Table m_cfgDefaultLosslessBufferParam; Table m_stateBufferMaximumTable; - ProducerStateTable m_applBufferQueueTable; - ProducerStateTable m_applBufferIngressProfileListTable; - ProducerStateTable m_applBufferEgressProfileListTable; - Table m_applPortTable; bool m_supportGearbox; @@ -218,6 +260,8 @@ class BufferMgrDynamic : public Orch // Initializers void initTableHandlerMap(); void parseGearboxInfo(std::shared_ptr> gearboxInfo); + void loadZeroPoolAndProfiles(); + void unloadZeroPoolAndProfiles(); // Tool functions to parse keys and references std::string getPgPoolMode(); @@ -229,11 +273,13 @@ class BufferMgrDynamic : public Orch return !value.empty() && value != "0"; } std::string getMaxSpeedFromList(std::string speedList); + std::string &fetchZeroProfileFromNormalProfile(const std::string &profile); // APPL_DB table operations void updateBufferPoolToDb(const std::string &name, const buffer_pool_t &pool); void updateBufferProfileToDb(const std::string &name, const buffer_profile_t &profile); - void updateBufferPgToDb(const std::string &key, const std::string &profile, bool add); + void updateBufferObjectToDb(const std::string &key, const std::string &profile, bool add, buffer_direction_t dir); + void updateBufferObjectListToDb(const std::string &key, const std::string &profileList, buffer_direction_t dir); // Meta flows bool needRefreshPortDueToEffectiveSpeed(port_info_t &portInfo, std::string &portName); @@ -244,30 +290,41 @@ class BufferMgrDynamic : public Orch void releaseProfile(const std::string &profile_name); bool isHeadroomResourceValid(const std::string &port, const buffer_profile_t &profile, const std::string &new_pg); void refreshSharedHeadroomPool(bool enable_state_updated_by_ratio, bool enable_state_updated_by_size); + task_process_status checkBufferProfileDirection(const std::string &profiles, buffer_direction_t dir); + std::string constructZeroProfileListFromNormalProfileList(const std::string &normalProfileList, const std::string &port); + void removeSupportedButNotConfiguredItemsOnPort(port_info_t &portInfo, const std::string &port); + void applyNormalBufferObjectsOnPort(const std::string &port); + void handlePendingBufferObjects(); + void handleSetSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const std::string &port, const std::string &key, const std::string &profile); + void handleDelSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const std::string &port, const std::string &key, port_info_t &portInfo); + bool isReadyToReclaimBufferOnPort(const std::string &port); // Main flows - task_process_status removeAllPgsFromPort(const std::string &port); + template task_process_status reclaimReservedBufferForPort(const std::string &port, T &obj, buffer_direction_t dir); task_process_status refreshPgsForPort(const std::string &port, const std::string &speed, const std::string &cable_length, const std::string &mtu, const std::string &exactly_matched_key); task_process_status doUpdatePgTask(const std::string &pg_key, const std::string &port); task_process_status doRemovePgTask(const std::string &pg_key, const std::string &port); - task_process_status doAdminStatusTask(const std::string port, const std::string adminStatus); task_process_status doUpdateBufferProfileForDynamicTh(buffer_profile_t &profile); task_process_status doUpdateBufferProfileForSize(buffer_profile_t &profile, bool update_pool_size); // Table update handlers - task_process_status handleBufferMaxParam(KeyOpFieldsValuesTuple &t); - task_process_status handleDefaultLossLessBufferParam(KeyOpFieldsValuesTuple &t); - task_process_status handleCableLenTable(KeyOpFieldsValuesTuple &t); - task_process_status handlePortStateTable(KeyOpFieldsValuesTuple &t); - task_process_status handlePortTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferPoolTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferProfileTable(KeyOpFieldsValuesTuple &t); - task_process_status handleOneBufferPgEntry(const std::string &key, const std::string &port, const std::string &op, const KeyOpFieldsValuesTuple &tuple); - task_process_status handleBufferPgTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferQueueTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &t); - task_process_status doBufferTableTask(KeyOpFieldsValuesTuple &t, ProducerStateTable &applTable); + task_process_status handleBufferMaxParam(KeyOpFieldsValuesTuple &tuple); + task_process_status handleDefaultLossLessBufferParam(KeyOpFieldsValuesTuple &tuple); + task_process_status handleCableLenTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handlePortStateTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handlePortTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferPoolTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferProfileTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPgEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferQueueEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPortProfileListEntry(const std::string &key, buffer_direction_t dir, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPortIngressProfileListEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPortEgressProfileListEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferObjectTables(KeyOpFieldsValuesTuple &tuple, const std::string &table, bool keyWithIds); + task_process_status handleBufferPgTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferQueueTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &tuple); void doTask(Consumer &consumer); void doTask(SelectableTimer &timer); }; diff --git a/cfgmgr/coppmgrd.cpp b/cfgmgr/coppmgrd.cpp index 1995405cd6..60b0a2442a 100644 --- a/cfgmgr/coppmgrd.cpp +++ b/cfgmgr/coppmgrd.cpp @@ -29,6 +29,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index d0eaca21d9..8489d09bb3 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -9,13 +9,16 @@ #include "shellcmd.h" #include "macaddress.h" #include "warm_restart.h" +#include "subscriberstatetable.h" #include +#include "subintf.h" using namespace std; using namespace swss; #define VLAN_PREFIX "Vlan" #define LAG_PREFIX "PortChannel" +#define SUBINTF_LAG_PREFIX "Po" #define LOOPBACK_PREFIX "Loopback" #define VNET_PREFIX "Vnet" #define MTU_INHERITANCE "0" @@ -23,6 +26,7 @@ using namespace swss; #define VRF_MGMT "mgmt" #define LOOPBACK_DEFAULT_MTU_STR "65536" +#define DEFAULT_MTU_STR 9100 IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), @@ -35,8 +39,20 @@ IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, c m_stateVlanTable(stateDb, STATE_VLAN_TABLE_NAME), m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME), m_stateIntfTable(stateDb, STATE_INTERFACE_TABLE_NAME), - m_appIntfTableProducer(appDb, APP_INTF_TABLE_NAME) + m_appIntfTableProducer(appDb, APP_INTF_TABLE_NAME), + m_neighTable(appDb, APP_NEIGH_TABLE_NAME), + m_appLagTable(appDb, APP_LAG_TABLE_NAME) { + auto subscriberStateTable = new swss::SubscriberStateTable(stateDb, + STATE_PORT_TABLE_NAME, TableConsumable::DEFAULT_POP_BATCH_SIZE, 100); + auto stateConsumer = new Consumer(subscriberStateTable, this, STATE_PORT_TABLE_NAME); + Orch::addExecutor(stateConsumer); + + auto subscriberStateLagTable = new swss::SubscriberStateTable(stateDb, + STATE_LAG_TABLE_NAME, TableConsumable::DEFAULT_POP_BATCH_SIZE, 200); + auto stateLagConsumer = new Consumer(subscriberStateLagTable, this, STATE_LAG_TABLE_NAME); + Orch::addExecutor(stateLagConsumer); + if (!WarmStart::isWarmStart()) { flushLoopbackIntfs(); @@ -288,22 +304,160 @@ void IntfMgr::addHostSubIntf(const string&intf, const string &subIntf, const str EXEC_WITH_ERROR_THROW(cmd.str(), res); } -void IntfMgr::setHostSubIntfMtu(const string &subIntf, const string &mtu) + +std::string IntfMgr::getIntfAdminStatus(const string &alias) +{ + Table *portTable; + string admin = "down"; + if (!alias.compare(0, strlen("Eth"), "Eth")) + { + portTable = &m_statePortTable; + } + else if (!alias.compare(0, strlen("Po"), "Po")) + { + portTable = &m_appLagTable; + } + else + { + return admin; + } + + vector temp; + portTable->get(alias, temp); + + for (auto idx : temp) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "admin_status") + { + admin = value; + } + } + return admin; +} + +std::string IntfMgr::getIntfMtu(const string &alias) +{ + Table *portTable; + string mtu = "0"; + if (!alias.compare(0, strlen("Eth"), "Eth")) + { + portTable = &m_statePortTable; + } + else if (!alias.compare(0, strlen("Po"), "Po")) + { + portTable = &m_appLagTable; + } + else + { + return mtu; + } + vector temp; + portTable->get(alias, temp); + for (auto idx : temp) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "mtu") + { + mtu = value; + } + } + if (mtu.empty()) + { + mtu = std::to_string(DEFAULT_MTU_STR); + } + return mtu; +} + +void IntfMgr::updateSubIntfMtu(const string &alias, const string &mtu) +{ + string intf; + for (auto entry : m_subIntfList) + { + intf = entry.first; + subIntf subIf(intf); + if (subIf.parentIntf() == alias) + { + std::vector fvVector; + + string subif_config_mtu = m_subIntfList[intf].mtu; + if (subif_config_mtu == MTU_INHERITANCE || subif_config_mtu.empty()) + subif_config_mtu = std::to_string(DEFAULT_MTU_STR); + + string subintf_mtu = setHostSubIntfMtu(intf, subif_config_mtu, mtu); + + FieldValueTuple fvTuple("mtu", subintf_mtu); + fvVector.push_back(fvTuple); + m_appIntfTableProducer.set(intf, fvVector); + } + } +} + +std::string IntfMgr::setHostSubIntfMtu(const string &alias, const string &mtu, const string &parent_mtu) { stringstream cmd; string res; - cmd << IP_CMD " link set " << shellquote(subIntf) << " mtu " << shellquote(mtu); + string subifMtu = mtu; + subIntf subIf(alias); + + int pmtu = (uint32_t)stoul(parent_mtu); + int cmtu = (uint32_t)stoul(mtu); + + if (pmtu < cmtu) + { + subifMtu = parent_mtu; + } + SWSS_LOG_INFO("subintf %s active mtu: %s", alias.c_str(), subifMtu.c_str()); + cmd << IP_CMD " link set " << shellquote(alias) << " mtu " << shellquote(subifMtu); EXEC_WITH_ERROR_THROW(cmd.str(), res); + + return subifMtu; } -void IntfMgr::setHostSubIntfAdminStatus(const string &subIntf, const string &adminStatus) +void IntfMgr::updateSubIntfAdminStatus(const string &alias, const string &admin) +{ + string intf; + for (auto entry : m_subIntfList) + { + intf = entry.first; + subIntf subIf(intf); + if (subIf.parentIntf() == alias) + { + /* Avoid duplicate interface admin UP event. */ + string curr_admin = m_subIntfList[intf].currAdminStatus; + if (curr_admin == "up" && curr_admin == admin) + { + continue; + } + std::vector fvVector; + string subintf_admin = setHostSubIntfAdminStatus(intf, m_subIntfList[intf].adminStatus, admin); + m_subIntfList[intf].currAdminStatus = subintf_admin; + FieldValueTuple fvTuple("admin_status", subintf_admin); + fvVector.push_back(fvTuple); + m_appIntfTableProducer.set(intf, fvVector); + } + } +} + +std::string IntfMgr::setHostSubIntfAdminStatus(const string &alias, const string &admin_status, const string &parent_admin_status) { stringstream cmd; string res; - cmd << IP_CMD " link set " << shellquote(subIntf) << " " << shellquote(adminStatus); - EXEC_WITH_ERROR_THROW(cmd.str(), res); + if (parent_admin_status == "up" || admin_status == "down") + { + SWSS_LOG_INFO("subintf %s admin_status: %s", alias.c_str(), admin_status.c_str()); + cmd << IP_CMD " link set " << shellquote(alias) << " " << shellquote(admin_status); + EXEC_WITH_ERROR_THROW(cmd.str(), res); + return admin_status; + } + else + { + return "down"; + } } void IntfMgr::removeHostSubIntf(const string &subIntf) @@ -319,7 +473,7 @@ void IntfMgr::setSubIntfStateOk(const string &alias) { vector fvTuples = {{"state", "ok"}}; - if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + if (!alias.compare(0, strlen(SUBINTF_LAG_PREFIX), SUBINTF_LAG_PREFIX)) { m_stateLagTable.set(alias, fvTuples); } @@ -332,7 +486,7 @@ void IntfMgr::setSubIntfStateOk(const string &alias) void IntfMgr::removeSubIntfState(const string &alias) { - if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + if (!alias.compare(0, strlen(SUBINTF_LAG_PREFIX), SUBINTF_LAG_PREFIX)) { m_stateLagTable.del(alias); } @@ -451,10 +605,47 @@ bool IntfMgr::isIntfStateOk(const string &alias) { return true; } + else if (!alias.compare(0, strlen(SUBINTF_LAG_PREFIX), SUBINTF_LAG_PREFIX)) + { + if (m_stateLagTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Lag %s is ready", alias.c_str()); + return true; + } + } return false; } +void IntfMgr::delIpv6LinkLocalNeigh(const string &alias) +{ + vector neighEntries; + + SWSS_LOG_INFO("Deleting ipv6 link local neighbors for %s", alias.c_str()); + + m_neighTable.getKeys(neighEntries); + for (auto neighKey : neighEntries) + { + if (!neighKey.compare(0, alias.size(), alias.c_str())) + { + vector keys = tokenize(neighKey, ':', 1); + if (keys.size() == 2) + { + IpAddress ipAddress(keys[1]); + if (ipAddress.getAddrScope() == IpAddress::AddrScope::LINK_SCOPE) + { + stringstream cmd; + string res; + + cmd << IP_CMD << " neigh del dev " << keys[0] << " " << keys[1] ; + swss::exec(cmd.str(), res); + SWSS_LOG_INFO("Deleted ipv6 link local neighbor - %s", keys[1].c_str()); + } + } + } + } +} + bool IntfMgr::doIntfGeneralTask(const vector& keys, vector data, const string& op) @@ -467,11 +658,24 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, size_t found = alias.find(VLAN_SUB_INTERFACE_SEPARATOR); if (found != string::npos) { - // This is a sub interface + subIntf subIf(alias); // alias holds the complete sub interface name // while parentAlias holds the parent port name - vlanId = alias.substr(found + 1); - parentAlias = alias.substr(0, found); + /*Check if subinterface is valid and sub interface name length is < 15(IFNAMSIZ)*/ + if (!subIf.isValid()) + { + SWSS_LOG_ERROR("Invalid subnitf: %s", alias.c_str()); + return true; + } + parentAlias = subIf.parentIntf(); + int subIntfId = subIf.subIntfIdx(); + /*If long name format, subinterface Id is vlanid */ + if (!subIf.isShortName()) + { + vlanId = std::to_string(subIntfId); + FieldValueTuple vlanTuple("vlan", vlanId); + data.push_back(vlanTuple); + } } bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); string mac = ""; @@ -521,6 +725,10 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, { ipv6_link_local_mode = value; } + else if (field == "vlan") + { + vlanId = value; + } } if (op == SET_COMMAND) @@ -577,6 +785,17 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, /* Set ipv6 mode */ if (!ipv6_link_local_mode.empty()) { + if ((ipv6_link_local_mode == "enable") && (m_ipv6LinkLocalModeList.find(alias) == m_ipv6LinkLocalModeList.end())) + { + m_ipv6LinkLocalModeList.insert(alias); + SWSS_LOG_INFO("Inserted ipv6 link local mode list for %s", alias.c_str()); + } + else if ((ipv6_link_local_mode == "disable") && (m_ipv6LinkLocalModeList.find(alias) != m_ipv6LinkLocalModeList.end())) + { + m_ipv6LinkLocalModeList.erase(alias); + delIpv6LinkLocalNeigh(alias); + SWSS_LOG_INFO("Erased ipv6 link local mode list for %s", alias.c_str()); + } FieldValueTuple fvTuple("ipv6_use_link_local_only", ipv6_link_local_mode); data.push_back(fvTuple); } @@ -584,8 +803,14 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, if (!parentAlias.empty()) { + subIntf subIf(alias); if (m_subIntfList.find(alias) == m_subIntfList.end()) { + if (vlanId == "0" || vlanId.empty()) + { + SWSS_LOG_INFO("Vlan ID not configured for sub interface %s", alias.c_str()); + return false; + } try { addHostSubIntf(parentAlias, alias, vlanId); @@ -596,25 +821,33 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, return false; } - m_subIntfList.insert(alias); + m_subIntfList[alias].vlanId = vlanId; } if (!mtu.empty()) { + string subintf_mtu; try { - setHostSubIntfMtu(alias, mtu); + string parentMtu = getIntfMtu(subIf.parentIntf()); + subintf_mtu = setHostSubIntfMtu(alias, mtu, parentMtu); + FieldValueTuple fvTuple("mtu", mtu); + std::remove(data.begin(), data.end(), fvTuple); + FieldValueTuple newMtuFvTuple("mtu", subintf_mtu); + data.push_back(newMtuFvTuple); } catch (const std::runtime_error &e) { SWSS_LOG_NOTICE("Sub interface ip link set mtu failure. Runtime error: %s", e.what()); return false; } + m_subIntfList[alias].mtu = mtu; } else { FieldValueTuple fvTuple("mtu", MTU_INHERITANCE); data.push_back(fvTuple); + m_subIntfList[alias].mtu = MTU_INHERITANCE; } if (adminStatus.empty()) @@ -625,13 +858,20 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, } try { - setHostSubIntfAdminStatus(alias, adminStatus); + string parentAdmin = getIntfAdminStatus(subIf.parentIntf()); + string subintf_admin = setHostSubIntfAdminStatus(alias, adminStatus, parentAdmin); + m_subIntfList[alias].currAdminStatus = subintf_admin; + FieldValueTuple fvTuple("admin_status", adminStatus); + std::remove(data.begin(), data.end(), fvTuple); + FieldValueTuple newAdminFvTuple("admin_status", subintf_admin); + data.push_back(newAdminFvTuple); } catch (const std::runtime_error &e) { SWSS_LOG_NOTICE("Sub interface ip link set admin status %s failure. Runtime error: %s", adminStatus.c_str(), e.what()); return false; } + m_subIntfList[alias].adminStatus = adminStatus; // set STATE_DB port state setSubIntfStateOk(alias); @@ -711,6 +951,13 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, removeSubIntfState(alias); } + if (m_ipv6LinkLocalModeList.find(alias) != m_ipv6LinkLocalModeList.end()) + { + m_ipv6LinkLocalModeList.erase(alias); + delIpv6LinkLocalNeigh(alias); + SWSS_LOG_INFO("Erased ipv6 link local mode list for %s", alias.c_str()); + } + m_appIntfTableProducer.del(alias); m_stateIntfTable.del(alias); } @@ -788,51 +1035,57 @@ void IntfMgr::doTask(Consumer &consumer) while (it != consumer.m_toSync.end()) { KeyOpFieldsValuesTuple t = it->second; - - vector keys = tokenize(kfvKey(t), config_db_key_delimiter); - const vector& data = kfvFieldsValues(t); - string op = kfvOp(t); - - if (keys.size() == 1) + if ((table_name == STATE_PORT_TABLE_NAME) || (table_name == STATE_LAG_TABLE_NAME)) { - if((table_name == CFG_VOQ_INBAND_INTERFACE_TABLE_NAME) && - (op == SET_COMMAND)) - { - //No further processing needed. Just relay to orchagent - m_appIntfTableProducer.set(keys[0], data); - m_stateIntfTable.hset(keys[0], "vrf", ""); + doPortTableTask(kfvKey(t), kfvFieldsValues(t), kfvOp(t)); + } + else + { + vector keys = tokenize(kfvKey(t), config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string op = kfvOp(t); - it = consumer.m_toSync.erase(it); - continue; - } - if (!doIntfGeneralTask(keys, data, op)) - { - it++; - continue; - } - else + if (keys.size() == 1) { - //Entry programmed, remove it from pending list if present - m_pendingReplayIntfList.erase(keys[0]); + if((table_name == CFG_VOQ_INBAND_INTERFACE_TABLE_NAME) && + (op == SET_COMMAND)) + { + //No further processing needed. Just relay to orchagent + m_appIntfTableProducer.set(keys[0], data); + m_stateIntfTable.hset(keys[0], "vrf", ""); + + it = consumer.m_toSync.erase(it); + continue; + } + if (!doIntfGeneralTask(keys, data, op)) + { + it++; + continue; + } + else + { + //Entry programmed, remove it from pending list if present + m_pendingReplayIntfList.erase(keys[0]); + } } - } - else if (keys.size() == 2) - { - if (!doIntfAddrTask(keys, data, op)) + else if (keys.size() == 2) { - it++; - continue; + if (!doIntfAddrTask(keys, data, op)) + { + it++; + continue; + } + else + { + //Entry programmed, remove it from pending list if present + m_pendingReplayIntfList.erase(keys[0] + config_db_key_delimiter + keys[1] ); + } } else { - //Entry programmed, remove it from pending list if present - m_pendingReplayIntfList.erase(keys[0] + config_db_key_delimiter + keys[1] ); + SWSS_LOG_ERROR("Invalid key %s", kfvKey(t).c_str()); } } - else - { - SWSS_LOG_ERROR("Invalid key %s", kfvKey(t).c_str()); - } it = consumer.m_toSync.erase(it); } @@ -842,3 +1095,26 @@ void IntfMgr::doTask(Consumer &consumer) setWarmReplayDoneState(); } } + +void IntfMgr::doPortTableTask(const string& key, vector data, string op) +{ + if (op == SET_COMMAND) + { + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + + if (field == "admin_status") + { + SWSS_LOG_INFO("Port %s Admin %s", key.c_str(), value.c_str()); + updateSubIntfAdminStatus(key, value); + } + else if (field == "mtu") + { + SWSS_LOG_INFO("Port %s MTU %s", key.c_str(), value.c_str()); + updateSubIntfMtu(key, value); + } + } + } +} diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index 655fb4deeb..84c0020eb0 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -9,6 +9,16 @@ #include #include +struct SubIntfInfo +{ + std::string vlanId; + std::string mtu; + std::string adminStatus; + std::string currAdminStatus; +}; + +typedef std::map SubIntfMap; + namespace swss { class IntfMgr : public Orch @@ -20,11 +30,13 @@ class IntfMgr : public Orch private: ProducerStateTable m_appIntfTableProducer; Table m_cfgIntfTable, m_cfgVlanIntfTable, m_cfgLagIntfTable, m_cfgLoopbackIntfTable; - Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable; + Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable, m_appLagTable; + Table m_neighTable; - std::set m_subIntfList; + SubIntfMap m_subIntfList; std::set m_loopbackIntfList; std::set m_pendingReplayIntfList; + std::set m_ipv6LinkLocalModeList; void setIntfIp(const std::string &alias, const std::string &opCmd, const IpPrefix &ipPrefix); void setIntfVrf(const std::string &alias, const std::string &vrfName); @@ -34,6 +46,7 @@ class IntfMgr : public Orch bool doIntfGeneralTask(const std::vector& keys, std::vector data, const std::string& op); bool doIntfAddrTask(const std::vector& keys, const std::vector& data, const std::string& op); void doTask(Consumer &consumer); + void doPortTableTask(const std::string& key, std::vector data, std::string op); bool isIntfStateOk(const std::string &alias); bool isIntfCreated(const std::string &alias); @@ -46,16 +59,22 @@ class IntfMgr : public Orch void delLoopbackIntf(const std::string &alias); void flushLoopbackIntfs(void); + std::string getIntfAdminStatus(const std::string &alias); + std::string getIntfMtu(const std::string &alias); void addHostSubIntf(const std::string&intf, const std::string &subIntf, const std::string &vlan); - void setHostSubIntfMtu(const std::string &subIntf, const std::string &mtu); - void setHostSubIntfAdminStatus(const std::string &subIntf, const std::string &admin_status); + std::string setHostSubIntfMtu(const std::string &alias, const std::string &mtu, const std::string &parent_mtu); + std::string setHostSubIntfAdminStatus(const std::string &alias, const std::string &admin_status, const std::string &parent_admin_status); void removeHostSubIntf(const std::string &subIntf); void setSubIntfStateOk(const std::string &alias); void removeSubIntfState(const std::string &alias); + void delIpv6LinkLocalNeigh(const std::string &alias); bool setIntfProxyArp(const std::string &alias, const std::string &proxy_arp); bool setIntfGratArp(const std::string &alias, const std::string &grat_arp); + void updateSubIntfAdminStatus(const std::string &alias, const std::string &admin); + void updateSubIntfMtu(const std::string &alias, const std::string &mtu); + bool m_replayDone {false}; }; diff --git a/cfgmgr/intfmgrd.cpp b/cfgmgr/intfmgrd.cpp index d6ed18526e..9ed3653333 100644 --- a/cfgmgr/intfmgrd.cpp +++ b/cfgmgr/intfmgrd.cpp @@ -29,6 +29,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/macsecmgrd.cpp b/cfgmgr/macsecmgrd.cpp index f77e3d8c07..913c0ac4ee 100644 --- a/cfgmgr/macsecmgrd.cpp +++ b/cfgmgr/macsecmgrd.cpp @@ -38,6 +38,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/natmgrd.cpp b/cfgmgr/natmgrd.cpp index 7e2aeba4a2..c2baf7eb87 100644 --- a/cfgmgr/natmgrd.cpp +++ b/cfgmgr/natmgrd.cpp @@ -52,6 +52,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; mutex gDbMutex; NatMgr *natmgr = NULL; @@ -200,4 +204,3 @@ int main(int argc, char **argv) } return -1; } - diff --git a/cfgmgr/nbrmgrd.cpp b/cfgmgr/nbrmgrd.cpp index d9b6829036..338d8d9d0d 100644 --- a/cfgmgr/nbrmgrd.cpp +++ b/cfgmgr/nbrmgrd.cpp @@ -33,6 +33,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/portmgrd.cpp b/cfgmgr/portmgrd.cpp index b0f0c887dd..180bbc1d63 100644 --- a/cfgmgr/portmgrd.cpp +++ b/cfgmgr/portmgrd.cpp @@ -28,6 +28,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/sflowmgrd.cpp b/cfgmgr/sflowmgrd.cpp index 0436ad5f00..7de5f15a2d 100644 --- a/cfgmgr/sflowmgrd.cpp +++ b/cfgmgr/sflowmgrd.cpp @@ -28,6 +28,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/teammgrd.cpp b/cfgmgr/teammgrd.cpp index e38456eebe..66bfa4b6d2 100644 --- a/cfgmgr/teammgrd.cpp +++ b/cfgmgr/teammgrd.cpp @@ -17,6 +17,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; bool received_sigterm = false; diff --git a/cfgmgr/tunnelmgrd.cpp b/cfgmgr/tunnelmgrd.cpp index d419b2b886..0165eb94b5 100644 --- a/cfgmgr/tunnelmgrd.cpp +++ b/cfgmgr/tunnelmgrd.cpp @@ -31,6 +31,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/vlanmgrd.cpp b/cfgmgr/vlanmgrd.cpp index 88e4745758..b69dc78122 100644 --- a/cfgmgr/vlanmgrd.cpp +++ b/cfgmgr/vlanmgrd.cpp @@ -36,6 +36,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/vrfmgrd.cpp b/cfgmgr/vrfmgrd.cpp index 556b937901..735e59191d 100644 --- a/cfgmgr/vrfmgrd.cpp +++ b/cfgmgr/vrfmgrd.cpp @@ -29,6 +29,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/vxlanmgrd.cpp b/cfgmgr/vxlanmgrd.cpp index 809c580f82..d47893a614 100644 --- a/cfgmgr/vxlanmgrd.cpp +++ b/cfgmgr/vxlanmgrd.cpp @@ -34,6 +34,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; MacAddress gMacAddress; diff --git a/doc/Configuration.md b/doc/Configuration.md index 039e749d37..40865366f6 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -286,6 +286,46 @@ and migration plan ``` +***ACL table type configuration example*** +``` +{ + "ACL_TABLE_TYPE": { + "CUSTOM_L3": { + "MATCHES": [ + "IN_PORTS", + "OUT_PORTS", + "SRC_IP" + ], + "ACTIONS": [ + "PACKET_ACTION", + "MIRROR_INGRESS_ACTION" + ], + "BIND_POINTS": [ + "PORT", + "LAG" + ] + } + }, + "ACL_TABLE": { + "DATAACL": { + "STAGE": "INGRESS", + "TYPE": "CUSTOM_L3", + "PORTS": [ + "Ethernet0", + "PortChannel1" + ] + } + }, + "ACL_RULE": { + "DATAACL|RULE0": { + "PRIORITY": "999", + "PACKET_ACTION": "DROP", + "SRC_IP": "1.1.1.1/32", + } + } +} +``` + ### BGP Sessions BGP session configuration is defined in **BGP_NEIGHBOR** table. BGP diff --git a/doc/swss-schema.md b/doc/swss-schema.md index 1fe9ef9b2c..ec28eb6c0f 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -569,15 +569,37 @@ It's possible to create separate configuration files for different ASIC platform ---------------------------------------------- +### ACL\_TABLE\_TYPE +Stores a definition of table - set of matches, actions and bind point types. ACL_TABLE references a key inside this table in "type" field. + +``` +key: ACL_TABLE_TYPE:name ; key of the ACL table type entry. The name is arbitary name user chooses. +; field = value +matches = match-list ; list of matches for this table, matches are same as in ACL_RULE table. +actions = action-list ; list of actions for this table, actions are same as in ACL_RULE table. +bind_points = bind-points-list ; list of bind point types for this table. + +; values annotation +match = 1*64VCHAR +match-list = [1-max-matches]*match +action = 1*64VCHAR +action-list = [1-max-actions]*action +bind-point = port/lag +bind-points-list = [1-max-bind-points]*bind-point +``` + ### ACL\_TABLE Stores information about ACL tables on the switch. Port names are defined in [port_config.ini](../portsyncd/port_config.ini). key = ACL_TABLE:name ; acl_table_name must be unique ;field = value policy_desc = 1*255VCHAR ; name of the ACL policy table description - type = "mirror"/"l3"/"l3v6" ; type of acl table, every type of + type = 1*255VCHAR ; type of acl table, every type of ; table defines the match/action a ; specific set of match and actions. + ; There are pre-defined table types like + ; "MIRROR", "MIRRORV6", "MIRROR_DSCP", + ; "L3", "L3V6", "MCLAG", "PFCWD", "DROP". ports = [0-max_ports]*port_name ; the ports to which this ACL ; table is applied, can be emtry ; value annotations diff --git a/mclagsyncd/mclaglink.cpp b/mclagsyncd/mclaglink.cpp index 7e7a9aff27..68b700fdb9 100644 --- a/mclagsyncd/mclaglink.cpp +++ b/mclagsyncd/mclaglink.cpp @@ -334,7 +334,6 @@ void MclagLink::setPortIsolate(char *msg) acl_rule_attrs.push_back(ip_type_attr); string temp; - isolate_dst_port.insert(0, (const char*)cur, op_hdr->op_len); istringstream dst_ss(isolate_dst_port); isolate_dst_port.clear(); diff --git a/neighsyncd/neighsync.cpp b/neighsyncd/neighsync.cpp index 6b1abc235f..cb04371d41 100644 --- a/neighsyncd/neighsync.cpp +++ b/neighsyncd/neighsync.cpp @@ -82,7 +82,7 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) /* Ignore IPv6 link-local addresses as neighbors, if ipv6 link local mode is disabled */ if (family == IPV6_NAME && IN6_IS_ADDR_LINKLOCAL(nl_addr_get_binary_addr(rtnl_neigh_get_dst(neigh)))) { - if (isLinkLocalEnabled(intfName) == false) + if ((isLinkLocalEnabled(intfName) == false) && (nlmsg_type != RTM_DELNEIGH)) { return; } diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 364fff4853..7225917e4d 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -25,7 +25,8 @@ dist_swss_DATA = \ watermark_pg.lua \ watermark_bufferpool.lua \ lagids.lua \ - tunnel_rates.lua + tunnel_rates.lua \ + trap_rates.lua bin_PROGRAMS = orchagent routeresync orchagent_restart_check @@ -38,6 +39,7 @@ endif orchagent_SOURCES = \ main.cpp \ $(top_srcdir)/lib/gearboxutils.cpp \ + $(top_srcdir)/lib/subintf.cpp \ orchdaemon.cpp \ orch.cpp \ notifications.cpp \ @@ -64,6 +66,7 @@ orchagent_SOURCES = \ pbh/pbhrule.cpp \ pbhorch.cpp \ saihelper.cpp \ + saiattr.cpp \ switchorch.cpp \ pfcwdorch.cpp \ pfcactionhandler.cpp \ @@ -87,9 +90,10 @@ orchagent_SOURCES = \ macsecorch.cpp \ lagid.cpp \ bfdorch.cpp \ - srv6orch.cpp + srv6orch.cpp \ + response_publisher.cpp -orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp +orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp flex_counter/flow_counter_handler.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index d9dc26e99e..900299b3d5 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -32,7 +32,10 @@ extern CrmOrch *gCrmOrch; #define MIN_VLAN_ID 1 // 0 is a reserved VLAN ID #define MAX_VLAN_ID 4095 // 4096 is a reserved VLAN ID +#define STATE_DB_ACL_ACTION_FIELD_IS_ACTION_LIST_MANDATORY "is_action_list_mandatory" +#define STATE_DB_ACL_ACTION_FIELD_ACTION_LIST "action_list" #define COUNTERS_ACL_COUNTER_RULE_MAP "ACL_COUNTER_RULE_MAP" + #define ACL_COUNTER_DEFAULT_POLLING_INTERVAL_MS 10000 // ms #define ACL_COUNTER_DEFAULT_ENABLED_STATE false @@ -60,8 +63,8 @@ acl_rule_attr_lookup_t aclMatchLookup = { MATCH_ICMP_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMP_CODE }, { MATCH_ICMPV6_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_TYPE }, { MATCH_ICMPV6_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_CODE }, - { MATCH_L4_SRC_PORT_RANGE, (sai_acl_entry_attr_t)SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE }, - { MATCH_L4_DST_PORT_RANGE, (sai_acl_entry_attr_t)SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE }, + { MATCH_L4_SRC_PORT_RANGE, SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE }, + { MATCH_L4_DST_PORT_RANGE, SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE }, { MATCH_TUNNEL_VNI, SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI }, { MATCH_INNER_ETHER_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE }, { MATCH_INNER_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL }, @@ -69,6 +72,18 @@ acl_rule_attr_lookup_t aclMatchLookup = { MATCH_INNER_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT } }; +static acl_range_type_lookup_t aclRangeTypeLookup = +{ + { MATCH_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE }, + { MATCH_L4_DST_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE }, +}; + +static acl_bind_point_type_lookup_t aclBindPointTypeLookup = +{ + { BIND_POINT_TYPE_PORT, SAI_ACL_BIND_POINT_TYPE_PORT }, + { BIND_POINT_TYPE_PORTCHANNEL, SAI_ACL_BIND_POINT_TYPE_LAG }, +}; + static acl_rule_attr_lookup_t aclL3ActionLookup = { { ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION }, @@ -106,20 +121,6 @@ static acl_dtel_flow_op_type_lookup_t aclDTelFlowOpTypeLookup = { DTEL_FLOW_OP_IOAM, SAI_ACL_DTEL_FLOW_OP_IOAM } }; -static acl_table_type_lookup_t aclTableTypeLookUp = -{ - { TABLE_TYPE_L3, ACL_TABLE_L3 }, - { TABLE_TYPE_L3V6, ACL_TABLE_L3V6 }, - { TABLE_TYPE_MIRROR, ACL_TABLE_MIRROR }, - { TABLE_TYPE_MIRRORV6, ACL_TABLE_MIRRORV6 }, - { TABLE_TYPE_MIRROR_DSCP, ACL_TABLE_MIRROR_DSCP }, - { TABLE_TYPE_CTRLPLANE, ACL_TABLE_CTRLPLANE }, - { TABLE_TYPE_DTEL_FLOW_WATCHLIST, ACL_TABLE_DTEL_FLOW_WATCHLIST }, - { TABLE_TYPE_DTEL_DROP_WATCHLIST, ACL_TABLE_DTEL_DROP_WATCHLIST }, - { TABLE_TYPE_MCLAG, ACL_TABLE_MCLAG }, - { TABLE_TYPE_DROP, ACL_TABLE_DROP } -}; - static acl_stage_type_lookup_t aclStageLookUp = { {STAGE_INGRESS, ACL_STAGE_INGRESS }, @@ -130,16 +131,24 @@ static const acl_capabilities_t defaultAclActionsSupported = { { ACL_STAGE_INGRESS, + AclActionCapabilities { - SAI_ACL_ACTION_TYPE_PACKET_ACTION, - SAI_ACL_ACTION_TYPE_MIRROR_INGRESS, - SAI_ACL_ACTION_TYPE_NO_NAT + { + SAI_ACL_ACTION_TYPE_PACKET_ACTION, + SAI_ACL_ACTION_TYPE_MIRROR_INGRESS, + SAI_ACL_ACTION_TYPE_NO_NAT + }, + false } }, { ACL_STAGE_EGRESS, + AclActionCapabilities { - SAI_ACL_ACTION_TYPE_PACKET_ACTION + { + SAI_ACL_ACTION_TYPE_PACKET_ACTION + }, + false } } }; @@ -164,18 +173,312 @@ static map aclCounterLookup = {SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT, SAI_ACL_COUNTER_ATTR_PACKETS}, }; -AclRule::AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : +static sai_acl_table_attr_t AclEntryFieldToAclTableField(sai_acl_entry_attr_t attr) +{ + if (!IS_ATTR_ID_IN_RANGE(attr, ACL_ENTRY, FIELD)) + { + SWSS_LOG_THROW("ACL entry attribute is not a in a range of SAI_ACL_ENTRY_ATTR_FIELD_* attribute: %d", attr); + } + return static_cast(SAI_ACL_TABLE_ATTR_FIELD_START + (attr - SAI_ACL_ENTRY_ATTR_FIELD_START)); +} + +static sai_acl_action_type_t AclEntryActionToAclAction(sai_acl_entry_attr_t attr) +{ + if (!IS_ATTR_ID_IN_RANGE(attr, ACL_ENTRY, ACTION)) + { + SWSS_LOG_THROW("ACL entry attribute is not a in a range of SAI_ACL_ENTRY_ATTR_ACTION_* attribute: %d", attr); + } + return static_cast(attr - SAI_ACL_ENTRY_ATTR_ACTION_START); +} + +static string getAttributeIdName(sai_object_type_t objectType, sai_attr_id_t attr) +{ + const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + if (!meta) + { + SWSS_LOG_THROW("Metadata null pointer returned by sai_metadata_get_attr_metadata"); + } + return meta->attridname; +} + +AclTableMatchInterface::AclTableMatchInterface(sai_acl_table_attr_t matchField): + m_matchField(matchField) +{ + if (!IS_ATTR_ID_IN_RANGE(m_matchField, ACL_TABLE, FIELD)) + { + SWSS_LOG_THROW("Invalid match table attribute %d", m_matchField); + } +} + +sai_acl_table_attr_t AclTableMatchInterface::getId() const +{ + return m_matchField; +} + +AclTableMatch::AclTableMatch(sai_acl_table_attr_t matchField): + AclTableMatchInterface(matchField) +{ + const auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_TABLE, getId()); + if (!meta) + { + SWSS_LOG_THROW("Failed to get metadata for attribute for SAI_OBJECT_TYPE_ACL_TABLE: attribute %d", getId()); + } + + if (meta->attrvaluetype != SAI_ATTR_VALUE_TYPE_BOOL) + { + SWSS_LOG_THROW("This API does not allow to set match with a non boolean value type"); + } +} + +sai_attribute_t AclTableMatch::toSaiAttribute() +{ + return sai_attribute_t{ + .id = getId(), + .value = { + .booldata = true, + }, + }; +} + +bool AclTableMatch::validateAclRuleMatch(const AclRule& rule) const +{ + // no need to validate rule configuration + return true; +} + +AclTableRangeMatch::AclTableRangeMatch(set rangeTypes): + AclTableMatchInterface(SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE), + m_rangeList(rangeTypes.begin(), rangeTypes.end()) +{ +} + +sai_attribute_t AclTableRangeMatch::toSaiAttribute() +{ + return sai_attribute_t{ + .id = getId(), + .value = { + .s32list = { + .count = static_cast(m_rangeList.size()), + .list = m_rangeList.data(), + }, + }, + }; +} + +bool AclTableRangeMatch::validateAclRuleMatch(const AclRule& rule) const +{ + const auto& rangeConfig = rule.getRangeConfig(); + for (const auto& range: rangeConfig) + { + if (find(m_rangeList.begin(), m_rangeList.end(), range.rangeType) == m_rangeList.end()) + { + SWSS_LOG_ERROR("Range match %s is not supported on table %s", + sai_metadata_get_acl_range_type_name(range.rangeType), rule.getTableId().c_str()); + return false; + } + } + + return true; +} + +string AclTableType::getName() const +{ + return m_name; +} + +const set& AclTableType::getBindPointTypes() const +{ + return m_bpointTypes; +} + +const map>& AclTableType::getMatches() const +{ + return m_matches; +} + +const set& AclTableType::getActions() const +{ + return m_aclAcitons; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withName(string name) +{ + m_tableType.m_name = name; + return *this; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withBindPointType(sai_acl_bind_point_type_t bpointType) +{ + m_tableType.m_bpointTypes.insert(bpointType); + return *this; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withMatch(shared_ptr match) +{ + m_tableType.m_matches.emplace(match->getId(), match); + return *this; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withAction(sai_acl_action_type_t action) +{ + m_tableType.m_aclAcitons.insert(action); + return *this; +} + +AclTableType AclTableTypeBuilder::build() +{ + auto tableType = m_tableType; + m_tableType = AclTableType(); + return tableType; +} + +bool AclTableTypeParser::parse(const std::string& key, + const vector& fieldValues, + AclTableTypeBuilder& builder) +{ + builder.withName(key); + + for (const auto& fieldValue: fieldValues) + { + auto field = to_upper(fvField(fieldValue)); + auto value = to_upper(fvValue(fieldValue)); + + SWSS_LOG_DEBUG("field %s, value %s", field.c_str(), value.c_str()); + + if (field == ACL_TABLE_TYPE_MATCHES) + { + if (!parseAclTableTypeMatches(value, builder)) + { + return false; + } + } + else if (field == ACL_TABLE_TYPE_ACTIONS) + { + if (!parseAclTableTypeActions(value, builder)) + { + return false; + } + } + else if (field == ACL_TABLE_TYPE_BPOINT_TYPES) + { + if (!parseAclTableTypeBindPointTypes(value, builder)) + { + return false; + } + } + else + { + SWSS_LOG_ERROR("Unknown field %s: value %s", field.c_str(), value.c_str()); + return false; + } + } + + return true; +} + +bool AclTableTypeParser::parseAclTableTypeMatches(const std::string& value, AclTableTypeBuilder& builder) +{ + auto matches = tokenize(value, comma); + set saiRangeTypes; + + for (const auto& match: matches) + { + auto matchIt = aclMatchLookup.find(match); + auto rangeMatchIt = aclRangeTypeLookup.find(match); + + if (rangeMatchIt != aclRangeTypeLookup.end()) + { + auto rangeType = rangeMatchIt->second; + saiRangeTypes.insert(rangeType); + } + else if (matchIt != aclMatchLookup.end()) + { + auto saiMatch = AclEntryFieldToAclTableField(matchIt->second); + builder.withMatch(make_unique(saiMatch)); + } + else + { + SWSS_LOG_ERROR("Unknown match %s", match.c_str()); + return false; + } + } + + if (!saiRangeTypes.empty()) + { + builder.withMatch(make_unique(saiRangeTypes)); + } + + return true; +} + +bool AclTableTypeParser::parseAclTableTypeActions(const std::string& value, AclTableTypeBuilder& builder) +{ + auto actions = tokenize(value, comma); + for (const auto& action: actions) + { + sai_acl_entry_attr_t saiActionAttr = SAI_ACL_ENTRY_ATTR_ACTION_END; + + auto l3Action = aclL3ActionLookup.find(action); + auto mirrorAction = aclMirrorStageLookup.find(action); + auto dtelAction = aclDTelActionLookup.find(action); + + if (l3Action != aclL3ActionLookup.end()) + { + saiActionAttr = l3Action->second; + } + else if (mirrorAction != aclMirrorStageLookup.end()) + { + saiActionAttr = mirrorAction->second; + } + else if (dtelAction != aclDTelActionLookup.end()) + { + saiActionAttr = dtelAction->second; + } + else + { + SWSS_LOG_ERROR("Unknown action %s", action.c_str()); + return false; + } + + builder.withAction(AclEntryActionToAclAction(saiActionAttr)); + } + + return true; +} + +bool AclTableTypeParser::parseAclTableTypeBindPointTypes(const std::string& value, AclTableTypeBuilder& builder) +{ + auto bpointTypes = tokenize(value, comma); + for (const auto& bpointType: bpointTypes) + { + auto bpointIt = aclBindPointTypeLookup.find(bpointType); + if (bpointIt == aclBindPointTypeLookup.end()) + { + SWSS_LOG_ERROR("Unknown bind point %s", bpointType.c_str()); + return false; + } + + auto saiBpointType = bpointIt->second; + builder.withBindPointType(saiBpointType); + } + + return true; +} + +AclRule::AclRule(AclOrch *pAclOrch, string rule, string table, bool createCounter) : m_pAclOrch(pAclOrch), m_id(rule), - m_tableId(table), - m_tableType(type), - m_tableOid(SAI_NULL_OBJECT_ID), m_ruleOid(SAI_NULL_OBJECT_ID), m_counterOid(SAI_NULL_OBJECT_ID), m_priority(0), m_createCounter(createCounter) { - m_tableOid = pAclOrch->getTableById(m_tableId); + auto tableOid = pAclOrch->getTableById(table); + m_pTable = pAclOrch->getTableByOid(tableOid); + if (!m_pTable) + { + SWSS_LOG_THROW("Failed to find ACL table %s. ACL table must exist at the time of creating AclRule", table.c_str()); + } } bool AclRule::validateAddPriority(string attr_name, string attr_value) @@ -186,12 +489,11 @@ bool AclRule::validateAddPriority(string attr_name, string attr_value) { char *endp = NULL; errno = 0; - m_priority = (uint32_t)strtol(attr_value.c_str(), &endp, 0); + auto priority = (uint32_t)strtol(attr_value.c_str(), &endp, 0); // check conversion was successful and the value is within the allowed range status = (errno == 0) && (endp == attr_value.c_str() + attr_value.size()) && - (m_priority >= m_minPriority) && - (m_priority <= m_maxPriority); + setPriority(priority); } return status; @@ -201,7 +503,11 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) { SWSS_LOG_ENTER(); - sai_attribute_value_t value; + sai_acl_field_data_t matchData{}; + vector inPorts; + vector outPorts; + + matchData.enable = true; try { @@ -218,7 +524,6 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_inPorts.clear(); for (auto alias : ports) { Port port; @@ -234,11 +539,11 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_inPorts.push_back(port.m_port_id); + inPorts.push_back(port.m_port_id); } - value.aclfield.data.objlist.count = static_cast(m_inPorts.size()); - value.aclfield.data.objlist.list = m_inPorts.data(); + matchData.data.objlist.count = static_cast(inPorts.size()); + matchData.data.objlist.list = inPorts.data(); } else if (attr_name == MATCH_OUT_PORTS) { @@ -249,7 +554,6 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_outPorts.clear(); for (auto alias : ports) { Port port; @@ -265,49 +569,49 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_outPorts.push_back(port.m_port_id); + outPorts.push_back(port.m_port_id); } - value.aclfield.data.objlist.count = static_cast(m_outPorts.size()); - value.aclfield.data.objlist.list = m_outPorts.data(); + matchData.data.objlist.count = static_cast(outPorts.size()); + matchData.data.objlist.list = outPorts.data(); } else if (attr_name == MATCH_IP_TYPE) { - if (!processIpType(attr_value, value.aclfield.data.u32)) + if (!processIpType(attr_value, matchData.data.u32)) { SWSS_LOG_ERROR("Invalid IP type %s", attr_value.c_str()); return false; } - value.aclfield.mask.u32 = 0xFFFFFFFF; + matchData.mask.u32 = 0xFFFFFFFF; } else if (attr_name == MATCH_TCP_FLAGS) { // Support both exact value match and value/mask match auto flag_data = tokenize(attr_value, '/'); - value.aclfield.data.u8 = to_uint(flag_data[0], 0, 0x3F); + matchData.data.u8 = to_uint(flag_data[0], 0, 0x3F); if (flag_data.size() == 2) { - value.aclfield.mask.u8 = to_uint(flag_data[1], 0, 0x3F); + matchData.mask.u8 = to_uint(flag_data[1], 0, 0x3F); } else { - value.aclfield.mask.u8 = 0x3F; + matchData.mask.u8 = 0x3F; } } else if (attr_name == MATCH_ETHER_TYPE || attr_name == MATCH_L4_SRC_PORT || attr_name == MATCH_L4_DST_PORT) { - value.aclfield.data.u16 = to_uint(attr_value); - value.aclfield.mask.u16 = 0xFFFF; + matchData.data.u16 = to_uint(attr_value); + matchData.mask.u16 = 0xFFFF; } else if (attr_name == MATCH_VLAN_ID) { - value.aclfield.data.u16 = to_uint(attr_value); - value.aclfield.mask.u16 = 0xFFF; + matchData.data.u16 = to_uint(attr_value); + matchData.mask.u16 = 0xFFF; - if (value.aclfield.data.u16 < MIN_VLAN_ID || value.aclfield.data.u16 > MAX_VLAN_ID) + if (matchData.data.u16 < MIN_VLAN_ID || matchData.data.u16 > MAX_VLAN_ID) { SWSS_LOG_ERROR("Invalid VLAN ID: %s", attr_value.c_str()); return false; @@ -318,21 +622,21 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) /* Support both exact value match and value/mask match */ auto dscp_data = tokenize(attr_value, '/'); - value.aclfield.data.u8 = to_uint(dscp_data[0], 0, 0x3F); + matchData.data.u8 = to_uint(dscp_data[0], 0, 0x3F); if (dscp_data.size() == 2) { - value.aclfield.mask.u8 = to_uint(dscp_data[1], 0, 0x3F); + matchData.mask.u8 = to_uint(dscp_data[1], 0, 0x3F); } else { - value.aclfield.mask.u8 = 0x3F; + matchData.mask.u8 = 0x3F; } } else if (attr_name == MATCH_IP_PROTOCOL || attr_name == MATCH_NEXT_HEADER) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } else if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP) { @@ -343,8 +647,8 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) SWSS_LOG_ERROR("IP type is not v4 type"); return false; } - value.aclfield.data.ip4 = ip.getIp().getV4Addr(); - value.aclfield.mask.ip4 = ip.getMask().getV4Addr(); + matchData.data.ip4 = ip.getIp().getV4Addr(); + matchData.mask.ip4 = ip.getMask().getV4Addr(); } else if (attr_name == MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6) { @@ -354,52 +658,59 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) SWSS_LOG_ERROR("IP type is not v6 type"); return false; } - memcpy(value.aclfield.data.ip6, ip.getIp().getV6Addr(), 16); - memcpy(value.aclfield.mask.ip6, ip.getMask().getV6Addr(), 16); + memcpy(matchData.data.ip6, ip.getIp().getV6Addr(), 16); + memcpy(matchData.mask.ip6, ip.getMask().getV6Addr(), 16); } else if ((attr_name == MATCH_L4_SRC_PORT_RANGE) || (attr_name == MATCH_L4_DST_PORT_RANGE)) { - if (sscanf(attr_value.c_str(), "%d-%d", &value.u32range.min, &value.u32range.max) != 2) + AclRangeConfig rangeConfig{}; + if (sscanf(attr_value.c_str(), "%d-%d", &rangeConfig.min, &rangeConfig.max) != 2) { SWSS_LOG_ERROR("Range parse error. Attribute: %s, value: %s", attr_name.c_str(), attr_value.c_str()); return false; } + rangeConfig.rangeType = aclRangeTypeLookup[attr_name]; + // check boundaries - if ((value.u32range.min > USHRT_MAX) || - (value.u32range.max > USHRT_MAX) || - (value.u32range.min > value.u32range.max)) + if ((rangeConfig.min > USHRT_MAX) || + (rangeConfig.max > USHRT_MAX) || + (rangeConfig.min > rangeConfig.max)) { SWSS_LOG_ERROR("Range parse error. Invalid range value. Attribute: %s, value: %s", attr_name.c_str(), attr_value.c_str()); return false; } + + m_rangeConfig.push_back(rangeConfig); + + return m_pTable->validateAclRuleMatch(SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE, *this); } else if (attr_name == MATCH_TC) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } else if (attr_name == MATCH_ICMP_TYPE || attr_name == MATCH_ICMP_CODE || attr_name == MATCH_ICMPV6_TYPE || attr_name == MATCH_ICMPV6_CODE) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } else if (attr_name == MATCH_TUNNEL_VNI) { - value.aclfield.data.u32 = to_uint(attr_value); - value.aclfield.mask.u32 = 0xFFFFFFFF; + matchData.data.u32 = to_uint(attr_value); + matchData.mask.u32 = 0xFFFFFFFF; } else if (attr_name == MATCH_INNER_ETHER_TYPE || attr_name == MATCH_INNER_L4_SRC_PORT || attr_name == MATCH_INNER_L4_DST_PORT) { - value.aclfield.data.u16 = to_uint(attr_value); - value.aclfield.mask.u16 = 0xFFFF; + matchData.data.u16 = to_uint(attr_value); + matchData.mask.u16 = 0xFFFF; } else if (attr_name == MATCH_INNER_IP_PROTOCOL) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } } catch (exception &e) @@ -415,49 +726,14 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) // TODO: For backwards compatibility, users can substitute IP_PROTOCOL for NEXT_HEADER. // This should be removed in a future release. - if ((m_tableType == ACL_TABLE_MIRRORV6 || m_tableType == ACL_TABLE_L3V6) + if ((m_pTable->type.getName() == TABLE_TYPE_MIRRORV6 || m_pTable->type.getName() == TABLE_TYPE_L3V6) && attr_name == MATCH_IP_PROTOCOL) { SWSS_LOG_WARN("Support for IP protocol on IPv6 tables will be removed in a future release, please switch to using NEXT_HEADER instead!"); attr_name = MATCH_NEXT_HEADER; } - m_matches[aclMatchLookup[attr_name]] = value; - - return true; -} - -bool AclRule::validateAddAction(string attr_name, string attr_value) -{ - for (const auto& it: m_actions) - { - if (!AclRule::isActionSupported(it.first)) - { - SWSS_LOG_ERROR("Action %s:%s is not supported by ASIC", - attr_name.c_str(), attr_value.c_str()); - return false; - } - - // check if ACL action attribute entry parameter is an enum value - const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, it.first); - if (meta == nullptr) - { - SWSS_LOG_THROW("Metadata null pointer returned by sai_metadata_get_attr_metadata for action %s", - attr_name.c_str()); - } - if (meta->isenum) - { - // if ACL action attribute requires enum value check if value is supported by the ASIC - if (!m_pAclOrch->isAclActionEnumValueSupported(AclOrch::getAclActionFromAclEntry(it.first), - it.second.aclaction.parameter)) - { - SWSS_LOG_ERROR("Action %s:%s is not supported by ASIC", - attr_name.c_str(), attr_value.c_str()); - return false; - } - } - } - return true; + return setMatch(aclMatchLookup[attr_name], matchData); } bool AclRule::processIpType(string type, sai_uint32_t &ip_type) @@ -496,7 +772,6 @@ bool AclRule::createRule() { SWSS_LOG_ENTER(); - sai_object_id_t table_oid = m_pAclOrch->getTableById(m_tableId); vector rule_attrs; sai_object_id_t range_objects[2]; sai_object_list_t range_object_list = {0, range_objects}; @@ -506,7 +781,7 @@ bool AclRule::createRule() // store table oid this rule belongs to attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; - attr.value.oid = table_oid; + attr.value.oid = m_pTable->getOid(); rule_attrs.push_back(attr); attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; @@ -526,50 +801,41 @@ bool AclRule::createRule() rule_attrs.push_back(attr); } - // store matches - for (auto it : m_matches) + if (!m_rangeConfig.empty()) { - // collect ranges and add them later as a list - if (((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE) || - ((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE)) + for (const auto& rangeConfig: m_rangeConfig) { - SWSS_LOG_INFO("Creating range object %u..%u", it.second.u32range.min, it.second.u32range.max); + SWSS_LOG_INFO("Creating range object %u..%u", rangeConfig.min, rangeConfig.max); - AclRange *range = AclRange::create((sai_acl_range_type_t)it.first, it.second.u32range.min, it.second.u32range.max); + AclRange *range = AclRange::create(rangeConfig.rangeType, rangeConfig.min, rangeConfig.max); if (!range) { // release already created range if any AclRange::remove(range_objects, range_object_list.count); return false; } - else - { - range_objects[range_object_list.count++] = range->getOid(); - } - } - else - { - attr.id = it.first; - attr.value = it.second; - attr.value.aclfield.enable = true; - rule_attrs.push_back(attr); + + m_ranges.push_back(range); + range_objects[range_object_list.count++] = range->getOid(); } - } - // store ranges if any - if (range_object_list.count > 0) - { attr.id = SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE; attr.value.aclfield.enable = true; attr.value.aclfield.data.objlist = range_object_list; rule_attrs.push_back(attr); } + // store matches + for (auto& it : m_matches) + { + attr = it.second.getSaiAttr(); + rule_attrs.push_back(attr); + } + // store actions - for (auto it : m_actions) + for (auto& it : m_actions) { - attr.id = it.first; - attr.value = it.second; + attr = it.second.getSaiAttr(); rule_attrs.push_back(attr); } @@ -582,7 +848,7 @@ bool AclRule::createRule() decreaseNextHopRefCount(); } - gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_tableOid); + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_pTable->getOid()); return (status == SAI_STATUS_SUCCESS); } @@ -619,13 +885,7 @@ void AclRule::decreaseNextHopRefCount() bool AclRule::isActionSupported(sai_acl_entry_attr_t action) const { - auto action_type = AclOrch::getAclActionFromAclEntry(action); - const auto* pTable = m_pAclOrch->getTableByOid(m_tableOid); - if (pTable == nullptr) - { - SWSS_LOG_THROW("ACL table does not exist for oid %" PRIu64, m_tableOid); - } - return m_pAclOrch->isAclActionSupported(pTable->stage, action_type); + return m_pAclOrch->isAclActionSupported(m_pTable->stage, AclEntryActionToAclAction(action)); } bool AclRule::removeRule() @@ -644,7 +904,7 @@ bool AclRule::removeRule() return false; } - gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_tableOid); + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_pTable->getOid()); m_ruleOid = SAI_NULL_OBJECT_ID; @@ -671,11 +931,9 @@ bool AclRule::remove() void AclRule::updateInPorts() { SWSS_LOG_ENTER(); - sai_attribute_t attr; sai_status_t status; - attr.id = SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS; - attr.value = m_matches[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS]; + auto attr = m_matches[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].getSaiAttr(); attr.value.aclfield.enable = true; status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); @@ -685,112 +943,379 @@ void AclRule::updateInPorts() } } -shared_ptr AclRule::makeShared(acl_table_type_t type, AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple& data) +bool AclRule::update(const AclRule& updatedRule) { - string action; - bool action_found = false; - /* Find action configured by user. Based on action type create rule. */ - for (const auto& itr : kfvFieldsValues(data)) - { - string attr_name = to_upper(fvField(itr)); - string attr_value = fvValue(itr); - if (aclL3ActionLookup.find(attr_name) != aclL3ActionLookup.cend() || - aclMirrorStageLookup.find(attr_name) != aclMirrorStageLookup.cend() || - /* handle "MIRROR_ACTION" key without mirror stage specified for backward compatibility */ - attr_name == ACTION_MIRROR_ACTION || - aclDTelActionLookup.find(attr_name) != aclDTelActionLookup.cend()) - { - action_found = true; - action = attr_name; - break; - } - } + SWSS_LOG_ENTER(); - if (!action_found) + if (!m_rangeConfig.empty() || !updatedRule.m_rangeConfig.empty()) { - throw runtime_error("ACL rule action is not found in rule " + rule); + SWSS_LOG_ERROR("Updating range matches is currently not implemented"); + return false; } - if (type != ACL_TABLE_L3 && - type != ACL_TABLE_L3V6 && - type != ACL_TABLE_MIRROR && - type != ACL_TABLE_MIRRORV6 && - type != ACL_TABLE_MIRROR_DSCP && - type != ACL_TABLE_DTEL_FLOW_WATCHLIST && - type != ACL_TABLE_DTEL_DROP_WATCHLIST && - type != ACL_TABLE_MCLAG && - type != ACL_TABLE_DROP) + if (!updateCounter(updatedRule)) { - throw runtime_error("Unknown table type"); + return false; } - /* Mirror rules can exist in both tables */ - if (aclMirrorStageLookup.find(action) != aclMirrorStageLookup.cend() || - action == ACTION_MIRROR_ACTION /* implicitly ingress in old schema */) - { - return make_shared(acl, mirror, rule, table, type); - } - /* L3 rules can exist only in L3 table */ - else if (type == ACL_TABLE_L3) + if (!updatePriority(updatedRule)) { - return make_shared(acl, rule, table, type); + return false; } - /* L3V6 rules can exist only in L3V6 table */ - else if (type == ACL_TABLE_L3V6) + + if (!updateMatches(updatedRule)) { - return make_shared(acl, rule, table, type); + return false; } - /* Pfcwd rules can exist only in PFCWD table */ - else if (type == ACL_TABLE_PFCWD) + + if (!updateActions(updatedRule)) { - return make_shared(acl, rule, table, type); + return false; } - else if (type == ACL_TABLE_DTEL_FLOW_WATCHLIST) + + return true; +} + +bool AclRule::updateCounter(const AclRule& updatedRule) +{ + if (updatedRule.m_createCounter) { - if (dtel) + if (!enableCounter()) { - return make_shared(acl, dtel, rule, table, type); - } else { - throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); + return false; } } - else if (type == ACL_TABLE_DTEL_DROP_WATCHLIST) + else { - if (dtel) + if (!disableCounter()) { - return make_shared(acl, dtel, rule, table, type); - } else { - throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); + return false; } } - else if (type == ACL_TABLE_MCLAG) - { - return make_shared(acl, rule, table, type); - } - else if (type == ACL_TABLE_DROP) - { - return make_shared(acl, rule, table, type); - } - throw runtime_error("Wrong combination of table type and action in rule " + rule); + m_createCounter = updatedRule.m_createCounter; + + return true; } -bool AclRule::enableCounter() +bool AclRule::updatePriority(const AclRule& updatedRule) { - SWSS_LOG_ENTER(); - - if (m_counterOid != SAI_NULL_OBJECT_ID) + if (m_priority == updatedRule.m_priority) { return true; } - if (m_ruleOid == SAI_NULL_OBJECT_ID) + sai_attribute_t attr {}; + attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; + attr.value.s32 = updatedRule.m_priority; + if (!setAttribute(attr)) { - SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_tableId.c_str()); return false; } - if (!createCounter()) + m_priority = updatedRule.m_priority; + + return true; +} + +bool AclRule::updateMatches(const AclRule& updatedRule) +{ + vector> matchesUpdated; + vector> matchesDisabled; + + // Diff by value to get new matches and updated matches + // in a single set_difference pass. + set_difference( + updatedRule.m_matches.begin(), + updatedRule.m_matches.end(), + m_matches.begin(), m_matches.end(), + back_inserter(matchesUpdated) + ); + + // Diff by key only to get delete matches. Assuming that + // deleted matches mean setting a match attribute to disabled state. + set_difference( + m_matches.begin(), m_matches.end(), + updatedRule.m_matches.begin(), + updatedRule.m_matches.end(), + back_inserter(matchesDisabled), + [](auto& oldMatch, auto& newMatch) + { + return oldMatch.first < newMatch.first; + } + ); + + for (const auto& attrPair: matchesDisabled) + { + auto attr = attrPair.second.getSaiAttr(); + attr.value.aclfield.enable = false; + if (!setAttribute(attr)) + { + return false; + } + m_matches.erase(attrPair.first); + } + + for (const auto& attrPair: matchesUpdated) + { + auto attr = attrPair.second.getSaiAttr(); + if (!setAttribute(attr)) + { + return false; + } + setMatch(attrPair.first, attr.value.aclfield); + } + + return true; +} + +bool AclRule::updateActions(const AclRule& updatedRule) +{ + vector> actionsUpdated; + vector> actionsDisabled; + + // Diff by value to get new action and updated actions + // in a single set_difference pass. + set_difference( + updatedRule.m_actions.begin(), + updatedRule.m_actions.end(), + m_actions.begin(), m_actions.end(), + back_inserter(actionsUpdated) + ); + + // Diff by key only to get delete actions. Assuming that + // action matches mean setting a action attribute to disabled state. + set_difference( + m_actions.begin(), m_actions.end(), + updatedRule.m_actions.begin(), + updatedRule.m_actions.end(), + back_inserter(actionsDisabled), + [](auto& oldAction, auto& newAction) + { + return oldAction.first < newAction.first; + } + ); + + for (const auto& attrPair: actionsDisabled) + { + auto attr = attrPair.second.getSaiAttr(); + attr.value.aclaction.enable = false; + if (!setAttribute(attr)) + { + return false; + } + m_actions.erase(attrPair.first); + } + + for (const auto& attrPair: actionsUpdated) + { + auto attr = attrPair.second.getSaiAttr(); + if (!setAttribute(attr)) + { + return false; + } + setAction(attrPair.first, attr.value.aclaction); + } + + return true; +} + +bool AclRule::setPriority(const sai_uint32_t &value) +{ + if (!(value >= m_minPriority && value <= m_maxPriority)) + { + SWSS_LOG_ERROR("Priority value %d is out of supported priority range %d-%d", + value, m_minPriority, m_maxPriority); + return false; + } + m_priority = value; + return true; +} + +bool AclRule::setAction(sai_acl_entry_attr_t actionId, sai_acl_action_data_t actionData) +{ + if (!isActionSupported(actionId)) + { + SWSS_LOG_ERROR("Action attribute %s is not supported", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, actionId).c_str()); + return false; + } + + // check if ACL action attribute entry parameter is an enum value + const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, actionId); + if (meta == nullptr) + { + SWSS_LOG_THROW("Metadata null pointer returned by sai_metadata_get_attr_metadata for action %d", actionId); + } + if (meta->isenum) + { + // if ACL action attribute requires enum value check if value is supported by the ASIC + if (!m_pAclOrch->isAclActionEnumValueSupported(AclEntryActionToAclAction(actionId), actionData.parameter)) + { + SWSS_LOG_ERROR("Action %s is not supported by ASIC", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, actionId).c_str()); + return false; + } + } + + sai_attribute_t attr; + attr.id = actionId; + attr.value.aclaction = actionData; + + m_actions[actionId] = SaiAttrWrapper(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + + if (!m_pTable->validateAclRuleAction(actionId, *this)) + { + return false; + } + + return true; +} + +bool AclRule::setMatch(sai_acl_entry_attr_t matchId, sai_acl_field_data_t matchData) +{ + sai_attribute_t attr; + attr.id = matchId; + attr.value.aclfield = matchData; + + m_matches[matchId] = SaiAttrWrapper(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + + if (!m_pTable->validateAclRuleMatch(matchId, *this)) + { + return false; + } + + return true; +} + +bool AclRule::setAttribute(sai_attribute_t attr) +{ + auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attr.id); + if (!meta) + { + SWSS_LOG_THROW("Failed to get metadata for attribute id %d", attr.id); + } + auto status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to update attribute %s on ACL rule %s in ACL table %s: %s", + meta->attridname, getId().c_str(), getTableId().c_str(), + sai_serialize_status(status).c_str()); + return false; + } + SWSS_LOG_INFO("Successfully updated action attribute %s on ACL rule %s in ACL table %s", + meta->attridname, getId().c_str(), getTableId().c_str()); + return true; +} + +vector AclRule::getInPorts() const +{ + vector inPorts; + auto it = m_matches.find(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS); + if (it == m_matches.end()) + { + return inPorts; + } + auto attr = it->second.getSaiAttr(); + if (!attr.value.aclfield.enable) + { + return inPorts; + } + auto objlist = attr.value.aclfield.data.objlist; + inPorts = vector(objlist.list, objlist.list + objlist.count); + return inPorts; +} + +string AclRule::getId() const +{ + return m_id; +} + +string AclRule::getTableId() const +{ + return m_pTable->getId(); +} + +sai_object_id_t AclRule::getOid() const +{ + return m_ruleOid; +} + +sai_object_id_t AclRule::getCounterOid() const +{ + return m_counterOid; +} + +bool AclRule::hasCounter() const +{ + return getCounterOid() != SAI_NULL_OBJECT_ID; +} + +const vector& AclRule::getRangeConfig() const +{ + return m_rangeConfig; +} + +shared_ptr AclRule::makeShared(AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple& data) +{ + shared_ptr aclRule; + + for (const auto& itr: kfvFieldsValues(data)) + { + auto action = to_upper(fvField(itr)); + if (aclMirrorStageLookup.find(action) != aclMirrorStageLookup.cend() || + action == ACTION_MIRROR_ACTION /* implicitly ingress in old schema */) + { + return make_shared(acl, mirror, rule, table); + } + else if (aclL3ActionLookup.find(action) != aclL3ActionLookup.cend()) + { + return make_shared(acl, rule, table); + } + else if (aclDTelFlowOpTypeLookup.find(action) != aclDTelFlowOpTypeLookup.cend()) + { + if (!dtel) + { + throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); + } + + if (action == ACTION_DTEL_DROP_REPORT_ENABLE || + action == ACTION_DTEL_TAIL_DROP_REPORT_ENABLE || + action == ACTION_DTEL_REPORT_ALL_PACKETS) + { + return make_shared(acl, dtel, rule, table); + } + else + { + return make_shared(acl, dtel, rule, table); + } + } + } + + if (!aclRule) + { + throw runtime_error("ACL rule action is not found in rule " + rule); + } + + return aclRule; +} + +bool AclRule::enableCounter() +{ + SWSS_LOG_ENTER(); + + if (m_counterOid != SAI_NULL_OBJECT_ID) + { + return true; + } + + if (m_ruleOid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); + return false; + } + + if (!createCounter()) { return false; } @@ -804,7 +1329,7 @@ bool AclRule::enableCounter() sai_status_t status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to enable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to enable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); removeCounter(); return false; } @@ -823,7 +1348,7 @@ bool AclRule::disableCounter() if (m_ruleOid == SAI_NULL_OBJECT_ID) { - SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } @@ -836,7 +1361,7 @@ bool AclRule::disableCounter() sai_status_t status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to disable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to disable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } @@ -861,7 +1386,7 @@ bool AclRule::createCounter() } attr.id = SAI_ACL_COUNTER_ATTR_TABLE_ID; - attr.value.oid = m_tableOid; + attr.value.oid = m_pTable->getOid(); counter_attrs.push_back(attr); for (const auto& counterAttrPair: aclCounterLookup) @@ -873,13 +1398,13 @@ bool AclRule::createCounter() if (sai_acl_api->create_acl_counter(&m_counterOid, gSwitchId, (uint32_t)counter_attrs.size(), counter_attrs.data()) != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to create counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to create counter for the rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } - gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_tableOid); + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_pTable->getOid()); - SWSS_LOG_INFO("Created counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_INFO("Created counter for the rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return true; } @@ -887,12 +1412,11 @@ bool AclRule::createCounter() bool AclRule::removeRanges() { SWSS_LOG_ENTER(); - for (auto it : m_matches) + for (const auto& rangeConfig: m_rangeConfig) { - if (((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE) || - ((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE)) + if (!AclRange::remove(rangeConfig.rangeType, rangeConfig.min, rangeConfig.max)) { - return AclRange::remove((sai_acl_range_type_t)it.first, it.second.u32range.min, it.second.u32range.max); + return false; } } return true; @@ -909,30 +1433,30 @@ bool AclRule::removeCounter() if (sai_acl_api->remove_acl_counter(m_counterOid) != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to remove ACL counter for rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to remove ACL counter for rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } - gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_tableOid); + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_pTable->getOid()); m_counterOid = SAI_NULL_OBJECT_ID; - SWSS_LOG_INFO("Removed counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_INFO("Removed counter for the rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return true; } -AclRuleL3::AclRuleL3(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRule(aclOrch, rule, table, type, createCounter) +AclRulePacket::AclRulePacket(AclOrch *aclOrch, string rule, string table, bool createCounter) : + AclRule(aclOrch, rule, table, createCounter) { } -bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) +bool AclRulePacket::validateAddAction(string attr_name, string _attr_value) { SWSS_LOG_ENTER(); string attr_value = to_upper(_attr_value); - sai_attribute_value_t value; + sai_acl_action_data_t actionData; auto action_str = attr_name; @@ -941,7 +1465,7 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) const auto it = aclPacketActionLookup.find(attr_value); if (it != aclPacketActionLookup.cend()) { - value.aclaction.parameter.s32 = it->second; + actionData.parameter.s32 = it->second; } // handle PACKET_ACTION_REDIRECT in ACTION_PACKET_ACTION for backward compatibility else if (attr_value.find(PACKET_ACTION_REDIRECT) != string::npos) @@ -968,14 +1492,14 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) { return false; } - value.aclaction.parameter.oid = param_id; + actionData.parameter.oid = param_id; action_str = ACTION_REDIRECT_ACTION; } // handle PACKET_ACTION_DO_NOT_NAT in ACTION_PACKET_ACTION else if (attr_value == PACKET_ACTION_DO_NOT_NAT) { - value.aclaction.parameter.booldata = true; + actionData.parameter.booldata = true; action_str = ACTION_DO_NOT_NAT_ACTION; } else @@ -990,22 +1514,20 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) { return false; } - value.aclaction.parameter.oid = param_id; + actionData.parameter.oid = param_id; } else { return false; } - value.aclaction.enable = true; - - m_actions[aclL3ActionLookup[action_str]] = value; + actionData.enable = true; - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(aclL3ActionLookup[action_str], actionData); } // This method should return sai attribute id of the redirect destination -sai_object_id_t AclRuleL3::getRedirectObjectId(const string& redirect_value) +sai_object_id_t AclRulePacket::getRedirectObjectId(const string& redirect_value) { string target = redirect_value; @@ -1078,40 +1600,11 @@ sai_object_id_t AclRuleL3::getRedirectObjectId(const string& redirect_value) return SAI_NULL_OBJECT_ID; } -bool AclRuleL3::validateAddMatch(string attr_name, string attr_value) -{ - if (attr_name == MATCH_DSCP) - { - SWSS_LOG_ERROR("DSCP match is not supported for table type L3"); - return false; - } - - if (attr_name == MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6) - { - SWSS_LOG_ERROR("IPv6 address match is not supported for table type L3"); - return false; - } - - if (attr_name == MATCH_ICMPV6_TYPE || attr_name == MATCH_ICMPV6_CODE) - { - SWSS_LOG_ERROR("ICMPv6 match is not supported for table type L3"); - return false; - } - - if (attr_name == MATCH_NEXT_HEADER) - { - SWSS_LOG_ERROR("IPv6 Next Header match is not supported for table type L3"); - return false; - } - - return AclRule::validateAddMatch(attr_name, attr_value); -} - -bool AclRuleL3::validate() +bool AclRulePacket::validate() { SWSS_LOG_ENTER(); - if (m_matches.size() == 0 || m_actions.size() != 1) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_actions.size() != 1) { return false; } @@ -1119,72 +1612,13 @@ bool AclRuleL3::validate() return true; } -void AclRuleL3::update(SubjectType, void *) +void AclRulePacket::onUpdate(SubjectType, void *) { // Do nothing } -AclRulePfcwd::AclRulePfcwd(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRuleL3(aclOrch, rule, table, type, createCounter) -{ -} - -bool AclRulePfcwd::validateAddMatch(string attr_name, string attr_value) -{ - return AclRule::validateAddMatch(attr_name, attr_value); -} - -AclRuleMux::AclRuleMux(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRuleL3(aclOrch, rule, table, type, createCounter) -{ -} - -bool AclRuleMux::validateAddMatch(string attr_name, string attr_value) -{ - return AclRule::validateAddMatch(attr_name, attr_value); -} - -AclRuleL3V6::AclRuleL3V6(AclOrch *aclOrch, string rule, string table, acl_table_type_t type) : - AclRuleL3(aclOrch, rule, table, type) -{ -} - - -bool AclRuleL3V6::validateAddMatch(string attr_name, string attr_value) -{ - if (attr_name == MATCH_DSCP) - { - SWSS_LOG_ERROR("DSCP match is not supported for table type L3V6"); - return false; - } - - if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP) - { - SWSS_LOG_ERROR("IPv4 address match is not supported for table type L3V6"); - return false; - } - - if (attr_name == MATCH_ICMP_TYPE || attr_name == MATCH_ICMP_CODE) - { - SWSS_LOG_ERROR("ICMPv4 match is not supported for table type L3V6"); - return false; - } - - if (attr_name == MATCH_ETHER_TYPE) - { - SWSS_LOG_ERROR("Ethertype match is not supported for table type L3V6"); - return false; - } - - // TODO: For backwards compatibility, users can substitute IP_PROTOCOL for NEXT_HEADER. - // Should add a check for IP_PROTOCOL in a future release. - - return AclRule::validateAddMatch(attr_name, attr_value); -} - - -AclRuleMirror::AclRuleMirror(AclOrch *aclOrch, MirrorOrch *mirror, string rule, string table, acl_table_type_t type) : - AclRule(aclOrch, rule, table, type), +AclRuleMirror::AclRuleMirror(AclOrch *aclOrch, MirrorOrch *mirror, string rule, string table) : + AclRule(aclOrch, rule, table), m_state(false), m_pMirrorOrch(mirror) { @@ -1214,80 +1648,14 @@ bool AclRuleMirror::validateAddAction(string attr_name, string attr_value) m_sessionName = attr_value; // insert placeholder value, we'll set the session oid in AclRuleMirror::create() - m_actions[action] = sai_attribute_value_t{}; - - return AclRule::validateAddAction(attr_name, attr_value); -} - -bool AclRuleMirror::validateAddMatch(string attr_name, string attr_value) -{ - if ((m_tableType == ACL_TABLE_L3 || m_tableType == ACL_TABLE_L3V6) - && attr_name == MATCH_DSCP) - { - SWSS_LOG_ERROR("DSCP match is not supported for the table of type L3"); - return false; - } - - if ((m_tableType == ACL_TABLE_MIRROR_DSCP && - aclMatchLookup.find(attr_name) != aclMatchLookup.end() && - attr_name != MATCH_DSCP)) - { - SWSS_LOG_ERROR("%s match is not supported for the table of type MIRROR_DSCP", - attr_name.c_str()); - return false; - } - - /* - * Type of Tables and Supported Match Types (Configuration) - * |---------------------------------------------------| - * | Match Type | TABLE_MIRROR | TABLE_MIRRORV6 | - * |---------------------------------------------------| - * | MATCH_SRC_IP | √ | | - * | MATCH_DST_IP | √ | | - * |---------------------------------------------------| - * | MATCH_ICMP_TYPE | √ | | - * | MATCH_ICMP_CODE | √ | | - * |---------------------------------------------------| - * | MATCH_ICMPV6_TYPE | | √ | - * | MATCH_ICMPV6_CODE | | √ | - * |---------------------------------------------------| - * | MATCH_SRC_IPV6 | | √ | - * | MATCH_DST_IPV6 | | √ | - * |---------------------------------------------------| - * | MARTCH_ETHERTYPE | √ | | - * |---------------------------------------------------| - */ - - if (m_tableType == ACL_TABLE_MIRROR && - (attr_name == MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6 || - attr_name == MATCH_ICMPV6_TYPE || attr_name == MATCH_ICMPV6_CODE || - attr_name == MATCH_NEXT_HEADER)) - { - SWSS_LOG_ERROR("%s match is not supported for the table of type MIRROR", - attr_name.c_str()); - return false; - } - - // TODO: For backwards compatibility, users can substitute IP_PROTOCOL for NEXT_HEADER. - // This check should be expanded to include IP_PROTOCOL in a future release. - if (m_tableType == ACL_TABLE_MIRRORV6 && - (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP || - attr_name == MATCH_ICMP_TYPE || attr_name == MATCH_ICMP_CODE || - attr_name == MATCH_ETHER_TYPE)) - { - SWSS_LOG_ERROR("%s match is not supported for the table of type MIRRORv6", - attr_name.c_str()); - return false; - } - - return AclRule::validateAddMatch(attr_name, attr_value); + return setAction(action, sai_acl_action_data_t{}); } bool AclRuleMirror::validate() { SWSS_LOG_ENTER(); - if (m_matches.size() == 0 || m_sessionName.empty()) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_sessionName.empty()) { return false; } @@ -1337,9 +1705,11 @@ bool AclRuleMirror::activate() for (auto& it: m_actions) { - it.second.aclaction.enable = true; - it.second.aclaction.parameter.objlist.list = &oid; - it.second.aclaction.parameter.objlist.count = 1; + auto attr = it.second.getSaiAttr(); + attr.value.aclaction.enable = true; + attr.value.aclaction.parameter.objlist.list = &oid; + attr.value.aclaction.parameter.objlist.count = 1; + setAction(it.first, attr.value.aclaction); } if (!AclRule::createRule()) @@ -1382,7 +1752,20 @@ bool AclRuleMirror::deactivate() return true; } -void AclRuleMirror::update(SubjectType type, void *cntx) +bool AclRuleMirror::update(const AclRule& rule) +{ + auto mirrorRule = dynamic_cast(&rule); + if (!mirrorRule) + { + SWSS_LOG_ERROR("Cannot update mirror rule with a rule of a different type"); + return false; + } + + SWSS_LOG_ERROR("Updating mirror rule is currently not implemented"); + return false; +} + +void AclRuleMirror::onUpdate(SubjectType type, void *cntx) { if (type != SUBJECT_TYPE_MIRROR_SESSION_CHANGE) { @@ -1408,33 +1791,6 @@ void AclRuleMirror::update(SubjectType type, void *cntx) } } -AclRuleMclag::AclRuleMclag(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRuleL3(aclOrch, rule, table, type, createCounter) -{ -} - -bool AclRuleMclag::validateAddMatch(string attr_name, string attr_value) -{ - if (attr_name != MATCH_IP_TYPE && attr_name != MATCH_OUT_PORTS) - { - return false; - } - - return AclRule::validateAddMatch(attr_name, attr_value); -} - -bool AclRuleMclag::validate() -{ - SWSS_LOG_ENTER(); - - if (m_matches.size() == 0) - { - return false; - } - - return true; -} - AclTable::AclTable(AclOrch *pAclOrch, string id) noexcept : m_pAclOrch(pAclOrch), id(id) { @@ -1445,20 +1801,11 @@ AclTable::AclTable(AclOrch *pAclOrch) noexcept : m_pAclOrch(pAclOrch) } -bool AclTable::validateAddType(const acl_table_type_t &value) +bool AclTable::validateAddType(const AclTableType &tableType) { SWSS_LOG_ENTER(); - if (value == ACL_TABLE_MIRROR || value == ACL_TABLE_MIRRORV6) - { - if (!m_pAclOrch->isAclMirrorTableSupported(value)) - { - SWSS_LOG_ERROR("Failed to validate type: mirror table is not supported"); - return false; - } - } - - type = value; + type = tableType; return true; } @@ -1496,339 +1843,146 @@ bool AclTable::validateAddPorts(const unordered_set &value) } sai_object_id_t bindPortOid; - if (!AclOrch::getAclBindPortId(port, bindPortOid)) - { - SWSS_LOG_ERROR( - "Failed to get port %s bind port ID for ACL table %s", - itAlias.c_str(), id.c_str() - ); - return false; - } - - link(bindPortOid); - portSet.emplace(itAlias); - } - - return true; -} - -bool AclTable::validate() -{ - if (type == ACL_TABLE_CTRLPLANE) - return true; - - if (type == ACL_TABLE_UNKNOWN || stage == ACL_STAGE_UNKNOWN) - return false; - - return true; -} - -bool AclTable::create() -{ - SWSS_LOG_ENTER(); - - sai_attribute_t attr; - vector table_attrs; - vector bpoint_list; - - // PFC watch dog ACLs are only applied to port - if ((type == ACL_TABLE_PFCWD) || (type == ACL_TABLE_DROP)) - { - bpoint_list = { SAI_ACL_BIND_POINT_TYPE_PORT }; - } - else - { - bpoint_list = { SAI_ACL_BIND_POINT_TYPE_PORT, SAI_ACL_BIND_POINT_TYPE_LAG }; - } - - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - attr.value.s32list.count = static_cast(bpoint_list.size()); - attr.value.s32list.list = bpoint_list.data(); - table_attrs.push_back(attr); - - if (type == ACL_TABLE_PBH) - { - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_GRE_KEY; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) - { - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_LAG); - } - - return status == SAI_STATUS_SUCCESS; - } - - if ((type == ACL_TABLE_PFCWD) || (type == ACL_TABLE_DROP)) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_TC; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = (stage == ACL_STAGE_INGRESS) ? SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; - table_attrs.push_back(attr); - - if (stage == ACL_STAGE_INGRESS) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS; - attr.value.booldata = true; - table_attrs.push_back(attr); - } - - sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) + if (!AclOrch::getAclBindPortId(port, bindPortOid)) { - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t) attr.value.s32, SAI_ACL_BIND_POINT_TYPE_PORT); + SWSS_LOG_ERROR( + "Failed to get port %s bind port ID for ACL table %s", + itAlias.c_str(), id.c_str() + ); + return false; } - return status == SAI_STATUS_SUCCESS; + link(bindPortOid); + portSet.emplace(itAlias); } - if (type == ACL_TABLE_MIRROR_DSCP) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; - attr.value.booldata = true; - table_attrs.push_back(attr); + return true; +} - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = (stage == ACL_STAGE_INGRESS) ? - SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; - table_attrs.push_back(attr); +bool AclTable::validate() +{ + if (type.getName() == TABLE_TYPE_CTRLPLANE) + { + return true; + } - sai_status_t status = sai_acl_api->create_acl_table( - &m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); + if (stage == ACL_STAGE_UNKNOWN) + { + return false; + } - if (status == SAI_STATUS_SUCCESS) + if (m_pAclOrch->isAclActionListMandatoryOnTableCreation(stage)) + { + if (type.getActions().empty()) { - gCrmOrch->incCrmAclUsedCounter( - CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)attr.value.s32, SAI_ACL_BIND_POINT_TYPE_PORT); - gCrmOrch->incCrmAclUsedCounter( - CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)attr.value.s32, SAI_ACL_BIND_POINT_TYPE_LAG); + SWSS_LOG_ERROR("Action list for table %s is mandatory", id.c_str()); + return false; } - - return status == SAI_STATUS_SUCCESS; } - if (type != ACL_TABLE_MIRRORV6 && type != ACL_TABLE_L3V6) + for (const auto& action: type.getActions()) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); + if (!m_pAclOrch->isAclActionSupported(stage, action)) + { + SWSS_LOG_ERROR("Action %s is not supported on table %s", + sai_metadata_get_acl_action_type_name(action), id.c_str()); + return false; + } } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - /* - * Type of Tables and Supported Match Types (ASIC database) - * |------------------------------------------------------------------| - * | | TABLE_MIRROR | TABLE_MIRROR | TABLE_MIRRORV6 | - * | Match Type |----------------------------------------------| - * | | combined | separated | - * |------------------------------------------------------------------| - * | MATCH_SRC_IP | √ | √ | | - * | MATCH_DST_IP | √ | √ | | - * |------------------------------------------------------------------| - * | MATCH_ICMP_TYPE | √ | √ | | - * | MATCH_ICMP_CODE | √ | √ | | - * |------------------------------------------------------------------| - * | MATCH_SRC_IPV6 | √ | | √ | - * | MATCH_DST_IPV6 | √ | | √ | - * |------------------------------------------------------------------| - * | MATCH_ICMPV6_TYPE | √ | | √ | - * | MATCH_ICMPV6_CODE | √ | | √ | - * |------------------------------------------------------------------| - * | MATCH_IP_PROTOCOL | √ | √ | | - * | MATCH_NEXT_HEADER | √ | | √ | - * | -----------------------------------------------------------------| - * | MATCH_ETHERTYPE | √ | √ | | - * |------------------------------------------------------------------| - * | MATCH_IN_PORTS | √ | √ | | - * |------------------------------------------------------------------| - */ + return true; +} - // FIXME: This section has become hard to maintain and should be refactored. - if (type == ACL_TABLE_MIRROR) +bool AclTable::validateAclRuleMatch(sai_acl_entry_attr_t matchId, const AclRule& rule) const +{ + const auto& tableMatches = type.getMatches(); + const auto tableMatchId = AclEntryFieldToAclTableField(matchId); + const auto tableMatchIt = tableMatches.find(tableMatchId); + if (tableMatchIt == tableMatches.end()) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); + SWSS_LOG_ERROR("Match %s in rule %s is not supported by table %s", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, matchId).c_str(), + rule.getId().c_str(), id.c_str()); + return false; + } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); + const auto& tableMatchAttrObject = tableMatchIt->second; + if (!tableMatchAttrObject->validateAclRuleMatch(rule)) + { + SWSS_LOG_ERROR("Match %s in rule configuration %s is invalid %s", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, matchId).c_str(), + rule.getId().c_str(), id.c_str()); + return false; + } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS; - attr.value.booldata = true; - table_attrs.push_back(attr); + return true; +} - // If the switch supports v6 and requires one single table - if (m_pAclOrch->m_mirrorTableCapabilities[ACL_TABLE_MIRRORV6] && - m_pAclOrch->m_isCombinedMirrorV6Table) +bool AclTable::validateAclRuleAction(sai_acl_entry_attr_t actionId, const AclRule& rule) const +{ + // This means ACL table can hold rules with any action. + if (!type.getActions().empty()) + { + auto action = AclEntryActionToAclAction(actionId); + if (!type.getActions().count(action)) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER; - attr.value.booldata = true; - table_attrs.push_back(attr); + SWSS_LOG_ERROR("Action %s is not supported on table %s", + sai_metadata_get_acl_action_type_name(action), id.c_str()); + return false; } } - else if (type == ACL_TABLE_L3V6 || type == ACL_TABLE_MIRRORV6) // v6 only - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); + return true; +} - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER; - attr.value.booldata = true; - table_attrs.push_back(attr); - } - else // v4 only - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); +bool AclTable::create() +{ + SWSS_LOG_ENTER(); - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); + sai_attribute_t attr; + vector table_attrs; + vector action_types_list {type.getActions().begin(), type.getActions().end()}; + vector bpoint_list {type.getBindPointTypes().begin(), type.getBindPointTypes().end()}; - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); + attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; + attr.value.s32list.count = static_cast(bpoint_list.size()); + attr.value.s32list.list = bpoint_list.data(); + table_attrs.push_back(attr); - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); + for (const auto& matchPair: type.getMatches()) + { + table_attrs.push_back(matchPair.second->toSaiAttribute()); + } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; + if (!action_types_list.empty()) + { + attr.id= SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; + attr.value.s32list.count = static_cast(action_types_list.size()); + attr.value.s32list.list = action_types_list.data(); table_attrs.push_back(attr); } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS; - attr.value.booldata = true; - table_attrs.push_back(attr); - - int32_t range_types_list[] = { SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE }; - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE; - attr.value.s32list.count = (uint32_t)(sizeof(range_types_list) / sizeof(range_types_list[0])); - attr.value.s32list.list = range_types_list; - table_attrs.push_back(attr); - sai_acl_stage_t acl_stage; attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; acl_stage = (stage == ACL_STAGE_INGRESS) ? SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; attr.value.s32 = acl_stage; table_attrs.push_back(attr); - if (type == ACL_TABLE_MIRROR || type == ACL_TABLE_MIRRORV6) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; - attr.value.booldata = true; - table_attrs.push_back(attr); - } - - if (type == ACL_TABLE_MCLAG) + sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); + if (status != SAI_STATUS_SUCCESS) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS; - attr.value.booldata = true; - table_attrs.push_back(attr); + return false; } - sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) + for (const auto& bpointType: type.getBindPointTypes()) { - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, acl_stage, SAI_ACL_BIND_POINT_TYPE_PORT); - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, acl_stage, SAI_ACL_BIND_POINT_TYPE_LAG); + gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, acl_stage, bpointType); } - return status == SAI_STATUS_SUCCESS; + return true; } -void AclTable::update(SubjectType type, void *cntx) +void AclTable::onUpdate(SubjectType type, void *cntx) { SWSS_LOG_ENTER(); @@ -2016,6 +2170,36 @@ bool AclTable::remove(string rule_id) } } +bool AclTable::updateRule(shared_ptr updatedRule) +{ + SWSS_LOG_ENTER(); + + if (!updatedRule) + { + return false; + } + + auto ruleId = updatedRule->getId(); + auto ruleIter = rules.find(ruleId); + if (ruleIter == rules.end()) + { + SWSS_LOG_ERROR("Failed to update ACL rule %s as it does not exist in %s", + ruleId.c_str(), id.c_str()); + return false; + } + + if (!ruleIter->second->update(*updatedRule)) + { + SWSS_LOG_ERROR("Failed to update ACL rule %s in table %s", + ruleId.c_str(), id.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Successfully updated ACL rule %s in table %s", + ruleId.c_str(), id.c_str()); + return true; +} + bool AclTable::clear() { SWSS_LOG_ENTER(); @@ -2035,8 +2219,8 @@ bool AclTable::clear() return true; } -AclRuleDTelFlowWatchListEntry::AclRuleDTelFlowWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table, acl_table_type_t type) : - AclRule(aclOrch, rule, table, type), +AclRuleDTelFlowWatchListEntry::AclRuleDTelFlowWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table) : + AclRule(aclOrch, rule, table), m_pDTelOrch(dtel) { } @@ -2045,7 +2229,7 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a { SWSS_LOG_ENTER(); - sai_attribute_value_t value; + sai_acl_action_data_t actionData; string attr_value = to_upper(attr_val); sai_object_id_t session_oid; @@ -2069,7 +2253,7 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a return false; } - value.aclaction.parameter.s32 = it->second; + actionData.parameter.s32 = it->second; if (attr_value == DTEL_FLOW_OP_INT) { @@ -2088,7 +2272,7 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a bool ret = m_pDTelOrch->getINTSessionOid(attr_value, session_oid); if (ret) { - value.aclaction.parameter.oid = session_oid; + actionData.parameter.oid = session_oid; // Increase session reference count regardless of state to deny // attempt to remove INT session with attached ACL rules. @@ -2107,22 +2291,20 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a if (attr_name == ACTION_DTEL_FLOW_SAMPLE_PERCENT) { - value.aclaction.parameter.u8 = to_uint(attr_value); + actionData.parameter.u8 = to_uint(attr_value); } - value.aclaction.enable = true; + actionData.enable = true; if (attr_name == ACTION_DTEL_REPORT_ALL_PACKETS || attr_name == ACTION_DTEL_DROP_REPORT_ENABLE || attr_name == ACTION_DTEL_TAIL_DROP_REPORT_ENABLE) { - value.aclaction.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; - value.aclaction.enable = (attr_value == DTEL_ENABLED) ? true : false; + actionData.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; + actionData.enable = (attr_value == DTEL_ENABLED) ? true : false; } - m_actions[aclDTelActionLookup[attr_name]] = value; - - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(aclDTelActionLookup[attr_name], actionData); } bool AclRuleDTelFlowWatchListEntry::validate() @@ -2134,7 +2316,7 @@ bool AclRuleDTelFlowWatchListEntry::validate() return false; } - if (m_matches.size() == 0 || m_actions.size() == 0) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_actions.size() == 0) { return false; } @@ -2202,9 +2384,9 @@ bool AclRuleDTelFlowWatchListEntry::deactivate() return true; } -void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) +void AclRuleDTelFlowWatchListEntry::onUpdate(SubjectType type, void *cntx) { - sai_attribute_value_t value; + sai_acl_action_data_t actionData; sai_object_id_t session_oid = SAI_NULL_OBJECT_ID; if (!m_pDTelOrch) @@ -2235,8 +2417,8 @@ void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) return; } - value.aclaction.enable = true; - value.aclaction.parameter.oid = session_oid; + actionData.enable = true; + actionData.parameter.oid = session_oid; // Increase session reference count regardless of state to deny // attempt to remove INT session with attached ACL rules. @@ -2245,7 +2427,11 @@ void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) throw runtime_error("Failed to increase INT session reference count"); } - m_actions[SAI_ACL_ENTRY_ATTR_ACTION_DTEL_INT_SESSION] = value; + if (!setAction(SAI_ACL_ENTRY_ATTR_ACTION_DTEL_INT_SESSION, actionData)) + { + SWSS_LOG_ERROR("Failed to set action SAI_ACL_ENTRY_ATTR_ACTION_DTEL_INT_SESSION"); + return; + } INT_session_valid = true; @@ -2259,8 +2445,21 @@ void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) } } -AclRuleDTelDropWatchListEntry::AclRuleDTelDropWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table, acl_table_type_t type) : - AclRule(aclOrch, rule, table, type), +bool AclRuleDTelFlowWatchListEntry::update(const AclRule& rule) +{ + auto dtelDropWathcListRule = dynamic_cast(&rule); + if (!dtelDropWathcListRule) + { + SWSS_LOG_ERROR("Cannot update DTEL flow watch list rule with a rule of a different type"); + return false; + } + + SWSS_LOG_ERROR("Updating DTEL flow watch list rule is currently not implemented"); + return false; +} + +AclRuleDTelDropWatchListEntry::AclRuleDTelDropWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table) : + AclRule(aclOrch, rule, table), m_pDTelOrch(dtel) { } @@ -2274,7 +2473,7 @@ bool AclRuleDTelDropWatchListEntry::validateAddAction(string attr_name, string a return false; } - sai_attribute_value_t value; + sai_acl_action_data_t actionData; string attr_value = to_upper(attr_val); if (attr_name != ACTION_DTEL_DROP_REPORT_ENABLE && @@ -2284,13 +2483,10 @@ bool AclRuleDTelDropWatchListEntry::validateAddAction(string attr_name, string a return false; } + actionData.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; + actionData.enable = (attr_value == DTEL_ENABLED) ? true : false; - value.aclaction.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; - value.aclaction.enable = (attr_value == DTEL_ENABLED) ? true : false; - - m_actions[aclDTelActionLookup[attr_name]] = value; - - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(aclDTelActionLookup[attr_name], actionData); } bool AclRuleDTelDropWatchListEntry::validate() @@ -2302,7 +2498,7 @@ bool AclRuleDTelDropWatchListEntry::validate() return false; } - if (m_matches.size() == 0 || m_actions.size() == 0) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_actions.size() == 0) { return false; } @@ -2310,7 +2506,7 @@ bool AclRuleDTelDropWatchListEntry::validate() return true; } -void AclRuleDTelDropWatchListEntry::update(SubjectType, void *) +void AclRuleDTelDropWatchListEntry::onUpdate(SubjectType, void *) { // Do nothing } @@ -2459,24 +2655,24 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr { m_mirrorTableCapabilities = { - { ACL_TABLE_MIRROR, true }, - { ACL_TABLE_MIRRORV6, true }, + { TABLE_TYPE_MIRROR, true }, + { TABLE_TYPE_MIRRORV6, true }, }; } else { m_mirrorTableCapabilities = { - { ACL_TABLE_MIRROR, true }, - { ACL_TABLE_MIRRORV6, false }, + { TABLE_TYPE_MIRROR, true }, + { TABLE_TYPE_MIRRORV6, false }, }; } SWSS_LOG_NOTICE("%s switch capability:", platform.c_str()); - SWSS_LOG_NOTICE(" ACL_TABLE_MIRROR: %s", - m_mirrorTableCapabilities[ACL_TABLE_MIRROR] ? "yes" : "no"); - SWSS_LOG_NOTICE(" ACL_TABLE_MIRRORV6: %s", - m_mirrorTableCapabilities[ACL_TABLE_MIRRORV6] ? "yes" : "no"); + SWSS_LOG_NOTICE(" TABLE_TYPE_MIRROR: %s", + m_mirrorTableCapabilities[TABLE_TYPE_MIRROR] ? "yes" : "no"); + SWSS_LOG_NOTICE(" TABLE_TYPE_MIRRORV6: %s", + m_mirrorTableCapabilities[TABLE_TYPE_MIRRORV6] ? "yes" : "no"); // In Mellanox platform, V4 and V6 rules are stored in different tables if (platform == MLNX_PLATFORM_SUBSTRING || @@ -2496,16 +2692,17 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr for (auto const& it : m_mirrorTableCapabilities) { string value = it.second ? "true" : "false"; - switch (it.first) + if (it.first == TABLE_TYPE_MIRROR) + { + fvVector.emplace_back(TABLE_TYPE_MIRROR, value); + } + else if (it.first == TABLE_TYPE_MIRRORV6) + { + fvVector.emplace_back(TABLE_TYPE_MIRRORV6, value); + } + else { - case ACL_TABLE_MIRROR: - fvVector.emplace_back(TABLE_TYPE_MIRROR, value); - break; - case ACL_TABLE_MIRRORV6: - fvVector.emplace_back(TABLE_TYPE_MIRRORV6, value); - break; - default: - break; + // ignore } } m_switchOrch->set_switch_capability(fvVector); @@ -2514,33 +2711,214 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr attrs[0].id = SAI_SWITCH_ATTR_ACL_ENTRY_MINIMUM_PRIORITY; attrs[1].id = SAI_SWITCH_ATTR_ACL_ENTRY_MAXIMUM_PRIORITY; - sai_status_t status = sai_switch_api->get_switch_attribute(gSwitchId, 2, attrs); - if (status == SAI_STATUS_SUCCESS) - { - SWSS_LOG_NOTICE("Get ACL entry priority values, min: %u, max: %u", attrs[0].value.u32, attrs[1].value.u32); - AclRule::setRulePriorities(attrs[0].value.u32, attrs[1].value.u32); - } - else + sai_status_t status = sai_switch_api->get_switch_attribute(gSwitchId, 2, attrs); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_NOTICE("Get ACL entry priority values, min: %u, max: %u", attrs[0].value.u32, attrs[1].value.u32); + AclRule::setRulePriorities(attrs[0].value.u32, attrs[1].value.u32); + } + else + { + SWSS_LOG_ERROR("Failed to get ACL entry priority min/max values, rv:%d", status); + task_process_status handle_status = handleSaiGetStatus(SAI_API_SWITCH, status); + if (handle_status != task_process_status::task_success) + { + throw "AclOrch initialization failure"; + } + } + + queryAclActionCapability(); + + for (auto stage: {ACL_STAGE_INGRESS, ACL_STAGE_EGRESS}) + { + m_mirrorTableId[stage] = ""; + m_mirrorV6TableId[stage] = ""; + } + + initDefaultTableTypes(); + + // Attach observers + m_mirrorOrch->attach(this); + gPortsOrch->attach(this); +} + +void AclOrch::initDefaultTableTypes() +{ + SWSS_LOG_ENTER(); + + AclTableTypeBuilder builder; + + addAclTableType( + builder.withName(TABLE_TYPE_L3) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_L3V6) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_MCLAG) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_PFCWD) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TC)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS)) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_DROP) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TC)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS)) + .build() + ); + + + /* + * Type of Tables and Supported Match Types (ASIC database) + * |------------------------------------------------------------------| + * | | TABLE_MIRROR | TABLE_MIRROR | TABLE_MIRRORV6 | + * | Match Type |----------------------------------------------| + * | | combined | separated | + * |------------------------------------------------------------------| + * | MATCH_SRC_IP | √ | √ | | + * | MATCH_DST_IP | √ | √ | | + * |------------------------------------------------------------------| + * | MATCH_ICMP_TYPE | √ | √ | | + * | MATCH_ICMP_CODE | √ | √ | | + * |------------------------------------------------------------------| + * | MATCH_SRC_IPV6 | √ | | √ | + * | MATCH_DST_IPV6 | √ | | √ | + * |------------------------------------------------------------------| + * | MATCH_ICMPV6_TYPE | √ | | √ | + * | MATCH_ICMPV6_CODE | √ | | √ | + * |------------------------------------------------------------------| + * | MATCH_IP_PROTOCOL | √ | √ | | + * | MATCH_NEXT_HEADER | √ | | √ | + * | -----------------------------------------------------------------| + * | MATCH_ETHERTYPE | √ | √ | | + * |------------------------------------------------------------------| + * | MATCH_IN_PORTS | √ | √ | | + * |------------------------------------------------------------------| + */ + + if (isAclMirrorV4Supported()) { - SWSS_LOG_ERROR("Failed to get ACL entry priority min/max values, rv:%d", status); - task_process_status handle_status = handleSaiGetStatus(SAI_API_SWITCH, status); - if (handle_status != task_process_status::task_success) + addAclTableType( + builder.withName(TABLE_TYPE_MIRROR_DSCP) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DSCP)) + .build() + ); + + builder.withName(TABLE_TYPE_MIRROR) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DSCP)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})); + + if (isAclMirrorV6Supported() && isCombinedMirrorV6Table()) { - throw "AclOrch initialization failure"; + builder + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)); } + addAclTableType(builder.build()); + } + + if (isAclMirrorV6Supported()) + { + addAclTableType( + builder.withName(TABLE_TYPE_MIRRORV6) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DSCP)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); } - queryAclActionCapability(); - - for (auto stage: {ACL_STAGE_INGRESS, ACL_STAGE_EGRESS}) - { - m_mirrorTableId[stage] = ""; - m_mirrorV6TableId[stage] = ""; - } - - // Attach observers - m_mirrorOrch->attach(this); - gPortsOrch->attach(this); + // Placeholder for control plane tables + addAclTableType(builder.withName(TABLE_TYPE_CTRLPLANE).build()); } void AclOrch::queryAclActionCapability() @@ -2574,12 +2952,15 @@ void AclOrch::queryAclActionCapability() SWSS_LOG_INFO("Supported %s action count %d:", stage_str, attr.value.aclcapability.action_list.count); + auto& capabilities = m_aclCapabilities[stage]; + for (size_t i = 0; i < static_cast(attr.value.aclcapability.action_list.count); i++) { auto action = static_cast(action_list[i]); - m_aclCapabilities[stage].insert(action); + capabilities.actionList.insert(action); SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); } + capabilities.isActionListMandatoryOnTableCreation = attr.value.aclcapability.is_action_list_mandatory; } else { @@ -2627,16 +3008,18 @@ void AclOrch::putAclActionCapabilityInDB(acl_stage_type_t stage) auto stage_str = (stage == ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS); auto field = std::string("ACL_ACTIONS") + '|' + stage_str; - auto& acl_action_set = m_aclCapabilities[stage]; + auto& capabilities = m_aclCapabilities[stage]; + auto& acl_action_set = capabilities.actionList; string delimiter; ostringstream acl_action_value_stream; + ostringstream is_action_list_mandatory_stream; for (const auto& action_map: {aclL3ActionLookup, aclMirrorStageLookup, aclDTelActionLookup}) { for (const auto& it: action_map) { - auto saiAction = getAclActionFromAclEntry(it.second); + auto saiAction = AclEntryActionToAclAction(it.second); if (acl_action_set.find(saiAction) != acl_action_set.cend()) { acl_action_value_stream << delimiter << it.first; @@ -2645,8 +3028,11 @@ void AclOrch::putAclActionCapabilityInDB(acl_stage_type_t stage) } } - fvVector.emplace_back(field, acl_action_value_stream.str()); - m_switchOrch->set_switch_capability(fvVector); + is_action_list_mandatory_stream << boolalpha << capabilities.isActionListMandatoryOnTableCreation; + + fvVector.emplace_back(STATE_DB_ACL_ACTION_FIELD_IS_ACTION_LIST_MANDATORY, is_action_list_mandatory_stream.str()); + fvVector.emplace_back(STATE_DB_ACL_ACTION_FIELD_ACTION_LIST, acl_action_value_stream.str()); + m_aclStageCapabilityTable.set(stage_str, fvVector); } void AclOrch::initDefaultAclActionCapabilities(acl_stage_type_t stage) @@ -2655,9 +3041,9 @@ void AclOrch::initDefaultAclActionCapabilities(acl_stage_type_t stage) SWSS_LOG_INFO("Assumed %s %zu actions to be supported:", stage == ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS, - m_aclCapabilities[stage].size()); + m_aclCapabilities[stage].actionList.size()); - for (auto action: m_aclCapabilities[stage]) + for (auto action: m_aclCapabilities[stage].actionList) { SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); } @@ -2672,7 +3058,7 @@ void AclOrch::queryAclActionAttrEnumValues(const string &action_name, { vector fvVector; auto acl_attr = ruleAttrLookupMap.at(action_name); - auto acl_action = getAclActionFromAclEntry(acl_attr); + auto acl_action = AclEntryActionToAclAction(acl_attr); /* if the action is not supported then no need to do secondary query for * supported values @@ -2751,19 +3137,10 @@ void AclOrch::queryAclActionAttrEnumValues(const string &action_name, m_switchOrch->set_switch_capability(fvVector); } -sai_acl_action_type_t AclOrch::getAclActionFromAclEntry(sai_acl_entry_attr_t attr) -{ - if (attr < SAI_ACL_ENTRY_ATTR_ACTION_START || attr > SAI_ACL_ENTRY_ATTR_ACTION_END) - { - SWSS_LOG_THROW("Invalid ACL entry attribute passed in: %d", attr); - } - - return static_cast(attr - SAI_ACL_ENTRY_ATTR_ACTION_START); -}; - -AclOrch::AclOrch(vector& connectors, SwitchOrch *switchOrch, +AclOrch::AclOrch(vector& connectors, DBConnector* stateDb, SwitchOrch *switchOrch, PortsOrch *portOrch, MirrorOrch *mirrorOrch, NeighOrch *neighOrch, RouteOrch *routeOrch, DTelOrch *dtelOrch) : Orch(connectors), + m_aclStageCapabilityTable(stateDb, STATE_ACL_STAGE_CAPABILITY_TABLE_NAME), m_switchOrch(switchOrch), m_mirrorOrch(mirrorOrch), m_neighOrch(neighOrch), @@ -2816,13 +3193,13 @@ void AclOrch::update(SubjectType type, void *cntx) { if (type == SUBJECT_TYPE_PORT_CHANGE) { - table.second.update(type, cntx); + table.second.onUpdate(type, cntx); } else { for (auto& rule : table.second.rules) { - rule.second->update(type, cntx); + rule.second->onUpdate(type, cntx); } } } @@ -2847,6 +3224,10 @@ void AclOrch::doTask(Consumer &consumer) { doAclRuleTask(consumer); } + else if (table_name == CFG_ACL_TABLE_TYPE_TABLE_NAME || table_name == APP_ACL_TABLE_TYPE_TABLE_NAME) + { + doAclTableTypeTask(consumer); + } else { SWSS_LOG_ERROR("Invalid table %s", table_name.c_str()); @@ -3001,7 +3382,7 @@ bool AclOrch::addAclTable(AclTable &newTable) SWSS_LOG_ENTER(); string table_id = newTable.id; - if (newTable.type == ACL_TABLE_CTRLPLANE) + if (newTable.type.getName() == TABLE_TYPE_CTRLPLANE) { m_ctrlAclTables.emplace(table_id, newTable); SWSS_LOG_NOTICE("Created control plane ACL table %s", newTable.id.c_str()); @@ -3026,15 +3407,15 @@ bool AclOrch::addAclTable(AclTable &newTable) // If ACL table is new, check for the existence of current mirror tables // Note: only one table per mirror type can be created auto table_type = newTable.type; - if (table_type == ACL_TABLE_MIRROR || table_type == ACL_TABLE_MIRRORV6) + if (table_type.getName() == TABLE_TYPE_MIRROR || table_type.getName() == TABLE_TYPE_MIRRORV6) { string mirror_type; - if (table_type == ACL_TABLE_MIRROR && !m_mirrorTableId[table_stage].empty()) + if (table_type.getName() == TABLE_TYPE_MIRROR && !m_mirrorTableId[table_stage].empty()) { mirror_type = TABLE_TYPE_MIRROR; } - if (table_type == ACL_TABLE_MIRRORV6 && !m_mirrorV6TableId[table_stage].empty()) + if (table_type.getName() == TABLE_TYPE_MIRRORV6 && !m_mirrorV6TableId[table_stage].empty()) { mirror_type = TABLE_TYPE_MIRRORV6; } @@ -3052,7 +3433,7 @@ bool AclOrch::addAclTable(AclTable &newTable) } // Check if a separate mirror table is needed or not based on the platform - if (newTable.type == ACL_TABLE_MIRROR || newTable.type == ACL_TABLE_MIRRORV6) + if (newTable.type.getName() == TABLE_TYPE_MIRROR || newTable.type.getName() == TABLE_TYPE_MIRRORV6) { if (m_isCombinedMirrorV6Table && (!m_mirrorTableId[table_stage].empty() || @@ -3086,11 +3467,11 @@ bool AclOrch::addAclTable(AclTable &newTable) newTable.id.c_str(), table_oid); // Mark the existence of the mirror table - if (newTable.type == ACL_TABLE_MIRROR) + if (newTable.type.getName() == TABLE_TYPE_MIRROR) { m_mirrorTableId[table_stage] = table_id; } - else if (newTable.type == ACL_TABLE_MIRRORV6) + else if (newTable.type.getName() == TABLE_TYPE_MIRRORV6) { m_mirrorV6TableId[table_stage] = table_id; } @@ -3125,11 +3506,9 @@ bool AclOrch::removeAclTable(string table_id) auto type = m_AclTables[table_oid].type; sai_acl_stage_t sai_stage = (stage == ACL_STAGE_INGRESS) ? SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, sai_stage, SAI_ACL_BIND_POINT_TYPE_PORT, table_oid); - - if (type != ACL_TABLE_PFCWD) + for (const auto& bpointType: type.getBindPointTypes()) { - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, sai_stage, SAI_ACL_BIND_POINT_TYPE_LAG, table_oid); + gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, sai_stage, bpointType, table_oid); } SWSS_LOG_NOTICE("Successfully deleted ACL table %s", table_id.c_str()); @@ -3164,6 +3543,44 @@ bool AclOrch::removeAclTable(string table_id) } } +bool AclOrch::addAclTableType(const AclTableType& tableType) +{ + SWSS_LOG_ENTER(); + + if (tableType.getName().empty()) + { + SWSS_LOG_ERROR("Received table type without a name"); + return false; + } + + if (m_AclTableTypes.find(tableType.getName()) != m_AclTableTypes.end()) + { + SWSS_LOG_ERROR("Table type %s already exists", tableType.getName().c_str()); + return false; + } + + m_AclTableTypes.emplace(tableType.getName(), tableType); + return true; +} + +bool AclOrch::removeAclTableType(const string& tableTypeName) +{ + // It is Ok to remove table type that is in use by AclTable. + // AclTable holds a copy of AclTableType and there is no + // SAI object associated with AclTableType. + // So it is no harm to remove it without validation. + // The upper layer can although ensure that + // user does not remove table type that is referenced + // by an ACL table. + if (!m_AclTableTypes.erase(tableTypeName)) + { + SWSS_LOG_ERROR("Unknown table type %s", tableTypeName.c_str()); + return false; + } + + return true; +} + bool AclOrch::addAclRule(shared_ptr newRule, string table_id) { sai_object_id_t table_oid = getTableById(table_id); @@ -3355,12 +3772,32 @@ bool AclOrch::updateAclRule(string table_id, string rule_id, bool enableCounter) return true; } +bool AclOrch::updateAclRule(shared_ptr updatedRule) +{ + SWSS_LOG_ENTER(); + + auto tableId = updatedRule->getTableId(); + sai_object_id_t tableOid = getTableById(tableId); + if (tableOid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Failed to add ACL rule in ACL table %s. Table doesn't exist", tableId.c_str()); + return false; + } + + if (!m_AclTables[tableOid].updateRule(updatedRule)) + { + return false; + } + + return true; +} + bool AclOrch::isCombinedMirrorV6Table() { return m_isCombinedMirrorV6Table; } -bool AclOrch::isAclMirrorTableSupported(acl_table_type_t type) const +bool AclOrch::isAclMirrorTableSupported(string type) const { const auto &cit = m_mirrorTableCapabilities.find(type); if (cit == m_mirrorTableCapabilities.cend()) @@ -3371,6 +3808,26 @@ bool AclOrch::isAclMirrorTableSupported(acl_table_type_t type) const return cit->second; } +bool AclOrch::isAclMirrorV4Supported() const +{ + return isAclMirrorTableSupported(TABLE_TYPE_MIRROR); +} + +bool AclOrch::isAclMirrorV6Supported() const +{ + return isAclMirrorTableSupported(TABLE_TYPE_MIRRORV6); +} + +bool AclOrch::isAclActionListMandatoryOnTableCreation(acl_stage_type_t stage) const +{ + const auto& it = m_aclCapabilities.find(stage); + if (it == m_aclCapabilities.cend()) + { + return false; + } + return it->second.isActionListMandatoryOnTableCreation; +} + bool AclOrch::isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t action) const { const auto& it = m_aclCapabilities.find(stage); @@ -3378,7 +3835,8 @@ bool AclOrch::isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t { return false; } - return it->second.find(action) != it->second.cend(); + const auto& actionCapability = it->second; + return actionCapability.actionList.find(action) != actionCapability.actionList.cend(); } bool AclOrch::isAclActionEnumValueSupported(sai_acl_action_type_t action, sai_acl_action_parameter_t param) const @@ -3409,6 +3867,7 @@ void AclOrch::doAclTableTask(Consumer &consumer) if (op == SET_COMMAND) { AclTable newTable(this); + string tableTypeName; bool bAllAttributesOk = true; newTable.id = table_id; @@ -3426,7 +3885,7 @@ void AclOrch::doAclTableTask(Consumer &consumer) } else if (attr_name == ACL_TABLE_TYPE) { - if (!processAclTableType(attr_value, newTable.type)) + if (!processAclTableType(attr_value, tableTypeName)) { SWSS_LOG_ERROR("Failed to process ACL table %s type", table_id.c_str()); @@ -3467,6 +3926,15 @@ void AclOrch::doAclTableTask(Consumer &consumer) } } + auto tableType = getAclTableType(tableTypeName); + if (!tableType) + { + it++; + continue; + } + + newTable.validateAddType(*tableType); + // validate and create/update ACL Table if (bAllAttributesOk && newTable.validate()) { @@ -3476,7 +3944,7 @@ void AclOrch::doAclTableTask(Consumer &consumer) sai_object_id_t table_oid = getTableById(table_id); if (table_oid != SAI_NULL_OBJECT_ID && - !isAclTableTypeUpdated(newTable.type, + !isAclTableTypeUpdated(newTable.type.getName(), m_AclTables[table_oid]) && !isAclTableStageUpdated(newTable.stage, m_AclTables[table_oid])) @@ -3557,8 +4025,7 @@ void AclOrch::doAclRuleTask(Consumer &consumer) sai_object_id_t table_oid = getTableById(table_id); /* ACL table is not yet created or ACL table is a control plane table */ - /* TODO: Remove ACL_TABLE_UNKNOWN as a table with this type cannot be successfully created */ - if (table_oid == SAI_NULL_OBJECT_ID || m_AclTables[table_oid].type == ACL_TABLE_UNKNOWN) + if (table_oid == SAI_NULL_OBJECT_ID) { /* Skip the control plane rules */ @@ -3574,17 +4041,17 @@ void AclOrch::doAclRuleTask(Consumer &consumer) continue; } - auto type = m_AclTables[table_oid].type; + auto type = m_AclTables[table_oid].type.getName(); auto stage = m_AclTables[table_oid].stage; - if (type == ACL_TABLE_MIRROR || type == ACL_TABLE_MIRRORV6) + if (type == TABLE_TYPE_MIRROR || type == TABLE_TYPE_MIRRORV6) { - type = table_id == m_mirrorTableId[stage] ? ACL_TABLE_MIRROR : ACL_TABLE_MIRRORV6; + type = table_id == m_mirrorTableId[stage] ? TABLE_TYPE_MIRROR : TABLE_TYPE_MIRRORV6; } try { - newRule = AclRule::makeShared(type, this, m_mirrorOrch, m_dTelOrch, rule_id, table_id, t); + newRule = AclRule::makeShared(this, m_mirrorOrch, m_dTelOrch, rule_id, table_id, t); } catch (exception &e) { @@ -3632,7 +4099,7 @@ void AclOrch::doAclRuleTask(Consumer &consumer) if (bHasTCPFlag && !bHasIPProtocol) { string attr_name; - if (type == ACL_TABLE_MIRRORV6 || type == ACL_TABLE_L3V6) + if (type == TABLE_TYPE_MIRRORV6 || type == TABLE_TYPE_L3V6) { attr_name = MATCH_NEXT_HEADER; } @@ -3681,6 +4148,42 @@ void AclOrch::doAclRuleTask(Consumer &consumer) } } +void AclOrch::doAclTableTypeTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto keyOpFieldValues = it->second; + auto key = kfvKey(keyOpFieldValues); + auto op = kfvOp(keyOpFieldValues); + + if (op == SET_COMMAND) + { + AclTableTypeBuilder builder; + if (!AclTableTypeParser().parse(key, kfvFieldsValues(keyOpFieldValues), builder)) + { + SWSS_LOG_ERROR("Failed to parse ACL table type configuration %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + addAclTableType(builder.build()); + } + else if (op == DEL_COMMAND) + { + removeAclTableType(key); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + } + + it = consumer.m_toSync.erase(it); + } +} + bool AclOrch::processAclTablePorts(string portList, AclTable &aclTable) { SWSS_LOG_ENTER(); @@ -3714,39 +4217,26 @@ bool AclOrch::processAclTablePorts(string portList, AclTable &aclTable) return true; } -bool AclOrch::isAclTableTypeUpdated(acl_table_type_t table_type, AclTable &t) +bool AclOrch::isAclTableTypeUpdated(string table_type, AclTable &t) { - if (m_isCombinedMirrorV6Table && (table_type == ACL_TABLE_MIRROR || table_type == ACL_TABLE_MIRRORV6)) + if (m_isCombinedMirrorV6Table && (table_type == TABLE_TYPE_MIRROR || table_type == TABLE_TYPE_MIRRORV6)) { - // ACL_TABLE_MIRRORV6 and ACL_TABLE_MIRROR should be treated as same type in combined scenario - return !(t.type == ACL_TABLE_MIRROR || t.type == ACL_TABLE_MIRRORV6); + // TABLE_TYPE_MIRRORV6 and TABLE_TYPE_MIRROR should be treated as same type in combined scenario + return !(t.type.getName() == TABLE_TYPE_MIRROR || t.type.getName() == TABLE_TYPE_MIRRORV6); } - return (table_type != t.type); + return (table_type != t.type.getName()); } -bool AclOrch::processAclTableType(string type, acl_table_type_t &table_type) +bool AclOrch::processAclTableType(string type, string &out_table_type) { SWSS_LOG_ENTER(); - auto iter = aclTableTypeLookUp.find(to_upper(type)); - - if (iter == aclTableTypeLookUp.end()) + if (type.empty()) { return false; } - table_type = iter->second; - - // Mirror table check procedure - if (table_type == ACL_TABLE_MIRROR || table_type == ACL_TABLE_MIRRORV6) - { - // Check the switch capability - if (!m_mirrorTableCapabilities[table_type]) - { - SWSS_LOG_ERROR("Mirror table type %s is not supported", type.c_str()); - return false; - } - } + out_table_type = type; return true; } @@ -3773,6 +4263,18 @@ bool AclOrch::processAclTableStage(string stage, acl_stage_type_t &acl_stage) return true; } +const AclTableType* AclOrch::getAclTableType(const string& tableTypeName) const +{ + auto it = m_AclTableTypes.find(to_upper(tableTypeName)); + if (it == m_AclTableTypes.end()) + { + SWSS_LOG_INFO("Failed to find ACL table type %s", tableTypeName.c_str()); + return nullptr; + } + + return &it->second; +} + sai_object_id_t AclOrch::getTableById(string table_id) { SWSS_LOG_ENTER(); @@ -3874,220 +4376,61 @@ sai_status_t AclOrch::bindAclTable(AclTable &aclTable, bool bind) return status; } -sai_status_t AclOrch::createDTelWatchListTables() +void AclOrch::createDTelWatchListTables() { SWSS_LOG_ENTER(); - AclTable flowWLTable, dropWLTable; - sai_object_id_t table_oid; - - sai_status_t status; - sai_attribute_t attr; - vector table_attrs; - - /* Create Flow watchlist ACL table */ - - flowWLTable.id = TABLE_TYPE_DTEL_FLOW_WATCHLIST; - flowWLTable.type = ACL_TABLE_DTEL_FLOW_WATCHLIST; - flowWLTable.description = "Dataplane Telemetry Flow Watchlist table"; - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - vector bpoint_list; - bpoint_list.push_back(SAI_ACL_BIND_POINT_TYPE_SWITCH); - attr.value.s32list.count = 1; - attr.value.s32list.list = bpoint_list.data(); - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; - int32_t acl_action_list[4]; - acl_action_list[0] = SAI_ACL_ACTION_TYPE_ACL_DTEL_FLOW_OP; - acl_action_list[1] = SAI_ACL_ACTION_TYPE_DTEL_INT_SESSION; - acl_action_list[2] = SAI_ACL_ACTION_TYPE_DTEL_REPORT_ALL_PACKETS; - acl_action_list[3] = SAI_ACL_ACTION_TYPE_DTEL_FLOW_SAMPLE_PERCENT; - attr.value.s32list.count = 4; - attr.value.s32list.list = acl_action_list; - table_attrs.push_back(attr); - - status = sai_acl_api->create_acl_table(&table_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create table %s", flowWLTable.description.c_str()); - if (handleSaiCreateStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH); - m_AclTables[table_oid] = flowWLTable; - SWSS_LOG_INFO("Successfully created ACL table %s, oid: %" PRIx64, flowWLTable.description.c_str(), table_oid); - - /* Create Drop watchlist ACL table */ - - table_attrs.clear(); - - dropWLTable.id = TABLE_TYPE_DTEL_DROP_WATCHLIST; - dropWLTable.type = ACL_TABLE_DTEL_DROP_WATCHLIST; - dropWLTable.description = "Dataplane Telemetry Drop Watchlist table"; - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - bpoint_list.clear(); - bpoint_list.push_back(SAI_ACL_BIND_POINT_TYPE_SWITCH); - attr.value.s32list.count = 1; - attr.value.s32list.list = bpoint_list.data(); - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; - acl_action_list[0] = SAI_ACL_ACTION_TYPE_DTEL_DROP_REPORT_ENABLE; - acl_action_list[1] = SAI_ACL_ACTION_TYPE_DTEL_TAIL_DROP_REPORT_ENABLE; - attr.value.s32list.count = 2; - attr.value.s32list.list = acl_action_list; - table_attrs.push_back(attr); - - status = sai_acl_api->create_acl_table(&table_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create table %s", dropWLTable.description.c_str()); - if (handleSaiCreateStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH); - m_AclTables[table_oid] = dropWLTable; - SWSS_LOG_INFO("Successfully created ACL table %s, oid: %" PRIx64, dropWLTable.description.c_str(), table_oid); - - return SAI_STATUS_SUCCESS; + AclTableTypeBuilder builder; + + AclTable flowWLTable(this, TABLE_TYPE_DTEL_FLOW_WATCHLIST); + AclTable dropWLTable(this, TABLE_TYPE_DTEL_DROP_WATCHLIST); + + flowWLTable.validateAddStage(ACL_STAGE_INGRESS); + flowWLTable.validateAddType(builder + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_SWITCH) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP)) + .withAction(SAI_ACL_ACTION_TYPE_ACL_DTEL_FLOW_OP) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_INT_SESSION) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_REPORT_ALL_PACKETS) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_FLOW_SAMPLE_PERCENT) + .build() + ); + flowWLTable.setDescription("Dataplane Telemetry Flow Watchlist table"); + + dropWLTable.validateAddStage(ACL_STAGE_INGRESS); + dropWLTable.validateAddType(builder + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_SWITCH) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_DROP_REPORT_ENABLE) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_TAIL_DROP_REPORT_ENABLE) + .build() + ); + dropWLTable.setDescription("Dataplane Telemetry Drop Watchlist table"); + + addAclTable(flowWLTable); + addAclTable(dropWLTable); } -sai_status_t AclOrch::deleteDTelWatchListTables() +void AclOrch::deleteDTelWatchListTables() { SWSS_LOG_ENTER(); - AclTable flowWLTable(this), dropWLTable(this); - sai_object_id_t table_oid; - string table_id = TABLE_TYPE_DTEL_FLOW_WATCHLIST; - - sai_status_t status; - - table_oid = getTableById(table_id); - - if (table_oid == SAI_NULL_OBJECT_ID) - { - SWSS_LOG_INFO("Failed to find ACL table %s", table_id.c_str()); - return SAI_STATUS_FAILURE; - } - - status = sai_acl_api->remove_acl_table(table_oid); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to delete table %s", table_id.c_str()); - if (handleSaiRemoveStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH, table_oid); - m_AclTables.erase(table_oid); - - table_id = TABLE_TYPE_DTEL_DROP_WATCHLIST; - - table_oid = getTableById(table_id); - - if (table_oid == SAI_NULL_OBJECT_ID) - { - SWSS_LOG_INFO("Failed to find ACL table %s", table_id.c_str()); - return SAI_STATUS_FAILURE; - } - - status = sai_acl_api->remove_acl_table(table_oid); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to delete table %s", table_id.c_str()); - if (handleSaiRemoveStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH, table_oid); - m_AclTables.erase(table_oid); - - return SAI_STATUS_SUCCESS; + removeAclTable(TABLE_TYPE_DTEL_FLOW_WATCHLIST); + removeAclTable(TABLE_TYPE_DTEL_DROP_WATCHLIST); } void AclOrch::registerFlexCounter(const AclRule& rule) diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index a34d06846e..9e6db3919c 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -19,6 +19,8 @@ #include "acltable.h" +#include "saiattr.h" + #define RULE_PRIORITY "PRIORITY" #define MATCH_IN_PORTS "IN_PORTS" #define MATCH_OUT_PORTS "OUT_PORTS" @@ -48,6 +50,9 @@ #define MATCH_INNER_L4_SRC_PORT "INNER_L4_SRC_PORT" #define MATCH_INNER_L4_DST_PORT "INNER_L4_DST_PORT" +#define BIND_POINT_TYPE_PORT "PORT" +#define BIND_POINT_TYPE_PORTCHANNEL "PORTCHANNEL" + #define ACTION_PACKET_ACTION "PACKET_ACTION" #define ACTION_REDIRECT_ACTION "REDIRECT_ACTION" #define ACTION_DO_NOT_NAT_ACTION "DO_NOT_NAT_ACTION" @@ -92,16 +97,109 @@ #define ACL_COUNTER_FLEX_COUNTER_GROUP "ACL_STAT_COUNTER" +struct AclActionCapabilities +{ + set actionList; + bool isActionListMandatoryOnTableCreation {false}; +}; + typedef map acl_rule_attr_lookup_t; +typedef map acl_range_type_lookup_t; +typedef map acl_bind_point_type_lookup_t; typedef map acl_ip_type_lookup_t; typedef map acl_dtel_flow_op_type_lookup_t; typedef map acl_packet_action_lookup_t; typedef tuple acl_range_properties_t; -typedef map> acl_capabilities_t; +typedef map acl_capabilities_t; typedef map> acl_action_enum_values_capabilities_t; +class AclRule; + +class AclTableMatchInterface +{ +public: + AclTableMatchInterface(sai_acl_table_attr_t matchField); + + sai_acl_table_attr_t getId() const; + virtual sai_attribute_t toSaiAttribute() = 0; + virtual bool validateAclRuleMatch(const AclRule& rule) const = 0; +private: + sai_acl_table_attr_t m_matchField; +}; + +class AclTableMatch: public AclTableMatchInterface +{ +public: + AclTableMatch(sai_acl_table_attr_t matchField); + + sai_attribute_t toSaiAttribute() override; + bool validateAclRuleMatch(const AclRule& rule) const override; +}; + +class AclTableRangeMatch: public AclTableMatchInterface +{ +public: + AclTableRangeMatch(set rangeTypes); + + sai_attribute_t toSaiAttribute() override; + bool validateAclRuleMatch(const AclRule& rule) const override; + +private: + vector m_rangeList; +}; +class AclTableType +{ +public: + string getName() const; + const set& getBindPointTypes() const; + const map>& getMatches() const; + const set& getRangeTypes() const; + const set& getActions() const; + +private: + friend class AclTableTypeBuilder; + + string m_name; + set m_bpointTypes; + map> m_matches; + set m_aclAcitons; +}; + +class AclTableTypeBuilder +{ +public: + AclTableTypeBuilder& withName(string name); + AclTableTypeBuilder& withBindPointType(sai_acl_bind_point_type_t bpointType); + AclTableTypeBuilder& withMatch(shared_ptr match); + AclTableTypeBuilder& withAction(sai_acl_action_type_t action); + AclTableType build(); + +private: + AclTableType m_tableType; +}; + +class AclTableTypeParser +{ +public: + bool parse( + const string& key, + const vector& fieldValues, + AclTableTypeBuilder& builder); +private: + bool parseAclTableTypeMatches(const string& value, AclTableTypeBuilder& builder); + bool parseAclTableTypeActions(const string& value, AclTableTypeBuilder& builder); + bool parseAclTableTypeBindPointTypes(const string& value, AclTableTypeBuilder& builder); +}; + class AclOrch; +struct AclRangeConfig +{ + sai_acl_range_type_t rangeType; + uint32_t min; + uint32_t max; +}; + class AclRange { public: @@ -124,13 +222,15 @@ class AclRange static map m_ranges; }; +class AclTable; + class AclRule { public: - AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = true); + AclRule(AclOrch *pAclOrch, string rule, string table, bool createCounter = true); virtual bool validateAddPriority(string attr_name, string attr_value); virtual bool validateAddMatch(string attr_name, string attr_value); - virtual bool validateAddAction(string attr_name, string attr_value); + virtual bool validateAddAction(string attr_name, string attr_value) = 0; virtual bool validate() = 0; bool processIpType(string type, sai_uint32_t &ip_type); inline static void setRulePriorities(sai_uint32_t min, sai_uint32_t max) @@ -140,44 +240,23 @@ class AclRule } virtual bool create(); + virtual bool update(const AclRule& updatedRule); virtual bool remove(); - virtual void update(SubjectType, void *) = 0; + virtual void onUpdate(SubjectType, void *) = 0; virtual void updateInPorts(); virtual bool enableCounter(); virtual bool disableCounter(); - sai_object_id_t getOid() const - { - return m_ruleOid; - } - - string getId() const - { - return m_id; - } - - string getTableId() const - { - return m_tableId; - } - - sai_object_id_t getCounterOid() const - { - return m_counterOid; - } + string getId() const; + string getTableId() const; + sai_object_id_t getOid() const; + sai_object_id_t getCounterOid() const; + bool hasCounter() const; + vector getInPorts() const; - bool hasCounter() const - { - return getCounterOid() != SAI_NULL_OBJECT_ID; - } - - vector getInPorts() - { - return m_inPorts; - } - - static shared_ptr makeShared(acl_table_type_t type, AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple&); + const vector& getRangeConfig() const; + static shared_ptr makeShared(AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple&); virtual ~AclRule() {} protected: @@ -187,6 +266,17 @@ class AclRule virtual bool removeRanges(); virtual bool removeRule(); + virtual bool updatePriority(const AclRule& updatedRule); + virtual bool updateMatches(const AclRule& updatedRule); + virtual bool updateActions(const AclRule& updatedRule); + virtual bool updateCounter(const AclRule& updatedRule); + + virtual bool setPriority(const sai_uint32_t &value); + virtual bool setAction(sai_acl_entry_attr_t actionId, sai_acl_action_data_t actionData); + virtual bool setMatch(sai_acl_entry_attr_t matchId, sai_acl_field_data_t matchData); + + virtual bool setAttribute(sai_attribute_t attr); + void decreaseNextHopRefCount(); bool isActionSupported(sai_acl_entry_attr_t) const; @@ -195,72 +285,49 @@ class AclRule static sai_uint32_t m_maxPriority; AclOrch *m_pAclOrch; string m_id; - string m_tableId; - acl_table_type_t m_tableType; - sai_object_id_t m_tableOid; + const AclTable* m_pTable {nullptr}; sai_object_id_t m_ruleOid; sai_object_id_t m_counterOid; uint32_t m_priority; - map m_matches; - map m_actions; + map m_actions; + map m_matches; string m_redirect_target_next_hop; string m_redirect_target_next_hop_group; - vector m_inPorts; - vector m_outPorts; + vector m_rangeConfig; + vector m_ranges; private: bool m_createCounter; }; -class AclRuleL3: public AclRule +class AclRulePacket: public AclRule { public: - AclRuleL3(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = true); + AclRulePacket(AclOrch *m_pAclOrch, string rule, string table, bool createCounter = true); bool validateAddAction(string attr_name, string attr_value); - bool validateAddMatch(string attr_name, string attr_value); bool validate(); - void update(SubjectType, void *); + void onUpdate(SubjectType, void *) override; + protected: sai_object_id_t getRedirectObjectId(const string& redirect_param); }; -class AclRuleL3V6: public AclRuleL3 -{ -public: - AclRuleL3V6(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type); - bool validateAddMatch(string attr_name, string attr_value); -}; - -class AclRulePfcwd: public AclRuleL3 -{ -public: - AclRulePfcwd(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); - bool validateAddMatch(string attr_name, string attr_value); -}; - -class AclRuleMux: public AclRuleL3 -{ -public: - AclRuleMux(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); - bool validateAddMatch(string attr_name, string attr_value); -}; - class AclRuleMirror: public AclRule { public: - AclRuleMirror(AclOrch *m_pAclOrch, MirrorOrch *m_pMirrorOrch, string rule, string table, acl_table_type_t type); + AclRuleMirror(AclOrch *m_pAclOrch, MirrorOrch *m_pMirrorOrch, string rule, string table); bool validateAddAction(string attr_name, string attr_value); - bool validateAddMatch(string attr_name, string attr_value); bool validate(); bool createRule(); bool removeRule(); - void update(SubjectType, void *); + void onUpdate(SubjectType, void *) override; bool activate(); bool deactivate(); + bool update(const AclRule& updatedRule) override; protected: bool m_state {false}; string m_sessionName; @@ -270,16 +337,17 @@ class AclRuleMirror: public AclRule class AclRuleDTelFlowWatchListEntry: public AclRule { public: - AclRuleDTelFlowWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table, acl_table_type_t type); + AclRuleDTelFlowWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table); bool validateAddAction(string attr_name, string attr_value); bool validate(); bool createRule(); bool removeRule(); - void update(SubjectType, void *); + void onUpdate(SubjectType, void *) override; bool activate(); bool deactivate(); + bool update(const AclRule& updatedRule) override; protected: DTelOrch *m_pDTelOrch; string m_intSessionId; @@ -290,23 +358,14 @@ class AclRuleDTelFlowWatchListEntry: public AclRule class AclRuleDTelDropWatchListEntry: public AclRule { public: - AclRuleDTelDropWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table, acl_table_type_t type); + AclRuleDTelDropWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table); bool validateAddAction(string attr_name, string attr_value); bool validate(); - void update(SubjectType, void *); - + void onUpdate(SubjectType, void *) override; protected: DTelOrch *m_pDTelOrch; }; -class AclRuleMclag: public AclRuleL3 -{ -public: - AclRuleMclag(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); - bool validateAddMatch(string attr_name, string attr_value); - bool validate(); -}; - class AclTable { public: @@ -316,18 +375,23 @@ class AclTable AclTable() = default; ~AclTable() = default; - sai_object_id_t getOid() { return m_oid; } - string getId() { return id; } + sai_object_id_t getOid() const { return m_oid; } + string getId() const { return id; } void setDescription(const string &value) { description = value; } const string& getDescription() const { return description; } - bool validateAddType(const acl_table_type_t &value); + bool validateAddType(const AclTableType &tableType); bool validateAddStage(const acl_stage_type_t &value); bool validateAddPorts(const unordered_set &value); bool validate(); bool create(); + // validate AclRule match attribute against rule and table configuration + bool validateAclRuleMatch(sai_acl_entry_attr_t matchId, const AclRule& rule) const; + // validate AclRule action attribute against rule and table configuration + bool validateAclRuleAction(sai_acl_entry_attr_t actionId, const AclRule& rule) const; + // Bind the ACL table to a port which is already linked bool bind(sai_object_id_t portOid); // Unbind the ACL table to a port which is already linked @@ -342,18 +406,20 @@ class AclTable void unlink(sai_object_id_t portOid); // Add or overwrite a rule into the ACL table bool add(shared_ptr newRule); + // Update existing ACL rule + bool updateRule(shared_ptr updatedRule); // Remove a rule from the ACL table bool remove(string rule_id); // Remove all rules from the ACL table bool clear(); // Update table subject to changes - void update(SubjectType, void *); + void onUpdate(SubjectType, void *); public: string id; string description; - acl_table_type_t type = ACL_TABLE_UNKNOWN; + AclTableType type; acl_stage_type_t stage = ACL_STAGE_INGRESS; // Map port oid to group member oid @@ -374,6 +440,7 @@ class AclOrch : public Orch, public Observer { public: AclOrch(vector& connectors, + DBConnector *m_stateDb, SwitchOrch *m_switchOrch, PortsOrch *portOrch, MirrorOrch *mirrorOrch, @@ -385,6 +452,7 @@ class AclOrch : public Orch, public Observer sai_object_id_t getTableById(string table_id); const AclTable* getTableByOid(sai_object_id_t oid) const; + const AclTableType* getAclTableType(const std::string& tableTypeName) const; static swss::Table& getCountersTable() { @@ -399,23 +467,27 @@ class AclOrch : public Orch, public Observer bool addAclTable(AclTable &aclTable); bool removeAclTable(string table_id); + bool addAclTableType(const AclTableType& tableType); + bool removeAclTableType(const string& tableTypeName); bool updateAclTable(AclTable ¤tTable, AclTable &newTable); bool updateAclTable(string table_id, AclTable &table); bool addAclRule(shared_ptr aclRule, string table_id); bool removeAclRule(string table_id, string rule_id); + bool updateAclRule(shared_ptr updatedAclRule); bool updateAclRule(string table_id, string rule_id, string attr_name, void *data, bool oper); bool updateAclRule(string table_id, string rule_id, bool enableCounter); AclRule* getAclRule(string table_id, string rule_id); bool isCombinedMirrorV6Table(); - bool isAclMirrorTableSupported(acl_table_type_t type) const; + bool isAclMirrorV6Supported() const; + bool isAclMirrorV4Supported() const; + bool isAclMirrorTableSupported(string type) const; + bool isAclActionListMandatoryOnTableCreation(acl_stage_type_t stage) const; bool isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t action) const; bool isAclActionEnumValueSupported(sai_acl_action_type_t action, sai_acl_action_parameter_t param) const; bool m_isCombinedMirrorV6Table = true; - map m_mirrorTableCapabilities; - - static sai_acl_action_type_t getAclActionFromAclEntry(sai_acl_entry_attr_t attr); + map m_mirrorTableCapabilities; // Get the OID for the ACL bind point for a given port static bool getAclBindPortId(Port& port, sai_object_id_t& port_id); @@ -431,7 +503,9 @@ class AclOrch : public Orch, public Observer void doTask(Consumer &consumer); void doAclTableTask(Consumer &consumer); void doAclRuleTask(Consumer &consumer); + void doAclTableTypeTask(Consumer &consumer); void init(vector& connectors, PortsOrch *portOrch, MirrorOrch *mirrorOrch, NeighOrch *neighOrch, RouteOrch *routeOrch); + void initDefaultTableTypes(); void queryMirrorTableCapability(); void queryAclActionCapability(); @@ -449,10 +523,10 @@ class AclOrch : public Orch, public Observer sai_status_t bindAclTable(AclTable &aclTable, bool bind = true); sai_status_t deleteUnbindAclTable(sai_object_id_t table_oid); - bool isAclTableTypeUpdated(acl_table_type_t table_type, AclTable &aclTable); - bool processAclTableType(string type, acl_table_type_t &table_type); + bool isAclTableTypeUpdated(string table_type, AclTable &aclTable); bool isAclTableStageUpdated(acl_stage_type_t acl_stage, AclTable &aclTable); bool processAclTableStage(string stage, acl_stage_type_t &acl_stage); + bool processAclTableType(string type, string &out_table_type); bool processAclTablePorts(string portList, AclTable &aclTable); bool validateAclTable(AclTable &aclTable); bool updateAclTablePorts(AclTable &newTable, AclTable &curTable); @@ -460,8 +534,8 @@ class AclOrch : public Orch, public Observer AclTable &curT, set &addSet, set &delSet); - sai_status_t createDTelWatchListTables(); - sai_status_t deleteDTelWatchListTables(); + void createDTelWatchListTables(); + void deleteDTelWatchListTables(); void registerFlexCounter(const AclRule& rule); void deregisterFlexCounter(const AclRule& rule); @@ -470,10 +544,13 @@ class AclOrch : public Orch, public Observer map m_AclTables; // TODO: Move all ACL tables into one map: name -> instance map m_ctrlAclTables; + map m_AclTableTypes; static DBConnector m_countersDb; static Table m_countersTable; + Table m_aclStageCapabilityTable; + map m_mirrorTableId; map m_mirrorV6TableId; diff --git a/orchagent/acltable.h b/orchagent/acltable.h index 44d6ea4dbf..081170984f 100644 --- a/orchagent/acltable.h +++ b/orchagent/acltable.h @@ -15,6 +15,10 @@ extern "C" { #define ACL_TABLE_PORTS "PORTS" #define ACL_TABLE_SERVICES "SERVICES" +#define ACL_TABLE_TYPE_MATCHES "MATCHES" +#define ACL_TABLE_TYPE_BPOINT_TYPES "BIND_POINTS" +#define ACL_TABLE_TYPE_ACTIONS "ACTIONS" + #define STAGE_INGRESS "INGRESS" #define STAGE_EGRESS "EGRESS" @@ -39,23 +43,3 @@ typedef enum } acl_stage_type_t; typedef std::unordered_map acl_stage_type_lookup_t; - -typedef enum -{ - ACL_TABLE_UNKNOWN, - ACL_TABLE_L3, - ACL_TABLE_L3V6, - ACL_TABLE_MIRROR, - ACL_TABLE_MIRRORV6, - ACL_TABLE_MIRROR_DSCP, - ACL_TABLE_PFCWD, - ACL_TABLE_CTRLPLANE, - ACL_TABLE_DTEL_FLOW_WATCHLIST, - ACL_TABLE_DTEL_DROP_WATCHLIST, - ACL_TABLE_MCLAG, - ACL_TABLE_MUX, - ACL_TABLE_DROP, - ACL_TABLE_PBH -} acl_table_type_t; - -typedef std::unordered_map acl_table_type_lookup_t; diff --git a/orchagent/copporch.cpp b/orchagent/copporch.cpp index 6b1321b5bb..d193e215c3 100644 --- a/orchagent/copporch.cpp +++ b/orchagent/copporch.cpp @@ -1,8 +1,14 @@ #include "sai.h" #include "copporch.h" #include "portsorch.h" +#include "flexcounterorch.h" #include "tokenize.h" #include "logger.h" +#include "sai_serialize.h" +#include "schema.h" +#include "directory.h" +#include "flow_counter_handler.h" +#include "timer.h" #include #include @@ -18,8 +24,11 @@ extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; extern PortsOrch* gPortsOrch; +extern Directory gDirectory; extern bool gIsNatSupported; +#define FLEX_COUNTER_UPD_INTERVAL 1 + static map policer_meter_map = { {"packets", SAI_METER_TYPE_PACKETS}, {"bytes", SAI_METER_TYPE_BYTES} @@ -82,6 +91,21 @@ static map trap_id_map = { {"bfdv6_micro", SAI_HOSTIF_TRAP_TYPE_BFDV6_MICRO} }; + +std::string get_trap_name_by_type(sai_hostif_trap_type_t trap_type) +{ + static map trap_name_to_id_map; + if (trap_name_to_id_map.empty()) + { + for (const auto &kv : trap_id_map) + { + trap_name_to_id_map.emplace(kv.second, kv.first); + } + } + + return trap_name_to_id_map.at(trap_type); +} + static map packet_action_map = { {"drop", SAI_PACKET_ACTION_DROP}, {"forward", SAI_PACKET_ACTION_FORWARD}, @@ -97,11 +121,23 @@ const string default_trap_group = "default"; const vector default_trap_ids = { SAI_HOSTIF_TRAP_TYPE_TTL_ERROR }; +const uint HOSTIF_TRAP_COUNTER_POLLING_INTERVAL_MS = 10000; CoppOrch::CoppOrch(DBConnector* db, string tableName) : - Orch(db, tableName) + Orch(db, tableName), + m_counter_db(std::shared_ptr(new DBConnector("COUNTERS_DB", 0))), + m_flex_db(std::shared_ptr(new DBConnector("FLEX_COUNTER_DB", 0))), + m_asic_db(std::shared_ptr(new DBConnector("ASIC_DB", 0))), + m_counter_table(std::unique_ptr(new Table(m_counter_db.get(), COUNTERS_TRAP_NAME_MAP))), + m_vidToRidTable(std::unique_ptr
(new Table(m_asic_db.get(), "VIDTORID"))), + m_flex_counter_group_table(std::unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_GROUP_TABLE))), + m_trap_counter_manager(HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, HOSTIF_TRAP_COUNTER_POLLING_INTERVAL_MS, false) { SWSS_LOG_ENTER(); + auto intervT = timespec { .tv_sec = FLEX_COUNTER_UPD_INTERVAL , .tv_nsec = 0 }; + m_FlexCounterUpdTimer = new SelectableTimer(intervT); + auto executorT = new ExecutableTimer(m_FlexCounterUpdTimer, this, "FLEX_COUNTER_UPD_TIMER"); + Orch::addExecutor(executorT); initDefaultHostIntfTable(); initDefaultTrapGroup(); @@ -321,6 +357,8 @@ bool CoppOrch::applyAttributesToTrapIds(sai_object_id_t trap_group_id, } m_syncdTrapIds[trap_id].trap_group_obj = trap_group_id; m_syncdTrapIds[trap_id].trap_obj = hostif_trap_id; + m_syncdTrapIds[trap_id].trap_type = trap_id; + bindTrapCounter(hostif_trap_id, trap_id); } return true; } @@ -706,6 +744,35 @@ void CoppOrch::doTask(Consumer &consumer) } } +void CoppOrch::doTask(SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + string value; + for (auto it = m_pendingAddToFlexCntr.begin(); it != m_pendingAddToFlexCntr.end(); ) + { + const auto id = sai_serialize_object_id(it->first); + if (m_vidToRidTable->hget("", id, value)) + { + SWSS_LOG_INFO("Registering %s, id %s", it->second.c_str(), id.c_str()); + + std::unordered_set counter_stats; + FlowCounterHandler::getGenericCounterStatIdList(counter_stats); + m_trap_counter_manager.setCounterIdList(it->first, CounterType::HOSTIF_TRAP, counter_stats); + it = m_pendingAddToFlexCntr.erase(it); + } + else + { + ++it; + } + } + + if (m_pendingAddToFlexCntr.empty()) + { + m_FlexCounterUpdTimer->stop(); + } +} + void CoppOrch::getTrapAddandRemoveList(string trap_group_name, vector &trap_ids, vector &add_trap_ids, @@ -777,17 +844,9 @@ bool CoppOrch::trapGroupProcessTrapIdChange (string trap_group_name, { if (m_syncdTrapIds.find(i)!= m_syncdTrapIds.end()) { - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap( - m_syncdTrapIds[i].trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(m_syncdTrapIds[i].trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", - m_syncdTrapIds[i].trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } } } @@ -830,17 +889,9 @@ bool CoppOrch::trapGroupProcessTrapIdChange (string trap_group_name, */ if (m_syncdTrapIds[i].trap_group_obj == m_trap_group_map[trap_group_name]) { - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap( - m_syncdTrapIds[i].trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(m_syncdTrapIds[i].trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", - m_syncdTrapIds[i].trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } m_syncdTrapIds.erase(i); } @@ -882,15 +933,9 @@ bool CoppOrch::processTrapGroupDel (string trap_group_name) if (it.second.trap_group_obj == m_trap_group_map[trap_group_name]) { trap_ids_to_reset.push_back(it.first); - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap(it.second.trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(it.second.trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", it.second.trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } } } @@ -1096,3 +1141,158 @@ bool CoppOrch::trapGroupUpdatePolicer (string trap_group_name, } return true; } + +void CoppOrch::initTrapRatePlugin() +{ + if (m_trap_rate_plugin_loaded) + { + return; + } + + std::string trapRatePluginName = "trap_rates.lua"; + try + { + std::string trapLuaScript = swss::loadLuaScript(trapRatePluginName); + std::string trapSha = swss::loadRedisScript(m_counter_db.get(), trapLuaScript); + + vector fieldValues; + fieldValues.emplace_back(FLOW_COUNTER_PLUGIN_FIELD, trapSha); + fieldValues.emplace_back(STATS_MODE_FIELD, STATS_MODE_READ); + m_flex_counter_group_table->set(HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP, fieldValues); + } + catch (const runtime_error &e) + { + SWSS_LOG_ERROR("Trap flex counter groups were not set successfully: %s", e.what()); + } + m_trap_rate_plugin_loaded = true; +} + +bool CoppOrch::removeTrap(sai_object_id_t hostif_trap_id) +{ + unbindTrapCounter(hostif_trap_id); + + sai_status_t sai_status = sai_hostif_api->remove_hostif_trap(hostif_trap_id); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", + hostif_trap_id); + task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); + if (handle_status != task_success) + { + return parseHandleSaiStatusFailure(handle_status); + } + } + + return true; +} + +bool CoppOrch::bindTrapCounter(sai_object_id_t hostif_trap_id, sai_hostif_trap_type_t trap_type) +{ + auto flex_counters_orch = gDirectory.get(); + + if (!flex_counters_orch || !flex_counters_orch->getHostIfTrapCounterState()) + { + return false; + } + + if (m_trap_obj_name_map.count(hostif_trap_id) > 0) + { + return true; + } + + initTrapRatePlugin(); + + // Create generic counter + sai_object_id_t counter_id; + if (!FlowCounterHandler::createGenericCounter(counter_id)) + { + return false; + } + + // Bind generic counter to trap + sai_attribute_t trap_attr; + trap_attr.id = SAI_HOSTIF_TRAP_ATTR_COUNTER_ID; + trap_attr.value.oid = counter_id; + sai_status_t sai_status = sai_hostif_api->set_hostif_trap_attribute(hostif_trap_id, &trap_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to bind trap %" PRId64 " to counter %" PRId64 "", hostif_trap_id, counter_id); + return false; + } + + // Update COUNTERS_TRAP_NAME_MAP + auto trap_name = get_trap_name_by_type(trap_type); + vector nameMapFvs; + nameMapFvs.emplace_back(trap_name, sai_serialize_object_id(counter_id)); + m_counter_table->set("", nameMapFvs); + + auto was_empty = m_pendingAddToFlexCntr.empty(); + m_pendingAddToFlexCntr[counter_id] = trap_name; + + if (was_empty) + { + m_FlexCounterUpdTimer->start(); + } + + m_trap_obj_name_map.emplace(hostif_trap_id, trap_name); + return true; +} + +void CoppOrch::unbindTrapCounter(sai_object_id_t hostif_trap_id) +{ + auto iter = m_trap_obj_name_map.find(hostif_trap_id); + if (iter == m_trap_obj_name_map.end()) + { + return; + } + + std::string counter_oid_str; + m_counter_table->hget("", iter->second, counter_oid_str); + + // Clear FLEX_COUNTER table + sai_object_id_t counter_id; + sai_deserialize_object_id(counter_oid_str, counter_id); + auto update_iter = m_pendingAddToFlexCntr.find(counter_id); + if (update_iter == m_pendingAddToFlexCntr.end()) + { + m_trap_counter_manager.clearCounterIdList(counter_id); + } + else + { + m_pendingAddToFlexCntr.erase(update_iter); + } + + // Remove trap from COUNTERS_TRAP_NAME_MAP + m_counter_table->hdel("", iter->second); + + // Unbind generic counter to trap + sai_attribute_t trap_attr; + trap_attr.id = SAI_HOSTIF_TRAP_ATTR_COUNTER_ID; + trap_attr.value.oid = SAI_NULL_OBJECT_ID; + sai_status_t sai_status = sai_hostif_api->set_hostif_trap_attribute(hostif_trap_id, &trap_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to unbind trap %" PRId64 " to counter %" PRId64 "", hostif_trap_id, counter_id); + } + + // Remove generic counter + FlowCounterHandler::removeGenericCounter(counter_id); + + m_trap_obj_name_map.erase(iter); +} + +void CoppOrch::generateHostIfTrapCounterIdList() +{ + for (const auto &kv : m_syncdTrapIds) + { + bindTrapCounter(kv.second.trap_obj, kv.second.trap_type); + } +} + +void CoppOrch::clearHostIfTrapCounterIdList() +{ + for (const auto &kv : m_syncdTrapIds) + { + unbindTrapCounter(kv.second.trap_obj); + } +} diff --git a/orchagent/copporch.h b/orchagent/copporch.h index 4794cfd2a6..096979ebb8 100644 --- a/orchagent/copporch.h +++ b/orchagent/copporch.h @@ -3,7 +3,17 @@ #include #include +#include +#include "dbconnector.h" #include "orch.h" +#include "flex_counter_manager.h" +#include "producertable.h" +#include "table.h" +#include "selectabletimer.h" + +using namespace swss; + +#define HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP "HOSTIF_TRAP_FLOW_COUNTER" // trap fields const std::string copp_trap_id_list = "trap_ids"; @@ -33,6 +43,7 @@ struct copp_trap_objects { sai_object_id_t trap_obj; sai_object_id_t trap_group_obj; + sai_hostif_trap_type_t trap_type; }; /* TrapGroupPolicerTable: trap group ID, policer ID */ @@ -45,11 +56,15 @@ typedef std::map TrapGroupHostIfMap; typedef std::map TrapIdHostIfTableMap; /* Trap group to trap ID attributes */ typedef std::map TrapGroupTrapIdAttribs; +/* Trap OID to trap name*/ +typedef std::map TrapObjectTrapNameMap; class CoppOrch : public Orch { public: CoppOrch(swss::DBConnector* db, std::string tableName); + void generateHostIfTrapCounterIdList(); + void clearHostIfTrapCounterIdList(); protected: object_map m_trap_group_map; @@ -59,10 +74,26 @@ class CoppOrch : public Orch TrapGroupHostIfMap m_trap_group_hostif_map; TrapIdHostIfTableMap m_trapid_hostif_table_map; TrapGroupTrapIdAttribs m_trap_group_trap_id_attrs; + TrapObjectTrapNameMap m_trap_obj_name_map; + std::map m_pendingAddToFlexCntr; + + std::shared_ptr m_counter_db; + std::shared_ptr m_flex_db; + std::shared_ptr m_asic_db; + std::unique_ptr
m_counter_table; + std::unique_ptr
m_vidToRidTable; + std::unique_ptr m_flex_counter_group_table; + + FlexCounterManager m_trap_counter_manager; + + bool m_trap_rate_plugin_loaded = false; + + SelectableTimer* m_FlexCounterUpdTimer = nullptr; void initDefaultHostIntfTable(); void initDefaultTrapGroup(); void initDefaultTrapIds(); + void initTrapRatePlugin(); task_process_status processCoppRule(Consumer& consumer); bool isValidList(std::vector &trap_id_list, std::vector &all_items) const; @@ -82,7 +113,7 @@ class CoppOrch : public Orch std::vector &add_trap_ids, std::vector &rem_trap_ids); - void getTrapIdsFromTrapGroup (sai_object_id_t trap_group_obj, + void getTrapIdsFromTrapGroup (sai_object_id_t trap_group_obj, std::vector &trap_ids); bool trapGroupProcessTrapIdChange (std::string trap_group_name, @@ -99,7 +130,13 @@ class CoppOrch : public Orch bool trapGroupUpdatePolicer (std::string trap_group_name, std::vector &policer_attribs); + bool removeTrap(sai_object_id_t hostif_trap_id); + + bool bindTrapCounter(sai_object_id_t hostif_trap_id, sai_hostif_trap_type_t trap_type); + void unbindTrapCounter(sai_object_id_t hostif_trap_id); + virtual void doTask(Consumer& consumer); + void doTask(swss::SelectableTimer&) override; }; #endif /* SWSS_COPPORCH_H */ diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 86048c2601..71731e84d3 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -43,6 +43,7 @@ const unordered_map FlexCounterManager::counter_id_field_lo { CounterType::MACSEC_FLOW, MACSEC_FLOW_COUNTER_ID_LIST }, { CounterType::ACL_COUNTER, ACL_COUNTER_ATTR_ID_LIST }, { CounterType::TUNNEL, TUNNEL_COUNTER_ID_LIST }, + { CounterType::HOSTIF_TRAP, FLOW_COUNTER_ID_LIST }, }; FlexManagerDirectory g_FlexManagerDirectory; @@ -57,19 +58,19 @@ FlexCounterManager *FlexManagerDirectory::createFlexCounterManager(const string& { if (stats_mode != m_managers[group_name]->getStatsMode()) { - SWSS_LOG_ERROR("Stats mode mismatch with already created flex counter manager %s", + SWSS_LOG_ERROR("Stats mode mismatch with already created flex counter manager %s", group_name.c_str()); return NULL; } if (polling_interval != m_managers[group_name]->getPollingInterval()) { - SWSS_LOG_ERROR("Polling interval mismatch with already created flex counter manager %s", + SWSS_LOG_ERROR("Polling interval mismatch with already created flex counter manager %s", group_name.c_str()); return NULL; } if (enabled != m_managers[group_name]->getEnabled()) { - SWSS_LOG_ERROR("Enabled field mismatch with already created flex counter manager %s", + SWSS_LOG_ERROR("Enabled field mismatch with already created flex counter manager %s", group_name.c_str()); return NULL; } diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 250586ab98..6e80feb8fb 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -30,6 +30,7 @@ enum class CounterType MACSEC_FLOW, ACL_COUNTER, TUNNEL, + HOSTIF_TRAP, }; // FlexCounterManager allows users to manage a group of flex counters. @@ -47,7 +48,7 @@ class FlexCounterManager const bool enabled, swss::FieldValueTuple fv_plugin = std::make_pair("","")); - FlexCounterManager() + FlexCounterManager() {} FlexCounterManager(const FlexCounterManager&) = delete; @@ -114,7 +115,7 @@ class FlexManagerDirectory { public: FlexCounterManager* createFlexCounterManager(const std::string& group_name, const StatsMode stats_mode, - const uint polling_interval, const bool enabled, + const uint polling_interval, const bool enabled, swss::FieldValueTuple fv_plugin = std::make_pair("","")); private: std::unordered_map m_managers; diff --git a/orchagent/flex_counter/flow_counter_handler.cpp b/orchagent/flex_counter/flow_counter_handler.cpp new file mode 100644 index 0000000000..89f621fe7b --- /dev/null +++ b/orchagent/flex_counter/flow_counter_handler.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "flow_counter_handler.h" +#include "logger.h" +#include "sai_serialize.h" + +extern sai_object_id_t gSwitchId; +extern sai_counter_api_t* sai_counter_api; + +const std::vector generic_counter_stat_ids = +{ + SAI_COUNTER_STAT_PACKETS, + SAI_COUNTER_STAT_BYTES, +}; + +bool FlowCounterHandler::createGenericCounter(sai_object_id_t &counter_id) +{ + sai_attribute_t counter_attr; + counter_attr.id = SAI_COUNTER_ATTR_TYPE; + counter_attr.value.s32 = SAI_COUNTER_TYPE_REGULAR; + sai_status_t sai_status = sai_counter_api->create_counter(&counter_id, gSwitchId, 1, &counter_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to create generic counter"); + return false; + } + + return true; +} + +bool FlowCounterHandler::removeGenericCounter(sai_object_id_t counter_id) +{ + sai_status_t sai_status = sai_counter_api->remove_counter(counter_id); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove generic counter: %" PRId64 "", counter_id); + return false; + } + + return true; +} + +void FlowCounterHandler::getGenericCounterStatIdList(std::unordered_set& counter_stats) +{ + for (const auto& it: generic_counter_stat_ids) + { + counter_stats.emplace(sai_serialize_counter_stat(it)); + } +} diff --git a/orchagent/flex_counter/flow_counter_handler.h b/orchagent/flex_counter/flow_counter_handler.h new file mode 100644 index 0000000000..1b6a8bbe2a --- /dev/null +++ b/orchagent/flex_counter/flow_counter_handler.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +extern "C" { +#include "sai.h" +} + +class FlowCounterHandler +{ +public: + static bool createGenericCounter(sai_object_id_t &counter_id); + static bool removeGenericCounter(sai_object_id_t counter_id); + static void getGenericCounterStatIdList(std::unordered_set& counter_stats); +}; diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index 7ccc52e06c..dc14998774 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -1,5 +1,4 @@ #include -#include "flexcounterorch.h" #include "portsorch.h" #include "fabricportsorch.h" #include "select.h" @@ -10,6 +9,7 @@ #include "flexcounterorch.h" #include "debugcounterorch.h" #include "directory.h" +#include "copporch.h" extern sai_port_api_t *sai_port_api; @@ -18,6 +18,7 @@ extern FabricPortsOrch *gFabricPortsOrch; extern IntfsOrch *gIntfsOrch; extern BufferOrch *gBufferOrch; extern Directory gDirectory; +extern CoppOrch *gCoppOrch; #define BUFFER_POOL_WATERMARK_KEY "BUFFER_POOL_WATERMARK" #define PORT_KEY "PORT" @@ -27,6 +28,7 @@ extern Directory gDirectory; #define RIF_KEY "RIF" #define ACL_KEY "ACL" #define TUNNEL_KEY "TUNNEL" +#define FLOW_CNT_TRAP_KEY "FLOW_CNT_TRAP" unordered_map flexCounterGroupMap = { @@ -44,11 +46,13 @@ unordered_map flexCounterGroupMap = {"DEBUG_COUNTER", DEBUG_COUNTER_FLEX_COUNTER_GROUP}, {"ACL", ACL_COUNTER_FLEX_COUNTER_GROUP}, {"TUNNEL", TUNNEL_STAT_COUNTER_FLEX_COUNTER_GROUP}, + {FLOW_CNT_TRAP_KEY, HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP}, }; FlexCounterOrch::FlexCounterOrch(DBConnector *db, vector &tableNames): Orch(db, tableNames), + m_flexCounterConfigTable(db, CFG_FLEX_COUNTER_TABLE_NAME), m_flexCounterDb(new DBConnector("FLEX_COUNTER_DB", 0)), m_flexCounterGroupTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_GROUP_TABLE)) { @@ -158,6 +162,19 @@ void FlexCounterOrch::doTask(Consumer &consumer) { vxlan_tunnel_orch->generateTunnelCounterMap(); } + if (gCoppOrch && (key == FLOW_CNT_TRAP_KEY)) + { + if (value == "enable") + { + m_hostif_trap_counter_enabled = true; + gCoppOrch->generateHostIfTrapCounterIdList(); + } + else if (value == "disable") + { + gCoppOrch->clearHostIfTrapCounterIdList(); + m_hostif_trap_counter_enabled = false; + } + } vector fieldValues; fieldValues.emplace_back(FLEX_COUNTER_STATUS_FIELD, value); m_flexCounterGroupTable->set(flexCounterGroupMap[key], fieldValues); @@ -188,3 +205,45 @@ bool FlexCounterOrch::getPortBufferDropCountersState() const { return m_port_buffer_drop_counter_enabled; } + +bool FlexCounterOrch::bake() +{ + /* + * bake is called during warmreboot reconciling procedure. + * By default, it should fetch items from the tables the sub agents listen to, + * and then push them into m_toSync of each sub agent. + * The motivation is to make sub agents handle the saved entries first and then handle the upcoming entries. + */ + + std::deque entries; + vector keys; + m_flexCounterConfigTable.getKeys(keys); + for (const auto &key: keys) + { + if (!flexCounterGroupMap.count(key)) + { + SWSS_LOG_NOTICE("FlexCounterOrch: Invalid flex counter group intput %s is skipped during reconciling", key.c_str()); + continue; + } + + if (key == BUFFER_POOL_WATERMARK_KEY) + { + SWSS_LOG_NOTICE("FlexCounterOrch: Do not handle any FLEX_COUNTER table for %s update during reconciling", + BUFFER_POOL_WATERMARK_KEY); + continue; + } + + KeyOpFieldsValuesTuple kco; + + kfvKey(kco) = key; + kfvOp(kco) = SET_COMMAND; + + if (!m_flexCounterConfigTable.get(key, kfvFieldsValues(kco))) + { + continue; + } + entries.push_back(kco); + } + Consumer* consumer = dynamic_cast(getExecutor(CFG_FLEX_COUNTER_TABLE_NAME)); + return consumer->addToSync(entries); +} diff --git a/orchagent/flexcounterorch.h b/orchagent/flexcounterorch.h index 0fb9f70e4b..ceb8187506 100644 --- a/orchagent/flexcounterorch.h +++ b/orchagent/flexcounterorch.h @@ -4,6 +4,7 @@ #include "orch.h" #include "port.h" #include "producertable.h" +#include "table.h" extern "C" { #include "sai.h" @@ -17,12 +18,17 @@ class FlexCounterOrch: public Orch virtual ~FlexCounterOrch(void); bool getPortCountersState() const; bool getPortBufferDropCountersState() const; + bool getHostIfTrapCounterState() const {return m_hostif_trap_counter_enabled;} + bool bake() override; + private: std::shared_ptr m_flexCounterDb = nullptr; std::shared_ptr m_flexCounterGroupTable = nullptr; bool m_port_counter_enabled = false; bool m_port_buffer_drop_counter_enabled = false; + bool m_hostif_trap_counter_enabled = false; + Table m_flexCounterConfigTable; }; #endif diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index b270a0c5e5..1feebb4d75 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -657,11 +657,13 @@ void IntfsOrch::doTask(Consumer &consumer) MacAddress mac; uint32_t mtu = 0; - bool adminUp = false; + bool adminUp; + bool adminStateChanged = false; uint32_t nat_zone_id = 0; string proxy_arp = ""; string inband_type = ""; bool mpls = false; + string vlan = ""; for (auto idx : data) { @@ -736,6 +738,7 @@ void IntfsOrch::doTask(Consumer &consumer) SWSS_LOG_WARN("Sub interface %s unknown admin status %s", alias.c_str(), value.c_str()); } } + adminStateChanged = true; } else if (field == "nat_zone") { @@ -749,6 +752,10 @@ void IntfsOrch::doTask(Consumer &consumer) { inband_type = value; } + else if (field == "vlan") + { + vlan = value; + } } if (alias == "eth0" || alias == "docker0") @@ -818,7 +825,11 @@ void IntfsOrch::doTask(Consumer &consumer) { if (!ip_prefix_in_key && isSubIntf) { - if (!gPortsOrch->addSubPort(port, alias, adminUp, mtu)) + if (adminStateChanged == false) + { + adminUp = port.m_admin_state_up; + } + if (!gPortsOrch->addSubPort(port, alias, vlan, adminUp, mtu)) { it++; continue; @@ -858,6 +869,10 @@ void IntfsOrch::doTask(Consumer &consumer) } else { + if (adminStateChanged == false) + { + adminUp = port.m_admin_state_up; + } if (!setIntf(alias, vrf_id, ip_prefix_in_key ? &ip_prefix : nullptr, adminUp, mtu)) { it++; diff --git a/orchagent/main.cpp b/orchagent/main.cpp index aacae5fe97..de96234a2d 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -13,6 +13,8 @@ extern "C" { #include #include #include +#include +#include #include #include @@ -54,8 +56,10 @@ int gBatchSize = DEFAULT_BATCH_SIZE; bool gSairedisRecord = true; bool gSwssRecord = true; +bool gResponsePublisherRecord = false; bool gLogRotate = false; bool gSaiRedisLogRotate = false; +bool gResponsePublisherLogRotate = false; bool gSyncMode = false; sai_redis_communication_mode_t gRedisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_REDIS_ASYNC; string gAsicInstance; @@ -64,6 +68,12 @@ extern bool gIsNatSupported; ofstream gRecordOfs; string gRecordFile; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; + +#define SAIREDIS_RECORD_ENABLE 0x1 +#define SWSS_RECORD_ENABLE (0x1 << 1) +#define RESPONSE_PUBLISHER_RECORD_ENABLE (0x1 << 2) string gMySwitchType = ""; int32_t gVoqMySwitchId = -1; @@ -77,10 +87,12 @@ void usage() cout << "usage: orchagent [-h] [-r record_type] [-d record_location] [-f swss_rec_filename] [-j sairedis_rec_filename] [-b batch_size] [-m MAC] [-i INST_ID] [-s] [-z mode] [-k bulk_size]" << endl; cout << " -h: display this message" << endl; cout << " -r record_type: record orchagent logs with type (default 3)" << endl; + cout << " Bit 0: sairedis.rec, Bit 1: swss.rec, Bit 2: responsepublisher.rec. For example:" << endl; cout << " 0: do not record logs" << endl; cout << " 1: record SAI call sequence as sairedis.rec" << endl; cout << " 2: record SwSS task sequence as swss.rec" << endl; cout << " 3: enable both above two records" << endl; + cout << " 7: enable sairedis.rec, swss.rec and responsepublisher.rec" << endl; cout << " -d record_location: set record logs folder location (default .)" << endl; cout << " -b batch_size: set consumer table pop operation batch size (default 128)" << endl; cout << " -m MAC: set switch MAC address" << endl; @@ -99,6 +111,7 @@ void sighup_handler(int signo) */ gLogRotate = true; gSaiRedisLogRotate = true; + gResponsePublisherLogRotate = true; } void syncd_apply_view() @@ -115,7 +128,7 @@ void syncd_apply_view() { SWSS_LOG_ERROR("Failed to notify syncd APPLY_VIEW %d", status); exit(EXIT_FAILURE); - } + } } /* @@ -321,6 +334,8 @@ int main(int argc, char **argv) string record_location = "."; string swss_rec_filename = "swss.rec"; string sairedis_rec_filename = "sairedis.rec"; + string responsepublisher_rec_filename = "responsepublisher.rec"; + int record_type = 3; // Only swss and sairedis recordings enabled by default. while ((opt = getopt(argc, argv, "b:m:r:f:j:d:i:hsz:k:")) != -1) { @@ -346,24 +361,10 @@ int main(int argc, char **argv) gMacAddress = MacAddress(optarg); break; case 'r': - if (!strcmp(optarg, "0")) - { - gSairedisRecord = false; - gSwssRecord = false; - } - else if (!strcmp(optarg, "1")) - { - gSwssRecord = false; - } - else if (!strcmp(optarg, "2")) - { - gSairedisRecord = false; - } - else if (!strcmp(optarg, "3")) - { - continue; /* default behavior */ - } - else + // Disable all recordings if atoi() fails i.e. returns 0 due to + // invalid command line argument. + record_type = atoi(optarg); + if (record_type < 0 || record_type > 7) { usage(); exit(EXIT_FAILURE); @@ -434,6 +435,14 @@ int main(int argc, char **argv) attr.value.ptr = (void *)on_fdb_event; attrs.push_back(attr); + // Initialize recording parameters. + gSairedisRecord = + (record_type & SAIREDIS_RECORD_ENABLE) == SAIREDIS_RECORD_ENABLE; + gSwssRecord = (record_type & SWSS_RECORD_ENABLE) == SWSS_RECORD_ENABLE; + gResponsePublisherRecord = + (record_type & RESPONSE_PUBLISHER_RECORD_ENABLE) == + RESPONSE_PUBLISHER_RECORD_ENABLE; + /* Disable/enable SwSS recording */ if (gSwssRecord) { @@ -447,6 +456,24 @@ int main(int argc, char **argv) gRecordOfs << getTimestamp() << "|recording started" << endl; } + // Disable/Enable response publisher recording. + if (gResponsePublisherRecord) + { + gResponsePublisherRecordFile = record_location + "/" + responsepublisher_rec_filename; + gResponsePublisherRecordOfs.open(gResponsePublisherRecordFile, std::ofstream::out | std::ofstream::app); + if (!gResponsePublisherRecordOfs.is_open()) + { + SWSS_LOG_ERROR("Failed to open Response Publisher recording file %s", + gResponsePublisherRecordFile.c_str()); + gResponsePublisherRecord = false; + } + else + { + gResponsePublisherRecordOfs << getTimestamp() << "|recording started" + << endl; + } + } + attr.id = SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY; attr.value.ptr = (void *)on_port_state_change; attrs.push_back(attr); @@ -644,7 +671,7 @@ int main(int argc, char **argv) } else { - orchDaemon = make_shared(&appl_db, &config_db, &state_db, chassis_app_db.get()); + orchDaemon = make_shared(&appl_db, &config_db, &state_db, chassis_app_db.get()); } if (!orchDaemon->init()) diff --git a/orchagent/muxorch.cpp b/orchagent/muxorch.cpp index f7af9f1f5c..5b7b0570a5 100644 --- a/orchagent/muxorch.cpp +++ b/orchagent/muxorch.cpp @@ -709,7 +709,6 @@ MuxAclHandler::MuxAclHandler(sai_object_id_t port, string alias) SWSS_LOG_ENTER(); // There is one handler instance per MUX port - acl_table_type_t table_type = ACL_TABLE_DROP; string table_name = MUX_ACL_TABLE_NAME; string rule_name = MUX_ACL_RULE_NAME; @@ -723,8 +722,8 @@ MuxAclHandler::MuxAclHandler(sai_object_id_t port, string alias) // First time handling of Mux Table, create ACL table, and bind createMuxAclTable(port, table_name); - shared_ptr newRule = - make_shared(gAclOrch, rule_name, table_name, table_type); + shared_ptr newRule = + make_shared(gAclOrch, rule_name, table_name); createMuxAclRule(newRule, table_name); } else @@ -734,8 +733,8 @@ MuxAclHandler::MuxAclHandler(sai_object_id_t port, string alias) AclRule* rule = gAclOrch->getAclRule(table_name, rule_name); if (rule == nullptr) { - shared_ptr newRule = - make_shared(gAclOrch, rule_name, table_name, table_type); + shared_ptr newRule = + make_shared(gAclOrch, rule_name, table_name); createMuxAclRule(newRule, table_name); } else @@ -776,7 +775,7 @@ void MuxAclHandler::createMuxAclTable(sai_object_id_t port, string strTable) auto inserted = acl_table_.emplace(piecewise_construct, std::forward_as_tuple(strTable), - std::forward_as_tuple()); + std::forward_as_tuple(gAclOrch, strTable)); assert(inserted.second); @@ -791,14 +790,15 @@ void MuxAclHandler::createMuxAclTable(sai_object_id_t port, string strTable) return; } - acl_table.type = ACL_TABLE_DROP; - acl_table.id = strTable; + auto dropType = gAclOrch->getAclTableType(TABLE_TYPE_DROP); + assert(dropType); + acl_table.validateAddType(*dropType); acl_table.stage = ACL_STAGE_INGRESS; gAclOrch->addAclTable(acl_table); bindAllPorts(acl_table); } -void MuxAclHandler::createMuxAclRule(shared_ptr rule, string strTable) +void MuxAclHandler::createMuxAclRule(shared_ptr rule, string strTable) { SWSS_LOG_ENTER(); diff --git a/orchagent/muxorch.h b/orchagent/muxorch.h index fa8b058830..6e4f70408c 100644 --- a/orchagent/muxorch.h +++ b/orchagent/muxorch.h @@ -43,7 +43,7 @@ class MuxAclHandler private: void createMuxAclTable(sai_object_id_t port, string strTable); - void createMuxAclRule(shared_ptr rule, string strTable); + void createMuxAclRule(shared_ptr rule, string strTable); void bindAllPorts(AclTable &acl_table); // class shared dict: ACL table name -> ACL table diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index 5a27e9161a..0992e329a4 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include "timestamp.h" #include "orch.h" @@ -197,9 +199,16 @@ size_t Consumer::refillToSync() auto subTable = dynamic_cast(consumerTable); if (subTable != NULL) { - std::deque entries; - subTable->pops(entries); - return addToSync(entries); + size_t update_size = 0; + size_t total_size = 0; + do + { + std::deque entries; + subTable->pops(entries); + update_size = addToSync(entries); + total_size += update_size; + } while (update_size != 0); + return total_size; } else { @@ -215,10 +224,13 @@ void Consumer::execute() { SWSS_LOG_ENTER(); - std::deque entries; - getConsumerTable()->pops(entries); - - addToSync(entries); + size_t update_size = 0; + do + { + std::deque entries; + getConsumerTable()->pops(entries); + update_size = addToSync(entries); + } while (update_size != 0); drain(); } @@ -1047,4 +1059,3 @@ void Orch2::doTask(Consumer &consumer) } } } - diff --git a/orchagent/orch.h b/orchagent/orch.h index ca75fac179..46a5d446ce 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -20,6 +20,7 @@ extern "C" { #include "notificationconsumer.h" #include "selectabletimer.h" #include "macaddress.h" +#include "response_publisher.h" const char delimiter = ':'; const char list_item_delimiter = ','; @@ -246,6 +247,8 @@ class Orch virtual task_process_status handleSaiRemoveStatus(sai_api_t api, sai_status_t status, void *context = nullptr); virtual task_process_status handleSaiGetStatus(sai_api_t api, sai_status_t status, void *context = nullptr); bool parseHandleSaiStatusFailure(task_process_status status); + + ResponsePublisher m_publisher; private: void removeMeFromObjsReferencedByMe(type_map &type_maps, const std::string &table, const std::string &obj_name, const std::string &field, const std::string &old_referenced_obj_name); void addConsumer(swss::DBConnector *db, std::string tableName, int pri = default_orch_pri); diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index dbfcf4a61f..8329a68f8c 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -48,6 +48,7 @@ MlagOrch *gMlagOrch; IsoGrpOrch *gIsoGrpOrch; MACsecOrch *gMacsecOrch; DebugCounterOrch *gDebugCounterOrch; +CoppOrch *gCoppOrch; BfdOrch *gBfdOrch; Srv6Orch *gSrv6Orch; @@ -123,6 +124,8 @@ bool OrchDaemon::init() TableConnector stateDbFdb(m_stateDb, STATE_FDB_TABLE_NAME); TableConnector stateMclagDbFdb(m_stateDb, STATE_MCLAG_REMOTE_FDB_TABLE_NAME); gFdbOrch = new FdbOrch(m_applDb, app_fdb_tables, stateDbFdb, stateMclagDbFdb, gPortsOrch); + TableConnector stateDbBfdSessionTable(m_stateDb, STATE_BFD_SESSION_TABLE_NAME); + gBfdOrch = new BfdOrch(m_applDb, APP_BFD_SESSION_TABLE_NAME, stateDbBfdSessionTable); vector vnet_tables = { APP_VNET_RT_TABLE_NAME, @@ -181,7 +184,7 @@ bool OrchDaemon::init() gNhgOrch = new NhgOrch(m_applDb, APP_NEXTHOP_GROUP_TABLE_NAME); gCbfNhgOrch = new CbfNhgOrch(m_applDb, APP_CLASS_BASED_NEXT_HOP_GROUP_TABLE_NAME); - CoppOrch *copp_orch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); + gCoppOrch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_applDb, APP_TUNNEL_DECAP_TABLE_NAME); VxlanTunnelOrch *vxlan_tunnel_orch = new VxlanTunnelOrch(m_stateDb, m_applDb, APP_VXLAN_TUNNEL_TABLE_NAME); @@ -230,15 +233,19 @@ bool OrchDaemon::init() gMirrorOrch = new MirrorOrch(stateDbMirrorSession, confDbMirrorSession, gPortsOrch, gRouteOrch, gNeighOrch, gFdbOrch, policer_orch); TableConnector confDbAclTable(m_configDb, CFG_ACL_TABLE_TABLE_NAME); + TableConnector confDbAclTableType(m_configDb, CFG_ACL_TABLE_TYPE_TABLE_NAME); TableConnector confDbAclRuleTable(m_configDb, CFG_ACL_RULE_TABLE_NAME); TableConnector appDbAclTable(m_applDb, APP_ACL_TABLE_TABLE_NAME); + TableConnector appDbAclTableType(m_applDb, APP_ACL_TABLE_TYPE_TABLE_NAME); TableConnector appDbAclRuleTable(m_applDb, APP_ACL_RULE_TABLE_NAME); vector acl_table_connectors = { + confDbAclTableType, confDbAclTable, confDbAclRuleTable, appDbAclTable, - appDbAclRuleTable + appDbAclRuleTable, + appDbAclTableType, }; vector dtel_tables = { @@ -306,9 +313,6 @@ bool OrchDaemon::init() gMacsecOrch = new MACsecOrch(m_applDb, m_stateDb, macsec_app_tables, gPortsOrch); - TableConnector stateDbBfdSessionTable(m_stateDb, STATE_BFD_SESSION_TABLE_NAME); - gBfdOrch = new BfdOrch(m_applDb, APP_BFD_SESSION_TABLE_NAME, stateDbBfdSessionTable); - gNhgMapOrch = new NhgMapOrch(m_applDb, APP_FC_TO_NHG_INDEX_MAP_TABLE_NAME); /* @@ -319,7 +323,8 @@ bool OrchDaemon::init() * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, copp_orch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, gDebugCounterOrch, gMacsecOrch, gBfdOrch, gSrv6Orch}; + + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, gDebugCounterOrch, gMacsecOrch, gBfdOrch, gSrv6Orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) @@ -351,7 +356,9 @@ bool OrchDaemon::init() dtel_orch = new DTelOrch(m_configDb, dtel_tables, gPortsOrch); m_orchList.push_back(dtel_orch); } - gAclOrch = new AclOrch(acl_table_connectors, gSwitchOrch, gPortsOrch, gMirrorOrch, gNeighOrch, gRouteOrch, dtel_orch); + + gAclOrch = new AclOrch(acl_table_connectors, m_stateDb, + gSwitchOrch, gPortsOrch, gMirrorOrch, gNeighOrch, gRouteOrch, dtel_orch); vector mlag_tables = { { CFG_MCLAG_TABLE_NAME }, diff --git a/orchagent/p4orch/acl_rule_manager.cpp b/orchagent/p4orch/acl_rule_manager.cpp new file mode 100644 index 0000000000..3984fd956f --- /dev/null +++ b/orchagent/p4orch/acl_rule_manager.cpp @@ -0,0 +1,2009 @@ +#include "p4orch/acl_rule_manager.h" + +#include +#include +#include + +#include "converter.h" +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "sai_serialize.h" +#include "tokenize.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_acl_api_t *sai_acl_api; +extern sai_policer_api_t *sai_policer_api; +extern sai_hostif_api_t *sai_hostif_api; +extern CrmOrch *gCrmOrch; +extern PortsOrch *gPortsOrch; +extern P4Orch *gP4Orch; + +namespace p4orch +{ +namespace +{ + +const std::string concatTableNameAndRuleKey(const std::string &table_name, const std::string &rule_key) +{ + return table_name + kTableKeyDelimiter + rule_key; +} + +} // namespace + +void AclRuleManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void AclRuleManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const auto &op = kfvOp(key_op_fvs_tuple); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + SWSS_LOG_NOTICE("OP: %s, RULE_KEY: %s", op.c_str(), QuotedVar(db_key).c_str()); + + ReturnCode status; + auto app_db_entry_or = deserializeAclRuleAppDbEntry(table_name, db_key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + status = validateAclRuleAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for ACL rule APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + + const auto &acl_table_name = app_db_entry.acl_table_name; + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + + const auto &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *acl_rule = getAclRule(acl_table_name, acl_rule_key); + if (acl_rule == nullptr) + { + status = processAddRuleRequest(acl_rule_key, app_db_entry); + } + else + { + status = processUpdateRuleRequest(app_db_entry, *acl_rule); + } + } + else if (operation == DEL_COMMAND) + { + status = processDeleteRuleRequest(acl_table_name, acl_rule_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << operation; + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +ReturnCode AclRuleManager::setUpUserDefinedTraps() +{ + SWSS_LOG_ENTER(); + + const auto trapGroupMap = m_coppOrch->getTrapGroupMap(); + const auto trapGroupHostIfMap = m_coppOrch->getTrapGroupHostIfMap(); + for (int queue_num = 1; queue_num <= P4_CPU_QUEUE_MAX_NUM; queue_num++) + { + auto trap_group_it = trapGroupMap.find(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(queue_num)); + if (trap_group_it == trapGroupMap.end()) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Trap group was not found given trap group name: " + << GENL_PACKET_TRAP_GROUP_NAME_PREFIX << queue_num); + } + const sai_object_id_t trap_group_oid = trap_group_it->second; + auto hostif_oid_it = trapGroupHostIfMap.find(trap_group_oid); + if (hostif_oid_it == trapGroupHostIfMap.end()) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Hostif object id was not found given trap group - " << trap_group_it->first); + } + // Create user defined trap + std::vector trap_attrs; + sai_attribute_t attr; + attr.id = SAI_HOSTIF_USER_DEFINED_TRAP_ATTR_TRAP_GROUP; + attr.value.oid = trap_group_oid; + trap_attrs.push_back(attr); + attr.id = SAI_HOSTIF_USER_DEFINED_TRAP_ATTR_TYPE; + attr.value.s32 = SAI_HOSTIF_USER_DEFINED_TRAP_TYPE_ACL; + trap_attrs.push_back(attr); + P4UserDefinedTrapHostifTableEntry udt_hostif; + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_hostif_api->create_hostif_user_defined_trap(&udt_hostif.user_defined_trap, gSwitchId, + (uint32_t)trap_attrs.size(), trap_attrs.data()), + "Failed to create trap by calling " + "sai_hostif_api->create_hostif_user_defined_trap"); + std::vector sai_host_table_attr; + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_TYPE; + attr.value.s32 = SAI_HOSTIF_TABLE_ENTRY_TYPE_TRAP_ID; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_TRAP_ID; + attr.value.oid = udt_hostif.user_defined_trap; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_CHANNEL_TYPE; + attr.value.s32 = SAI_HOSTIF_TABLE_ENTRY_CHANNEL_TYPE_GENETLINK; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_HOST_IF; + attr.value.oid = hostif_oid_it->second; + sai_host_table_attr.push_back(attr); + + auto sai_status = + sai_hostif_api->create_hostif_table_entry(&udt_hostif.hostif_table_entry, gSwitchId, + (uint32_t)sai_host_table_attr.size(), sai_host_table_attr.data()); + if (sai_status != SAI_STATUS_SUCCESS) + { + ReturnCode return_code = ReturnCode(sai_status) << "Failed to create hostif table entry by calling " + "sai_hostif_api->remove_hostif_user_defined_trap"; + sai_hostif_api->remove_hostif_user_defined_trap(udt_hostif.user_defined_trap); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", return_code.message().c_str(), + sai_serialize_status(sai_status).c_str()); + return return_code; + } + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, std::to_string(queue_num), + udt_hostif.user_defined_trap, /*ref_count=*/1); + m_userDefinedTraps.push_back(udt_hostif); + SWSS_LOG_NOTICE("Created user defined trap for QUEUE number %d: %s", queue_num, + sai_serialize_object_id(udt_hostif.user_defined_trap).c_str()); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::cleanUpUserDefinedTraps() +{ + SWSS_LOG_ENTER(); + + for (size_t queue_num = 1; queue_num <= m_userDefinedTraps.size(); queue_num++) + { + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_hostif_api->remove_hostif_table_entry(m_userDefinedTraps[queue_num - 1].hostif_table_entry), + "Failed to create hostif table entry."); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, std::to_string(queue_num)); + sai_hostif_api->remove_hostif_user_defined_trap(m_userDefinedTraps[queue_num - 1].user_defined_trap); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, std::to_string(queue_num)); + } + m_userDefinedTraps.clear(); + return ReturnCode(); +} + +void AclRuleManager::doAclCounterStatsTask() +{ + SWSS_LOG_ENTER(); + + for (const auto &table_it : m_aclRuleTables) + { + const auto &table_name = fvField(table_it); + for (const auto &rule_it : fvValue(table_it)) + { + if (!fvValue(rule_it).counter.packets_enabled && !fvValue(rule_it).counter.bytes_enabled) + continue; + auto status = setAclRuleCounterStats(fvValue(rule_it)); + if (!status.ok()) + { + status.prepend("Failed to set counters stats for ACL rule " + QuotedVar(table_name) + ":" + + QuotedVar(fvField(rule_it)) + " in COUNTERS_DB: "); + SWSS_LOG_ERROR("%s", status.message().c_str()); + continue; + } + } + } +} + +ReturnCode AclRuleManager::createAclCounter(const std::string &acl_table_name, const std::string &counter_key, + const P4AclCounter &p4_acl_counter, sai_object_id_t *counter_oid) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + std::vector counter_attrs; + sai_object_id_t acl_table_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name, &acl_table_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Invalid ACL counter to create: ACL table key " << QuotedVar(acl_table_name) + << " not found."); + } + attr.id = SAI_ACL_COUNTER_ATTR_TABLE_ID; + attr.value.oid = acl_table_oid; + counter_attrs.push_back(attr); + + if (p4_acl_counter.bytes_enabled) + { + attr.id = SAI_ACL_COUNTER_ATTR_ENABLE_BYTE_COUNT; + attr.value.booldata = true; + counter_attrs.push_back(attr); + } + + if (p4_acl_counter.packets_enabled) + { + attr.id = SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT; + attr.value.booldata = true; + counter_attrs.push_back(attr); + } + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_acl_api->create_acl_counter(counter_oid, gSwitchId, (uint32_t)counter_attrs.size(), counter_attrs.data()), + "Faied to create counter for the rule in table " << sai_serialize_object_id(acl_table_oid)); + SWSS_LOG_NOTICE("Suceeded to create ACL counter %s ", sai_serialize_object_id(*counter_oid).c_str()); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_COUNTER, counter_key, *counter_oid); + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, acl_table_oid); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); + return ReturnCode(); +} + +ReturnCode AclRuleManager::removeAclCounter(const std::string &acl_table_name, const std::string &counter_key) +{ + SWSS_LOG_ENTER(); + sai_object_id_t counter_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_COUNTER, counter_key, &counter_oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to remove ACL counter by key " << QuotedVar(counter_key) + << ": invalid counter key."); + } + sai_object_id_t table_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name, &table_oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to remove ACL counter " + << sai_serialize_object_id(counter_oid) << " in table " + << QuotedVar(acl_table_name) << ": invalid table key."); + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->remove_acl_counter(counter_oid), + "Failed to remove ACL counter " << sai_serialize_object_id(counter_oid) + << " in table " << QuotedVar(acl_table_name)); + + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, table_oid); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_COUNTER, counter_key); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); + SWSS_LOG_NOTICE("Removing record about the counter %s from the DB", sai_serialize_object_id(counter_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclRuleManager::createAclMeter(const P4AclMeter &p4_acl_meter, const std::string &meter_key, + sai_object_id_t *meter_oid) +{ + SWSS_LOG_ENTER(); + + std::vector meter_attrs; + sai_attribute_t meter_attr; + meter_attr.id = SAI_POLICER_ATTR_METER_TYPE; + meter_attr.value.s32 = p4_acl_meter.type; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_MODE; + meter_attr.value.s32 = p4_acl_meter.mode; + meter_attrs.push_back(meter_attr); + + if (p4_acl_meter.enabled) + { + meter_attr.id = SAI_POLICER_ATTR_CBS; + meter_attr.value.u64 = p4_acl_meter.cburst; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_CIR; + meter_attr.value.u64 = p4_acl_meter.cir; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_PIR; + meter_attr.value.u64 = p4_acl_meter.pir; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_PBS; + meter_attr.value.u64 = p4_acl_meter.pburst; + meter_attrs.push_back(meter_attr); + } + + for (const auto &packet_color_action : p4_acl_meter.packet_color_actions) + { + meter_attr.id = fvField(packet_color_action); + meter_attr.value.s32 = fvValue(packet_color_action); + meter_attrs.push_back(meter_attr); + } + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_policer_api->create_policer(meter_oid, gSwitchId, (uint32_t)meter_attrs.size(), meter_attrs.data()), + "Failed to create ACL meter"); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_POLICER, meter_key, *meter_oid); + SWSS_LOG_NOTICE("Suceeded to create ACL meter %s ", sai_serialize_object_id(*meter_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclRuleManager::updateAclMeter(const P4AclMeter &new_acl_meter, const P4AclMeter &old_acl_meter) +{ + SWSS_LOG_ENTER(); + + std::vector meter_attrs; + std::vector rollback_attrs; + sai_attribute_t meter_attr; + + if (old_acl_meter.cburst != new_acl_meter.cburst) + { + meter_attr.id = SAI_POLICER_ATTR_CBS; + meter_attr.value.u64 = new_acl_meter.cburst; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.cburst; + rollback_attrs.push_back(meter_attr); + } + if (old_acl_meter.cir != new_acl_meter.cir) + { + meter_attr.id = SAI_POLICER_ATTR_CIR; + meter_attr.value.u64 = new_acl_meter.cir; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.cir; + rollback_attrs.push_back(meter_attr); + } + if (old_acl_meter.pir != new_acl_meter.pir) + { + meter_attr.id = SAI_POLICER_ATTR_PIR; + meter_attr.value.u64 = new_acl_meter.pir; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.pir; + rollback_attrs.push_back(meter_attr); + } + if (old_acl_meter.pburst != new_acl_meter.pburst) + { + meter_attr.id = SAI_POLICER_ATTR_PBS; + meter_attr.value.u64 = new_acl_meter.pburst; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.pburst; + rollback_attrs.push_back(meter_attr); + } + + std::set colors_to_reset; + for (const auto &old_color_action : old_acl_meter.packet_color_actions) + { + colors_to_reset.insert(fvField(old_color_action)); + } + + for (const auto &packet_color_action : new_acl_meter.packet_color_actions) + { + const auto &it = old_acl_meter.packet_color_actions.find(fvField(packet_color_action)); + if (it == old_acl_meter.packet_color_actions.end() || it->second != fvValue(packet_color_action)) + { + meter_attr.id = fvField(packet_color_action); + meter_attr.value.s32 = fvValue(packet_color_action); + meter_attrs.push_back(meter_attr); + meter_attr.value.s32 = + (it == old_acl_meter.packet_color_actions.end()) ? SAI_PACKET_ACTION_FORWARD : it->second; + rollback_attrs.push_back(meter_attr); + } + if (it != old_acl_meter.packet_color_actions.end()) + { + colors_to_reset.erase(fvField(packet_color_action)); + } + } + + for (const auto &packet_color : colors_to_reset) + { + meter_attr.id = packet_color; + meter_attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + meter_attrs.push_back(meter_attr); + const auto &it = old_acl_meter.packet_color_actions.find(packet_color); + meter_attr.value.s32 = it->second; + rollback_attrs.push_back(meter_attr); + } + + ReturnCode status; + int i; + for (i = 0; i < static_cast(meter_attrs.size()); ++i) + { + status = ReturnCode(sai_policer_api->set_policer_attribute(old_acl_meter.meter_oid, &meter_attrs[i])); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update ACL meter attributes: %s", status.message().c_str()); + break; + } + } + if (!status.ok()) + { + for (--i; i >= 0; --i) + { + auto sai_status = sai_policer_api->set_policer_attribute(old_acl_meter.meter_oid, &rollback_attrs[i]); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set ACL policer attribute. SAI_STATUS: %s", + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to set ACL policer attribute in recovery."); + } + } + return status; + } + SWSS_LOG_NOTICE("Suceeded to update ACL meter %s ", sai_serialize_object_id(old_acl_meter.meter_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclRuleManager::removeAclMeter(const std::string &meter_key) +{ + SWSS_LOG_ENTER(); + sai_object_id_t meter_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_POLICER, meter_key, &meter_oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get ACL meter object id for ACL rule " + << QuotedVar(meter_key)); + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_policer_api->remove_policer(meter_oid), + "Failed to remove ACL meter for ACL rule " << QuotedVar(meter_key)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_POLICER, meter_key); + SWSS_LOG_NOTICE("Suceeded to remove ACL meter %s: %s ", QuotedVar(meter_key).c_str(), + sai_serialize_object_id(meter_oid).c_str()); + return ReturnCode(); +} + +ReturnCodeOr AclRuleManager::deserializeAclRuleAppDbEntry( + const std::string &acl_table_name, const std::string &key, const std::vector &attributes) +{ + sai_object_id_t table_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name, &table_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "ACL table " << QuotedVar(acl_table_name) << " is not found"; + } + P4AclRuleAppDbEntry app_db_entry = {}; + app_db_entry.acl_table_name = acl_table_name; + app_db_entry.db_key = concatTableNameAndRuleKey(acl_table_name, key); + // Parse rule key : match fields and priority + try + { + const auto &rule_key_json = nlohmann::json::parse(key); + if (!rule_key_json.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid ACL rule key: should be a JSON object."; + } + for (auto rule_key_it = rule_key_json.begin(); rule_key_it != rule_key_json.end(); ++rule_key_it) + { + if (rule_key_it.key() == kPriority) + { + if (!rule_key_it.value().is_number_unsigned()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL rule priority type: should be uint32_t"; + } + app_db_entry.priority = rule_key_it.value(); + continue; + } + else + { + const auto &tokenized_match_field = tokenize(rule_key_it.key(), kFieldDelimiter); + if (tokenized_match_field.size() <= 1 || tokenized_match_field[0] != kMatchPrefix) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown ACL match field string " << QuotedVar(rule_key_it.key()); + } + app_db_entry.match_fvs[tokenized_match_field[1]] = rule_key_it.value(); + } + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize ACL rule match key"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == kControllerMetadata) + continue; + if (field == kAction) + { + app_db_entry.action = value; + continue; + } + const auto &tokenized_field = tokenize(field, kFieldDelimiter); + if (tokenized_field.size() <= 1) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown ACL rule field " << QuotedVar(field); + } + const auto &prefix = tokenized_field[0]; + if (prefix == kActionParamPrefix) + { + const auto ¶m_name = tokenized_field[1]; + app_db_entry.action_param_fvs[param_name] = value; + } + else if (prefix == kMeterPrefix) + { + const auto &meter_attr_name = tokenized_field[1]; + if (std::stoi(value) < 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL meter field value " << QuotedVar(field) << ": " << QuotedVar(value); + } + if (meter_attr_name == kMeterCir) + { + app_db_entry.meter.cir = std::stoi(value); + } + else if (meter_attr_name == kMeterCburst) + { + app_db_entry.meter.cburst = std::stoi(value); + } + else if (meter_attr_name == kMeterPir) + { + app_db_entry.meter.pir = std::stoi(value); + } + else if (meter_attr_name == kMeterPburst) + { + app_db_entry.meter.pburst = std::stoi(value); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown ACL meter field " << QuotedVar(field); + } + app_db_entry.meter.enabled = true; + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown ACL rule field " << QuotedVar(field); + } + } + return app_db_entry; +} + +ReturnCode AclRuleManager::validateAclRuleAppDbEntry(const P4AclRuleAppDbEntry &app_db_entry) +{ + if (app_db_entry.priority == 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL rule in table " << QuotedVar(app_db_entry.acl_table_name) << " is missing priority"; + } + return ReturnCode(); +} + +P4AclRule *AclRuleManager::getAclRule(const std::string &acl_table_name, const std::string &acl_rule_key) +{ + if (m_aclRuleTables[acl_table_name].find(acl_rule_key) == m_aclRuleTables[acl_table_name].end()) + { + return nullptr; + } + return &m_aclRuleTables[acl_table_name][acl_rule_key]; +} + +ReturnCode AclRuleManager::setAclRuleCounterStats(const P4AclRule &acl_rule) +{ + SWSS_LOG_ENTER(); + + std::vector counter_stats_values; + // Query colored packets/bytes stats by ACL meter object id if packet color is + // defined + if (!acl_rule.meter.packet_color_actions.empty()) + { + std::vector counter_stats_ids; + const auto &packet_colors = acl_rule.meter.packet_color_actions; + for (const auto &pc : packet_colors) + { + if (acl_rule.counter.packets_enabled) + { + const auto &pkt_stats_id_it = aclCounterColoredPacketsStatsIdMap.find(fvField(pc)); + if (pkt_stats_id_it == aclCounterColoredPacketsStatsIdMap.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid meter attribute " << pkt_stats_id_it->first << " for packet color in ACL rule " + << QuotedVar(acl_rule.db_key); + } + counter_stats_ids.push_back(pkt_stats_id_it->second); + } + if (acl_rule.counter.bytes_enabled) + { + const auto &byte_stats_id_it = aclCounterColoredBytesStatsIdMap.find(fvField(pc)); + if (byte_stats_id_it == aclCounterColoredBytesStatsIdMap.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid meter attribute " << byte_stats_id_it->first << " for packet color in ACL rule " + << QuotedVar(acl_rule.db_key); + } + counter_stats_ids.push_back(byte_stats_id_it->second); + } + } + std::vector meter_stats(counter_stats_ids.size()); + CHECK_ERROR_AND_LOG_AND_RETURN(sai_policer_api->get_policer_stats( + acl_rule.meter.meter_oid, static_cast(counter_stats_ids.size()), + counter_stats_ids.data(), meter_stats.data()), + "Failed to get meter stats for ACL rule " << QuotedVar(acl_rule.db_key)); + for (size_t i = 0; i < counter_stats_ids.size(); i++) + { + counter_stats_values.push_back(swss::FieldValueTuple{aclCounterStatsIdNameMap.at(counter_stats_ids[i]), + std::to_string(meter_stats[i])}); + } + } + else + { + // Query general packets/bytes stats by ACL counter object id. + std::vector counter_attrs; + sai_attribute_t counter_attr; + if (acl_rule.counter.packets_enabled) + { + counter_attr.id = SAI_ACL_COUNTER_ATTR_PACKETS; + counter_attrs.push_back(counter_attr); + } + if (acl_rule.counter.bytes_enabled) + { + counter_attr.id = SAI_ACL_COUNTER_ATTR_BYTES; + counter_attrs.push_back(counter_attr); + } + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_acl_api->get_acl_counter_attribute(acl_rule.counter.counter_oid, + static_cast(counter_attrs.size()), counter_attrs.data()), + "Failed to get counters stats for " << QuotedVar(acl_rule.acl_table_name)); + for (const auto &counter_attr : counter_attrs) + { + if (counter_attr.id == SAI_ACL_COUNTER_ATTR_PACKETS) + { + counter_stats_values.push_back( + swss::FieldValueTuple{P4_COUNTER_STATS_PACKETS, std::to_string(counter_attr.value.u64)}); + } + if (counter_attr.id == SAI_ACL_COUNTER_ATTR_BYTES) + { + counter_stats_values.push_back( + swss::FieldValueTuple{P4_COUNTER_STATS_BYTES, std::to_string(counter_attr.value.u64)}); + } + } + } + // Set field value tuples for counters stats in COUNTERS_DB + m_countersTable->set(acl_rule.db_key, counter_stats_values); + return ReturnCode(); +} + +ReturnCode AclRuleManager::setMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule, + const std::string &ip_type_bit_type) +{ + try + { + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS: { + const auto &ports = tokenize(attr_value, kPortsDelimiter); + if (ports.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "IN_PORTS are emtpy."; + } + for (const auto &alias : ports) + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(alias); + } + acl_rule->in_ports.push_back(alias); + acl_rule->in_ports_oids.push_back(port.m_port_id); + } + value->aclfield.data.objlist.count = static_cast(acl_rule->in_ports_oids.size()); + value->aclfield.data.objlist.list = acl_rule->in_ports_oids.data(); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS: { + const auto &ports = tokenize(attr_value, kPortsDelimiter); + if (ports.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "OUT_PORTS are emtpy."; + } + for (const auto &alias : ports) + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(alias); + } + acl_rule->out_ports.push_back(alias); + acl_rule->out_ports_oids.push_back(port.m_port_id); + } + value->aclfield.data.objlist.count = static_cast(acl_rule->out_ports_oids.size()); + value->aclfield.data.objlist.list = acl_rule->out_ports_oids.data(); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT: { + Port port; + if (!gPortsOrch->getPort(attr_value, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(attr_value); + } + value->aclfield.data.oid = port.m_port_id; + acl_rule->in_ports.push_back(attr_value); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT: { + Port port; + if (!gPortsOrch->getPort(attr_value, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(attr_value); + } + value->aclfield.data.oid = port.m_port_id; + acl_rule->out_ports.push_back(attr_value); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE: { + if (!setMatchFieldIpType(attr_value, value, ip_type_bit_type)) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to set IP_TYPE with value " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS: + case SAI_ACL_ENTRY_ATTR_FIELD_DSCP: { + // Support both exact value match and value/mask match + const auto &flag_data = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u8 = to_uint(trim(flag_data[0]), 0, 0x3F); + + if (flag_data.size() == 2) + { + value->aclfield.mask.u8 = to_uint(trim(flag_data[1]), 0, 0x3F); + } + else + { + value->aclfield.mask.u8 = 0x3F; + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_L4_DST_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION: + case SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT: { + const std::vector &value_and_mask = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u16 = to_uint(trim(value_and_mask[0])); + if (value_and_mask.size() > 1) + { + value->aclfield.mask.u16 = to_uint(trim(value_and_mask[1])); + } + else + { + value->aclfield.mask.u16 = 0xFFFF; + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IP: + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_IP: { + const auto &tokenized_ip = tokenize(attr_value, kDataMaskDelimiter); + if (tokenized_ip.size() == 2) + { + // data & mask + swss::IpAddress ip_data(trim(tokenized_ip[0])); + if (!ip_data.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP data type should be v4 type: " << QuotedVar(attr_value); + } + swss::IpAddress ip_mask(trim(tokenized_ip[1])); + if (!ip_mask.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP mask type should be v4 type: " << QuotedVar(attr_value); + } + value->aclfield.data.ip4 = ip_data.getV4Addr(); + value->aclfield.mask.ip4 = ip_mask.getV4Addr(); + } + else + { + // LPM annotated value + swss::IpPrefix ip_prefix(trim(attr_value)); + if (!ip_prefix.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP type should be v6 type: " << QuotedVar(attr_value); + } + value->aclfield.data.ip4 = ip_prefix.getIp().getV4Addr(); + value->aclfield.mask.ip4 = ip_prefix.getMask().getV4Addr(); + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IPV6: + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6: { + const auto &tokenized_ip = tokenize(attr_value, kDataMaskDelimiter); + if (tokenized_ip.size() == 2) + { + // data & mask + swss::IpAddress ip_data(trim(tokenized_ip[0])); + if (ip_data.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP data type should be v6 type: " << QuotedVar(attr_value); + } + swss::IpAddress ip_mask(trim(tokenized_ip[1])); + if (ip_mask.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP mask type should be v6 type: " << QuotedVar(attr_value); + } + memcpy(value->aclfield.data.ip6, ip_data.getV6Addr(), sizeof(sai_ip6_t)); + memcpy(value->aclfield.mask.ip6, ip_mask.getV6Addr(), sizeof(sai_ip6_t)); + } + else + { + // LPM annotated value + swss::IpPrefix ip_prefix(trim(attr_value)); + if (ip_prefix.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP type should be v6 type: " << QuotedVar(attr_value); + } + memcpy(value->aclfield.data.ip6, ip_prefix.getIp().getV6Addr(), sizeof(sai_ip6_t)); + memcpy(value->aclfield.mask.ip6, ip_prefix.getMask().getV6Addr(), sizeof(sai_ip6_t)); + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC: { + const std::vector mask_and_value = tokenize(attr_value, kDataMaskDelimiter); + swss::MacAddress mac(trim(mask_and_value[0])); + memcpy(value->aclfield.data.mac, mac.getMac(), sizeof(sai_mac_t)); + if (mask_and_value.size() > 1) + { + swss::MacAddress mask(trim(mask_and_value[1])); + memcpy(value->aclfield.mask.mac, mask.getMac(), sizeof(sai_mac_t)); + } + else + { + const sai_mac_t mac_mask = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + memcpy(value->aclfield.mask.mac, mac_mask, sizeof(sai_mac_t)); + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_TC: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMP_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMP_CODE: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_CODE: + case SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_CFI: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_CFI: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_PROTOCOL: + case SAI_ACL_ENTRY_ATTR_FIELD_ECN: + case SAI_ACL_ENTRY_ATTR_FIELD_TTL: + case SAI_ACL_ENTRY_ATTR_FIELD_TOS: + case SAI_ACL_ENTRY_ATTR_FIELD_IPV6_NEXT_HEADER: { + const std::vector &value_and_mask = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u8 = to_uint(trim(value_and_mask[0])); + if (value_and_mask.size() > 1) + { + value->aclfield.mask.u8 = to_uint(trim(value_and_mask[1])); + } + else + { + value->aclfield.mask.u8 = 0xFF; + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI: + case SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL: { + const std::vector &value_and_mask = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u32 = to_uint(trim(value_and_mask[0])); + if (value_and_mask.size() > 1) + { + value->aclfield.mask.u32 = to_uint(trim(value_and_mask[1])); + } + else + { + value->aclfield.mask.u32 = 0xFFFFFFFF; + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG: { + const auto &ip_frag_it = aclIpFragLookup.find(attr_value); + if (ip_frag_it == aclIpFragLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid IP frag " << QuotedVar(attr_value); + } + value->aclfield.data.u32 = ip_frag_it->second; + value->aclfield.mask.u32 = 0xFFFFFFFF; + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN: { + const auto &packet_vlan_it = aclPacketVlanLookup.find(attr_value); + if (packet_vlan_it == aclPacketVlanLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid Packet VLAN " << QuotedVar(attr_value); + } + value->aclfield.data.u32 = packet_vlan_it->second; + value->aclfield.mask.u32 = 0xFFFFFFFF; + break; + } + default: { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL match field " << attr_name << " is not supported."; + } + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to parse match attribute " << attr_name << " value: " << QuotedVar(attr_value); + } + value->aclfield.enable = true; + return ReturnCode(); +} + +ReturnCode AclRuleManager::getRedirectActionPortOid(const std::string &target, sai_object_id_t *rediect_oid) +{ + // Try to parse physical port and LAG first + Port port; + if (gPortsOrch->getPort(target, port)) + { + if (port.m_type == Port::PHY) + { + *rediect_oid = port.m_port_id; + return ReturnCode(); + } + else if (port.m_type == Port::LAG) + { + *rediect_oid = port.m_lag_id; + return ReturnCode(); + } + else + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Wrong port type for REDIRECT action. Only " + "physical ports and LAG ports are supported"); + } + } + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Port " << QuotedVar(target) << " not found."; +} + +ReturnCode AclRuleManager::getRedirectActionNextHopOid(const std::string &target, sai_object_id_t *rediect_oid) +{ + // Try to get nexthop object id + const auto &next_hop_key = KeyGenerator::generateNextHopKey(target); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, rediect_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL Redirect action target next hop ip: " << QuotedVar(target) + << " doesn't exist on the switch"); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::setAllMatchFieldValues(const P4AclRuleAppDbEntry &app_db_entry, + const P4AclTableDefinition *acl_table, P4AclRule &acl_rule) +{ + for (const auto &match_fv : app_db_entry.match_fvs) + { + const auto &match_field = fvField(match_fv); + const auto &match_value = fvValue(match_fv); + ReturnCode set_match_rc; + // Set UDF fields + auto udf_fields_it = acl_table->udf_fields_lookup.find(match_field); + if (udf_fields_it != acl_table->udf_fields_lookup.end()) + { + // Bytes Offset to extract Hex value from match_value string + uint16_t bytes_offset = 0; + for (const auto &udf_field : udf_fields_it->second) + { + auto udf_group_index_it = acl_table->udf_group_attr_index_lookup.find(udf_field.group_id); + if (udf_group_index_it == acl_table->udf_group_attr_index_lookup.end()) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL( + "No UDF group found in ACL table definition with id:" << QuotedVar(udf_field.group_id)); + } + set_match_rc = setUdfMatchValue( + udf_field, match_value, + &acl_rule.match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN + udf_group_index_it->second], + &acl_rule + .udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN + udf_group_index_it->second], + bytes_offset); + if (!set_match_rc.ok()) + { + set_match_rc.prepend("Invalid ACL rule match field " + QuotedVar(match_field) + ": " + + QuotedVar(match_value) + " to add: "); + return set_match_rc; + } + bytes_offset = (uint16_t)(bytes_offset + udf_field.length); + } + continue; + } + // Set Composite SAI fields + auto composite_sai_match_field_it = acl_table->composite_sai_match_fields_lookup.find(match_field); + if (composite_sai_match_field_it != acl_table->composite_sai_match_fields_lookup.end()) + { + // Handle composite SAI match fields + for (const auto &sai_match_field : composite_sai_match_field_it->second) + { + set_match_rc = setCompositeSaiMatchValue(sai_match_field.entry_attr, match_value, + &acl_rule.match_fvs[sai_match_field.entry_attr]); + if (!set_match_rc.ok()) + { + set_match_rc.prepend("Invalid ACL rule match field " + QuotedVar(match_field) + ": " + + QuotedVar(match_value) + " to add: "); + return set_match_rc; + } + } + continue; + } + auto sai_match_field_it = acl_table->sai_match_field_lookup.find(match_field); + if (sai_match_field_it == acl_table->sai_match_field_lookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL rule match field " << QuotedVar(match_field) << ": " << QuotedVar(match_value) + << " is an invalid ACL rule attribute"; + } + auto &sai_match_field = sai_match_field_it->second; + if (sai_match_field.entry_attr == SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE && + acl_table->ip_type_bit_type_lookup.find(sai_match_field_it->first) != + acl_table->ip_type_bit_type_lookup.end()) + { + set_match_rc = + setMatchValue(sai_match_field.entry_attr, match_value, &acl_rule.match_fvs[sai_match_field.entry_attr], + &acl_rule, acl_table->ip_type_bit_type_lookup.at(sai_match_field_it->first)); + } + else + { + set_match_rc = setMatchValue(sai_match_field.entry_attr, match_value, + &acl_rule.match_fvs[sai_match_field.entry_attr], &acl_rule); + } + if (!set_match_rc.ok()) + { + set_match_rc.prepend("Invalid ACL rule match field " + QuotedVar(match_field) + ": " + + QuotedVar(match_value) + " to add: "); + return set_match_rc; + } + } + if (!acl_table->ip_type_bit_type_lookup.empty() && + acl_rule.match_fvs.find(SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE) == acl_rule.match_fvs.end()) + { + // Wildcard match on ip type bits + sai_attribute_value_t ip_type_attr; + ip_type_attr.aclfield.data.u32 = SAI_ACL_IP_TYPE_ANY; + ip_type_attr.aclfield.mask.u32 = 0xFFFFFFFF; + ip_type_attr.aclfield.enable = true; + acl_rule.match_fvs.insert({SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE, ip_type_attr}); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::setAllActionFieldValues(const P4AclRuleAppDbEntry &app_db_entry, + const P4AclTableDefinition *acl_table, P4AclRule &acl_rule) +{ + const auto &action_param_list_it = acl_table->rule_action_field_lookup.find(app_db_entry.action); + if (action_param_list_it == acl_table->rule_action_field_lookup.end()) + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid P4 ACL action " << QuotedVar(app_db_entry.action); + return status; + } + SaiActionWithParam sai_action_param; + for (const auto &action_param : action_param_list_it->second) + { + sai_action_param.action = action_param.action; + sai_action_param.param_name = action_param.param_name; + sai_action_param.param_value = action_param.param_value; + if (!action_param.param_name.empty()) + { + const auto ¶m_value_it = app_db_entry.action_param_fvs.find(action_param.param_name); + if (param_value_it == app_db_entry.action_param_fvs.end()) + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "No action param found for action " << action_param.action; + return status; + } + if (!param_value_it->second.empty()) + { + sai_action_param.param_value = param_value_it->second; + } + } + auto set_action_rc = setActionValue(sai_action_param.action, sai_action_param.param_value, + &acl_rule.action_fvs[sai_action_param.action], &acl_rule); + if (!set_action_rc.ok()) + { + return set_action_rc; + } + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::setActionValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule) +{ + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION: { + const auto it = aclPacketActionLookup.find(attr_value); + if (it != aclPacketActionLookup.end()) + { + value->aclaction.parameter.s32 = it->second; + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL packet action " << QuotedVar(attr_value) << " for " + << QuotedVar(acl_rule->acl_table_name); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT: { + sai_object_id_t redirect_oid; + if (getRedirectActionPortOid(attr_value, &redirect_oid).ok()) + { + value->aclaction.parameter.oid = redirect_oid; + break; + } + RETURN_IF_ERROR(getRedirectActionNextHopOid(attr_value, &redirect_oid)); + value->aclaction.parameter.oid = redirect_oid; + acl_rule->action_redirect_nexthop_key = KeyGenerator::generateNextHopKey(attr_value); + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP: { + try + { + swss::IpAddress ip(attr_value); + if (ip.isV4()) + { + value->aclaction.parameter.ip4 = ip.getV4Addr(); + } + else + { + memcpy(value->aclaction.parameter.ip6, ip.getV6Addr(), sizeof(sai_ip6_t)); + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IP address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS: + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS: { + sai_object_id_t mirror_session_oid; + std::string key = KeyGenerator::generateMirrorSessionKey(attr_value); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, key, &mirror_session_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Mirror session " << QuotedVar(attr_value) << " does not exist for " + << QuotedVar(acl_rule->acl_table_name); + } + auto &mirror_session = acl_rule->action_mirror_sessions[attr_name]; + mirror_session.name = attr_value; + mirror_session.key = key; + mirror_session.oid = mirror_session_oid; + value->aclaction.parameter.objlist.list = &mirror_session.oid; + value->aclaction.parameter.objlist.count = 1; + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR: { + const auto &it = aclPacketColorLookup.find(attr_value); + if (it == aclPacketColorLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL packet color " << QuotedVar(attr_value) << " in action for " + << QuotedVar(acl_rule->acl_table_name); + } + value->aclaction.parameter.s32 = it->second; + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC: { + try + { + swss::MacAddress mac(attr_value); + memcpy(value->aclaction.parameter.mac, mac.getMac(), sizeof(sai_mac_t)); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect MAC address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP: { + try + { + swss::IpAddress ip(attr_value); + if (!ip.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IPv4 address but got " + << QuotedVar(attr_value); + } + value->aclaction.parameter.ip4 = ip.getV4Addr(); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IP address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6: { + try + { + swss::IpAddress ip(attr_value); + if (ip.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IPv6 address but got " + << QuotedVar(attr_value); + } + memcpy(value->aclaction.parameter.ip6, ip.getV6Addr(), sizeof(sai_ip6_t)); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IP address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID: { + try + { + uint32_t queue_num = to_uint(attr_value); + if (queue_num < 1 || queue_num > m_userDefinedTraps.size()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid CPU queue number " << QuotedVar(attr_value) << " for " + << QuotedVar(acl_rule->acl_table_name) + << ". Queue number should >= 1 and <= " << m_userDefinedTraps.size(); + } + value->aclaction.parameter.oid = m_userDefinedTraps[queue_num - 1].user_defined_trap; + acl_rule->action_qos_queue_num = queue_num; + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_TC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI: { + try + { + value->aclaction.parameter.u8 = to_uint(attr_value); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID: { + try + { + value->aclaction.parameter.u32 = to_uint(attr_value); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT: { + try + { + value->aclaction.parameter.u16 = to_uint(attr_value); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_FLOOD: + case SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN: { + // parameter is not needed + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF: { + if (!attr_value.empty() && !m_vrfOrch->isVRFexists(attr_value)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "No VRF found with name " << QuotedVar(attr_value) + << " for " << QuotedVar(acl_rule->acl_table_name); + } + value->aclaction.parameter.oid = m_vrfOrch->getVRFid(attr_value); + break; + } + default: { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL action " << attr_name << " for " << QuotedVar(acl_rule->acl_table_name); + } + } + value->aclaction.enable = true; + return ReturnCode(); +} + +ReturnCode AclRuleManager::setMeterValue(const P4AclTableDefinition *acl_table, const P4AclRuleAppDbEntry &app_db_entry, + P4AclMeter &acl_meter) +{ + if (app_db_entry.meter.enabled) + { + acl_meter.cir = app_db_entry.meter.cir; + acl_meter.cburst = app_db_entry.meter.cburst; + acl_meter.pir = app_db_entry.meter.pir; + acl_meter.pburst = app_db_entry.meter.pburst; + acl_meter.mode = SAI_POLICER_MODE_TR_TCM; + if (acl_table->meter_unit == P4_METER_UNIT_PACKETS) + { + acl_meter.type = SAI_METER_TYPE_PACKETS; + } + else if (acl_table->meter_unit == P4_METER_UNIT_BYTES) + { + acl_meter.type = SAI_METER_TYPE_BYTES; + } + else + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL meter type " << QuotedVar(acl_table->meter_unit); + return status; + } + acl_meter.enabled = true; + } + const auto &action_color_it = acl_table->rule_packet_action_color_lookup.find(app_db_entry.action); + if (action_color_it != acl_table->rule_packet_action_color_lookup.end() && !action_color_it->second.empty()) + { + acl_meter.packet_color_actions = action_color_it->second; + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::createAclRule(P4AclRule &acl_rule) +{ + SWSS_LOG_ENTER(); + std::vector acl_entry_attrs; + sai_attribute_t acl_entry_attr; + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; + acl_entry_attr.value.oid = acl_rule.acl_table_oid; + acl_entry_attrs.push_back(acl_entry_attr); + + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; + acl_entry_attr.value.u32 = acl_rule.priority; + acl_entry_attrs.push_back(acl_entry_attr); + + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ADMIN_STATE; + acl_entry_attr.value.booldata = true; + acl_entry_attrs.push_back(acl_entry_attr); + + // Add matches + for (const auto &match_fv : acl_rule.match_fvs) + { + acl_entry_attr.id = fvField(match_fv); + acl_entry_attr.value = fvValue(match_fv); + acl_entry_attrs.push_back(acl_entry_attr); + } + + // Add actions + for (const auto &action_fv : acl_rule.action_fvs) + { + acl_entry_attr.id = fvField(action_fv); + acl_entry_attr.value = fvValue(action_fv); + acl_entry_attrs.push_back(acl_entry_attr); + } + + // Track if the entry creats a new counter or meter + bool created_meter = false; + bool created_counter = false; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule.acl_table_name, acl_rule.acl_rule_key); + + // Add meter + if (acl_rule.meter.enabled || !acl_rule.meter.packet_color_actions.empty()) + { + if (acl_rule.meter.meter_oid == SAI_NULL_OBJECT_ID) + { + auto status = createAclMeter(acl_rule.meter, table_name_and_rule_key, &acl_rule.meter.meter_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + return status; + } + created_meter = true; + } + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; + acl_entry_attr.value.aclaction.parameter.oid = acl_rule.meter.meter_oid; + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attrs.push_back(acl_entry_attr); + } + + // Add counter + if (acl_rule.counter.packets_enabled || acl_rule.counter.bytes_enabled) + { + if (acl_rule.counter.counter_oid == SAI_NULL_OBJECT_ID) + { + auto status = createAclCounter(acl_rule.acl_table_name, table_name_and_rule_key, acl_rule.counter, + &acl_rule.counter.counter_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL counter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + if (created_meter) + { + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL meter in recovery."); + } + } + return status; + } + created_counter = true; + } + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_COUNTER; + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attr.value.aclaction.parameter.oid = acl_rule.counter.counter_oid; + acl_entry_attrs.push_back(acl_entry_attr); + } + + auto sai_status = sai_acl_api->create_acl_entry(&acl_rule.acl_entry_oid, gSwitchId, + (uint32_t)acl_entry_attrs.size(), acl_entry_attrs.data()); + if (sai_status != SAI_STATUS_SUCCESS) + { + ReturnCode status = ReturnCode(sai_status) + << "Failed to create ACL entry in table " << QuotedVar(acl_rule.acl_table_name); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", status.message().c_str(), sai_serialize_status(sai_status).c_str()); + if (created_meter) + { + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL meter in recovery."); + } + } + if (created_counter) + { + auto rc = removeAclCounter(acl_rule.acl_table_name, table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL counter in recovery."); + } + } + return status; + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::updateAclRule(const P4AclRule &acl_rule, const P4AclRule &old_acl_rule, + std::vector &acl_entry_attrs, + std::vector &rollback_attrs) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t acl_entry_attr; + std::set actions_to_reset; + for (const auto &old_action_fv : old_acl_rule.action_fvs) + { + actions_to_reset.insert(fvField(old_action_fv)); + } + + for (const auto &action_fv : acl_rule.action_fvs) + { + const auto &it = old_acl_rule.action_fvs.find(fvField(action_fv)); + if (it == old_acl_rule.action_fvs.end()) + { + acl_entry_attr.id = fvField(action_fv); + acl_entry_attr.value = fvValue(action_fv); + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = false; + rollback_attrs.push_back(acl_entry_attr); + } + else if (isDiffActionFieldValue(fvField(action_fv), fvValue(action_fv), it->second, acl_rule, old_acl_rule)) + { + acl_entry_attr.id = fvField(action_fv); + acl_entry_attr.value = fvValue(action_fv); + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value = it->second; + rollback_attrs.push_back(acl_entry_attr); + } + if (it != old_acl_rule.action_fvs.end()) + { + actions_to_reset.erase(fvField(action_fv)); + } + } + + for (const auto &action : actions_to_reset) + { + acl_entry_attr.id = action; + acl_entry_attr.value = old_acl_rule.action_fvs.at(action); + acl_entry_attr.value.aclaction.enable = false; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = true; + rollback_attrs.push_back(acl_entry_attr); + } + + ReturnCode status; + int i; + for (i = 0; i < static_cast(acl_entry_attrs.size()); ++i) + { + status = ReturnCode(sai_acl_api->set_acl_entry_attribute(old_acl_rule.acl_entry_oid, &acl_entry_attrs[i])); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update ACL rule attributes: %s", status.message().c_str()); + break; + } + } + if (!status.ok()) + { + for (--i; i >= 0; --i) + { + auto sai_status = sai_acl_api->set_acl_entry_attribute(old_acl_rule.acl_entry_oid, &rollback_attrs[i]); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set ACL rule attribute. SAI_STATUS: %s", + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to set ACL rule attribute in recovery."); + } + } + return status; + } + + // Clear old ACL rule dependent refcount and update refcount in new rule + if (!old_acl_rule.action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, old_acl_rule.action_redirect_nexthop_key); + } + if (!acl_rule.action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, acl_rule.action_redirect_nexthop_key); + } + for (const auto &mirror_session : old_acl_rule.action_mirror_sessions) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + for (const auto &mirror_session : acl_rule.action_mirror_sessions) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + auto old_set_vrf_action_it = old_acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (old_set_vrf_action_it != old_acl_rule.action_fvs.end()) + { + m_vrfOrch->decreaseVrfRefCount(old_set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_vrf_action_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (set_vrf_action_it != acl_rule.action_fvs.end()) + { + m_vrfOrch->increaseVrfRefCount(set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_user_trap_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (set_user_trap_it != acl_rule.action_fvs.end()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(acl_rule.action_qos_queue_num)); + } + auto old_set_user_trap_it = old_acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (old_set_user_trap_it != old_acl_rule.action_fvs.end()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(old_acl_rule.action_qos_queue_num)); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::removeAclRule(const std::string &acl_table_name, const std::string &acl_rule_key) +{ + auto *acl_rule = getAclRule(acl_table_name, acl_rule_key); + if (acl_rule == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "ACL rule with key " << QuotedVar(acl_rule_key) << " in table " + << QuotedVar(acl_table_name) << " does not exist"); + } + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_table_name, acl_rule_key); + // Check if there is anything referring to the next hop before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for ACL rule " + << QuotedVar(table_name_and_rule_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL rule " << QuotedVar(acl_rule_key) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->remove_acl_entry(acl_rule->acl_entry_oid), + "Failed to remove ACL rule with key " + << sai_serialize_object_id(acl_rule->acl_entry_oid) << " in table " + << QuotedVar(acl_table_name)); + bool deleted_meter = false; + if (acl_rule->meter.enabled || !acl_rule->meter.packet_color_actions.empty()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + auto status = removeAclMeter(table_name_and_rule_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL meter for rule with key %s in table %s.", + QuotedVar(acl_rule_key).c_str(), QuotedVar(acl_table_name).c_str()); + auto rc = createAclRule(*acl_rule); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL rule in recovery."); + } + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + return status; + } + acl_rule->meter.meter_oid = SAI_NULL_OBJECT_ID; + deleted_meter = true; + } + if (acl_rule->counter.packets_enabled || acl_rule->counter.bytes_enabled) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + auto status = removeAclCounter(acl_table_name, table_name_and_rule_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL counter for rule with key %s.", + QuotedVar(table_name_and_rule_key).c_str()); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + if (deleted_meter) + { + auto rc = createAclMeter(acl_rule->meter, table_name_and_rule_key, &acl_rule->meter.meter_oid); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL rule in recovery."); + return status; + } + } + auto rc = createAclRule(*acl_rule); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL rule in recovery."); + } + return status; + } + // Remove counter stats + m_countersTable->del(acl_rule->db_key); + } + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, acl_rule->acl_table_oid); + if (!acl_rule->action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, acl_rule->action_redirect_nexthop_key); + } + for (const auto &mirror_session : acl_rule->action_mirror_sessions) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + auto set_vrf_action_it = acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (set_vrf_action_it != acl_rule->action_fvs.end()) + { + m_vrfOrch->decreaseVrfRefCount(set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_user_trap_it = acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (set_user_trap_it != acl_rule->action_fvs.end()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(acl_rule->action_qos_queue_num)); + } + for (const auto &port_alias : acl_rule->in_ports) + { + gPortsOrch->decreasePortRefCount(port_alias); + } + for (const auto &port_alias : acl_rule->out_ports) + { + gPortsOrch->decreasePortRefCount(port_alias); + } + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + m_aclRuleTables[acl_table_name].erase(acl_rule_key); + return ReturnCode(); +} + +ReturnCode AclRuleManager::processAddRuleRequest(const std::string &acl_rule_key, + const P4AclRuleAppDbEntry &app_db_entry) +{ + P4AclRule acl_rule; + acl_rule.priority = app_db_entry.priority; + acl_rule.acl_rule_key = acl_rule_key; + acl_rule.p4_action = app_db_entry.action; + acl_rule.db_key = app_db_entry.db_key; + const auto *acl_table = gP4Orch->getAclTableManager()->getAclTable(app_db_entry.acl_table_name); + acl_rule.acl_table_oid = acl_table->table_oid; + acl_rule.acl_table_name = acl_table->acl_table_name; + + // Add match field values + LOG_AND_RETURN_IF_ERROR(setAllMatchFieldValues(app_db_entry, acl_table, acl_rule)); + + // Add action field values + auto status = setAllActionFieldValues(app_db_entry, acl_table, acl_rule); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to add action field values for ACL rule %s: %s", + QuotedVar(acl_rule.acl_rule_key).c_str(), status.message().c_str()); + return status; + } + + // Add meter + LOG_AND_RETURN_IF_ERROR(setMeterValue(acl_table, app_db_entry, acl_rule.meter)); + + // Add counter + if (!acl_table->counter_unit.empty()) + { + if (acl_table->counter_unit == P4_COUNTER_UNIT_PACKETS) + { + acl_rule.counter.packets_enabled = true; + } + else if (acl_table->counter_unit == P4_COUNTER_UNIT_BYTES) + { + acl_rule.counter.bytes_enabled = true; + } + else if (acl_table->counter_unit == P4_COUNTER_UNIT_BOTH) + { + acl_rule.counter.bytes_enabled = true; + acl_rule.counter.packets_enabled = true; + } + else + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL counter type " << QuotedVar(acl_table->counter_unit)); + } + } + status = createAclRule(acl_rule); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL rule with key %s in table %s", QuotedVar(acl_rule.acl_rule_key).c_str(), + QuotedVar(app_db_entry.acl_table_name).c_str()); + return status; + } + // ACL entry created in HW, update refcount + if (!acl_rule.action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, acl_rule.action_redirect_nexthop_key); + } + for (const auto &mirror_session : acl_rule.action_mirror_sessions) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + auto set_vrf_action_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (set_vrf_action_it != acl_rule.action_fvs.end()) + { + m_vrfOrch->increaseVrfRefCount(set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_user_trap_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (set_user_trap_it != acl_rule.action_fvs.end()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(acl_rule.action_qos_queue_num)); + } + for (const auto &port_alias : acl_rule.in_ports) + { + gPortsOrch->increasePortRefCount(port_alias); + } + for (const auto &port_alias : acl_rule.out_ports) + { + gPortsOrch->increasePortRefCount(port_alias); + } + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, acl_rule.acl_table_oid); + m_aclRuleTables[acl_rule.acl_table_name][acl_rule.acl_rule_key] = acl_rule; + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_rule.acl_table_name); + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule.acl_table_name, acl_rule.acl_rule_key); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, acl_rule.acl_entry_oid); + if (acl_rule.counter.packets_enabled || acl_rule.counter.bytes_enabled) + { + // Counter was created, increase ACL rule ref count + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + } + if (acl_rule.meter.enabled || !acl_rule.meter.packet_color_actions.empty()) + { + // Meter was created, increase ACL rule ref count + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + } + SWSS_LOG_NOTICE("Suceeded to create ACL rule %s : %s", QuotedVar(acl_rule.acl_rule_key).c_str(), + sai_serialize_object_id(acl_rule.acl_entry_oid).c_str()); + return status; +} + +ReturnCode AclRuleManager::processDeleteRuleRequest(const std::string &acl_table_name, const std::string &acl_rule_key) +{ + SWSS_LOG_ENTER(); + auto status = removeAclRule(acl_table_name, acl_rule_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL rule with key %s in table %s", QuotedVar(acl_rule_key).c_str(), + QuotedVar(acl_table_name).c_str()); + } + return status; +} + +ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &app_db_entry, + const P4AclRule &old_acl_rule) +{ + SWSS_LOG_ENTER(); + + P4AclRule acl_rule; + const auto *acl_table = gP4Orch->getAclTableManager()->getAclTable(app_db_entry.acl_table_name); + acl_rule.acl_table_oid = acl_table->table_oid; + acl_rule.acl_table_name = acl_table->acl_table_name; + acl_rule.db_key = app_db_entry.db_key; + + // Skip match field comparison because the acl_rule_key including match + // field value and priority should be the same with old one. + acl_rule.match_fvs = old_acl_rule.match_fvs; + acl_rule.in_ports = old_acl_rule.in_ports; + acl_rule.out_ports = old_acl_rule.out_ports; + acl_rule.priority = app_db_entry.priority; + acl_rule.acl_rule_key = old_acl_rule.acl_rule_key; + // Skip Counter comparison since the counter unit is defined in table + // definition + acl_rule.counter = old_acl_rule.counter; + + std::vector acl_entry_attrs; + std::vector rollback_attrs; + sai_attribute_t acl_entry_attr; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule.acl_table_name, acl_rule.acl_rule_key); + + // Update action field + acl_rule.p4_action = app_db_entry.action; + acl_rule.acl_entry_oid = old_acl_rule.acl_entry_oid; + auto set_actions_rc = setAllActionFieldValues(app_db_entry, acl_table, acl_rule); + if (!set_actions_rc.ok()) + { + SWSS_LOG_ERROR("Failed to add action field values for ACL rule %s: %s", + QuotedVar(acl_rule.acl_rule_key).c_str(), set_actions_rc.message().c_str()); + return set_actions_rc; + } + + // Update meter + bool remove_meter = false; + bool created_meter = false; + bool updated_meter = false; + LOG_AND_RETURN_IF_ERROR(setMeterValue(acl_table, app_db_entry, acl_rule.meter)); + if (old_acl_rule.meter.meter_oid == SAI_NULL_OBJECT_ID && + (acl_rule.meter.enabled || !acl_rule.meter.packet_color_actions.empty())) + { + // Create new meter + auto status = createAclMeter(acl_rule.meter, table_name_and_rule_key, &acl_rule.meter.meter_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + return status; + } + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + created_meter = true; + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attr.value.aclaction.parameter.oid = acl_rule.meter.meter_oid; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = false; + acl_entry_attr.value.aclaction.parameter.oid = SAI_NULL_OBJECT_ID; + rollback_attrs.push_back(acl_entry_attr); + } + else if (old_acl_rule.meter.meter_oid != SAI_NULL_OBJECT_ID && !acl_rule.meter.enabled && + acl_rule.meter.packet_color_actions.empty()) + { + // Remove old meter + remove_meter = true; + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; + acl_entry_attr.value.aclaction.enable = false; + acl_entry_attr.value.aclaction.parameter.oid = SAI_NULL_OBJECT_ID; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attr.value.aclaction.parameter.oid = old_acl_rule.meter.meter_oid; + rollback_attrs.push_back(acl_entry_attr); + } + else if (old_acl_rule.meter.meter_oid != SAI_NULL_OBJECT_ID) + { + // Update meter attributes + auto status = updateAclMeter(acl_rule.meter, old_acl_rule.meter); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + return status; + } + updated_meter = true; + acl_rule.meter.meter_oid = old_acl_rule.meter.meter_oid; + } + + auto status = updateAclRule(acl_rule, old_acl_rule, acl_entry_attrs, rollback_attrs); + if (status.ok()) + { + // Remove old meter. + if (remove_meter) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + for (const auto &entry_attr : rollback_attrs) + { + auto sai_status = sai_acl_api->set_acl_entry_attribute(old_acl_rule.acl_entry_oid, &entry_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set ACL rule attribute. SAI_STATUS: %s", + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to set ACL rule attribute in recovery."); + } + } + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + return rc; + } + } + } + else + { + SWSS_LOG_ERROR("Failed to update ACL rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + // Clean up + if (created_meter) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL meter in recovery."); + } + } + if (updated_meter) + { + auto rc = updateAclMeter(old_acl_rule.meter, acl_rule.meter); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to update ACL meter in recovery."); + } + } + return status; + } + + m_aclRuleTables[acl_rule.acl_table_name][acl_rule.acl_rule_key] = acl_rule; + return ReturnCode(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_rule_manager.h b/orchagent/p4orch/acl_rule_manager.h new file mode 100644 index 0000000000..cc00735d84 --- /dev/null +++ b/orchagent/p4orch/acl_rule_manager.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include +#include + +#include "copporch.h" +#include "orch.h" +#include "p4orch/acl_util.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" +#include "vrforch.h" + +extern "C" +{ +#include "sai.h" +} + +namespace p4orch +{ +namespace test +{ +class AclManagerTest; +} // namespace test + +class AclRuleManager : public ObjectManagerInterface +{ + public: + explicit AclRuleManager(P4OidMapper *p4oidMapper, VRFOrch *vrfOrch, CoppOrch *coppOrch, + ResponsePublisherInterface *publisher) + : m_p4OidMapper(p4oidMapper), m_vrfOrch(vrfOrch), m_publisher(publisher), m_coppOrch(coppOrch), + m_countersDb(std::make_unique("COUNTERS_DB", 0)), + m_countersTable(std::make_unique( + m_countersDb.get(), std::string(COUNTERS_TABLE) + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME)) + { + SWSS_LOG_ENTER(); + assert(m_p4OidMapper != nullptr); + } + virtual ~AclRuleManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + // Update counters stats for every rule in each ACL table in COUNTERS_DB, if + // counters are enabled in rules. + void doAclCounterStatsTask(); + + private: + // Deserializes an entry in a dynamically created ACL table. + ReturnCodeOr deserializeAclRuleAppDbEntry( + const std::string &acl_table_name, const std::string &key, + const std::vector &attributes); + + // Validate an ACL rule APP_DB entry. + ReturnCode validateAclRuleAppDbEntry(const P4AclRuleAppDbEntry &app_db_entry); + + // Get ACL rule by table name and rule key. Return nullptr if not found. + P4AclRule *getAclRule(const std::string &acl_table_name, const std::string &acl_rule_key); + + // Processes add operation for an ACL rule. + ReturnCode processAddRuleRequest(const std::string &acl_rule_key, const P4AclRuleAppDbEntry &app_db_entry); + + // Processes delete operation for an ACL rule. + ReturnCode processDeleteRuleRequest(const std::string &acl_table_name, const std::string &acl_rule_key); + + // Processes update operation for an ACL rule. + ReturnCode processUpdateRuleRequest(const P4AclRuleAppDbEntry &app_db_entry, const P4AclRule &old_acl_rule); + + // Set counters stats for an ACL rule in COUNTERS_DB. + ReturnCode setAclRuleCounterStats(const P4AclRule &acl_rule); + + // Create an ACL rule. + ReturnCode createAclRule(P4AclRule &acl_rule); + + // Create an ACL counter. + ReturnCode createAclCounter(const std::string &acl_table_name, const std::string &counter_key, + const P4AclCounter &p4_acl_counter, sai_object_id_t *counter_oid); + + // Create an ACL meter. + ReturnCode createAclMeter(const P4AclMeter &p4_acl_meter, const std::string &meter_key, sai_object_id_t *meter_oid); + + // Remove an ACL counter. + ReturnCode removeAclCounter(const std::string &acl_table_name, const std::string &counter_key); + + // Update ACL meter. + ReturnCode updateAclMeter(const P4AclMeter &new_acl_meter, const P4AclMeter &old_acl_meter); + + // Update ACL rule. + ReturnCode updateAclRule(const P4AclRule &new_acl_rule, const P4AclRule &old_acl_rule, + std::vector &acl_entry_attrs, + std::vector &rollback_attrs); + + // Remove an ACL meter. + ReturnCode removeAclMeter(const std::string &meter_key); + + // Remove the ACL rule by key in the given ACL table. + ReturnCode removeAclRule(const std::string &acl_table_name, const std::string &acl_rule_key); + + // Set Meter value in ACL rule. + ReturnCode setMeterValue(const P4AclTableDefinition *acl_table, const P4AclRuleAppDbEntry &app_db_entry, + P4AclMeter &acl_meter); + + // Validate and set all match attributes in an ACL rule. + ReturnCode setAllMatchFieldValues(const P4AclRuleAppDbEntry &app_db_entry, const P4AclTableDefinition *acl_table, + P4AclRule &acl_rules); + + // Validate and set all action attributes in an ACL rule. + ReturnCode setAllActionFieldValues(const P4AclRuleAppDbEntry &app_db_entry, const P4AclTableDefinition *acl_table, + P4AclRule &acl_rule); + + // Validate and set a match attribute in an ACL rule. + ReturnCode setMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule, + const std::string &ip_type_bit_type = EMPTY_STRING); + + // Validate and set an action attribute in an ACL rule. + ReturnCode setActionValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule); + + // Get port object id by name for redirect action. + ReturnCode getRedirectActionPortOid(const std::string &target, sai_object_id_t *rediect_oid); + + // Get next hop object id by name for redirect action. + ReturnCode getRedirectActionNextHopOid(const std::string &target, sai_object_id_t *rediect_oid); + + // Create user defined trap for each cpu queue/trap group and program user + // defined traps in hostif. Save the user defined trap oids in m_p4OidMapper + // and default ref count is 1. + ReturnCode setUpUserDefinedTraps(); + + // Clean up user defined traps created for cpu queues. Callers need to make + // sure ref count on user defined traps in m_userDefinedTraps are ones before + // clean up. + ReturnCode cleanUpUserDefinedTraps(); + + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + P4AclRuleTables m_aclRuleTables; + VRFOrch *m_vrfOrch; + CoppOrch *m_coppOrch; + std::deque m_entries; + std::unique_ptr m_countersDb; + std::unique_ptr m_countersTable; + std::vector m_userDefinedTraps; + + friend class AclTableManager; + friend class p4orch::test::AclManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_table_manager.cpp b/orchagent/p4orch/acl_table_manager.cpp new file mode 100644 index 0000000000..456c2f04d2 --- /dev/null +++ b/orchagent/p4orch/acl_table_manager.cpp @@ -0,0 +1,901 @@ +#include "p4orch/acl_table_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "sai_serialize.h" +#include "switchorch.h" +#include "tokenize.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_acl_api_t *sai_acl_api; +extern sai_udf_api_t *sai_udf_api; +extern sai_switch_api_t *sai_switch_api; +extern CrmOrch *gCrmOrch; +extern P4Orch *gP4Orch; +extern SwitchOrch *gSwitchOrch; +extern int gBatchSize; + +namespace p4orch +{ + +AclTableManager::AclTableManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + : m_p4OidMapper(p4oidMapper), m_publisher(publisher) +{ + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + // Create the default UDF match + auto status = createDefaultUdfMatch(); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL UDF default match : %s", status.message().c_str()); + } +} + +AclTableManager::~AclTableManager() +{ + auto status = removeDefaultUdfMatch(); + if (!status.ok()) + { + status.prepend("Failed to remove default UDF match: "); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } +} + +void AclTableManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void AclTableManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + SWSS_LOG_NOTICE("P4AclTableManager drain tuple for table %s", QuotedVar(table_name).c_str()); + if (table_name != APP_P4RT_ACL_TABLE_DEFINITION_NAME) + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid table " << QuotedVar(table_name); + SWSS_LOG_ERROR("%s", status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto app_db_entry_or = deserializeAclTableDefinitionAppDbEntry(db_key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + status = validateAclTableDefinitionAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for ACL definition APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto *acl_table_definition = getAclTable(app_db_entry.acl_table_name); + if (acl_table_definition == nullptr) + { + SWSS_LOG_NOTICE("ACL table SET %s", app_db_entry.acl_table_name.c_str()); + status = processAddTableRequest(app_db_entry); + } + else + { + // All attributes in sai_acl_table_attr_t are CREATE_ONLY. + status = ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Unable to update ACL table definition in APP DB entry with key " + << QuotedVar(table_name + ":" + db_key) + << " : All attributes in sai_acl_table_attr_t are CREATE_ONLY."; + } + } + else if (operation == DEL_COMMAND) + { + status = processDeleteTableRequest(db_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + } + if (!status.ok()) + { + SWSS_LOG_ERROR("Processed DEFINITION entry status: %s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +ReturnCodeOr AclTableManager::deserializeAclTableDefinitionAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4AclTableDefinitionAppDbEntry app_db_entry = {}; + app_db_entry.acl_table_name = key; + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + SWSS_LOG_INFO("ACL table definition attr string %s : %s\n", QuotedVar(field).c_str(), QuotedVar(value).c_str()); + if (field == kStage) + { + app_db_entry.stage = value; + continue; + } + else if (field == kPriority) + { + int priority = std::stoi(value); + if (priority < 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL table priority " << QuotedVar(value); + } + app_db_entry.priority = static_cast(priority); + continue; + } + else if (field == kSize) + { + int size = std::stoi(value); + if (size < 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid ACL table size " << QuotedVar(value); + } + app_db_entry.size = static_cast(size); + continue; + } + else if (field == kMeterUnit) + { + app_db_entry.meter_unit = value; + continue; + } + else if (field == kCounterUnit) + { + app_db_entry.counter_unit = value; + continue; + } + std::vector tokenized_field = swss::tokenize(field, kFieldDelimiter); + if (tokenized_field.size() <= 1) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown ACL table definition field string " << QuotedVar(field); + } + const auto &p4_field = tokenized_field[1]; + if (tokenized_field[0] == kMatchPrefix) + { + app_db_entry.match_field_lookup[p4_field] = value; + } + else if (tokenized_field[0] == kAction) + { + if (!parseAclTableAppDbActionField(value, &app_db_entry.action_field_lookup[p4_field], + &app_db_entry.packet_action_color_lookup[p4_field])) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Error parsing ACL table definition action " << QuotedVar(field) << ":" << QuotedVar(value); + } + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown ACL table definition field string " << QuotedVar(field); + } + } + return app_db_entry; +} + +ReturnCode AclTableManager::validateAclTableDefinitionAppDbEntry(const P4AclTableDefinitionAppDbEntry &app_db_entry) +{ + // Perform generic APP DB entry validations. Operation specific + // validations will be done by the respective request process methods. + if (!app_db_entry.meter_unit.empty() && app_db_entry.meter_unit != P4_METER_UNIT_BYTES && + app_db_entry.meter_unit != P4_METER_UNIT_PACKETS) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table meter unit " << QuotedVar(app_db_entry.meter_unit) << " is invalid"; + } + if (!app_db_entry.counter_unit.empty() && app_db_entry.counter_unit != P4_COUNTER_UNIT_BYTES && + app_db_entry.counter_unit != P4_COUNTER_UNIT_PACKETS && app_db_entry.counter_unit != P4_COUNTER_UNIT_BOTH) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table counter unit " << QuotedVar(app_db_entry.counter_unit) << " is invalid"; + } + return ReturnCode(); +} + +P4AclTableDefinition *AclTableManager::getAclTable(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + if (m_aclTableDefinitions.find(acl_table_name) == m_aclTableDefinitions.end()) + return nullptr; + return &m_aclTableDefinitions[acl_table_name]; +} + +ReturnCode AclTableManager::processAddTableRequest(const P4AclTableDefinitionAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + auto stage_it = aclStageLookup.find(app_db_entry.stage); + sai_acl_stage_t stage; + if (stage_it != aclStageLookup.end()) + { + stage = stage_it->second; + } + else + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table stage " << QuotedVar(app_db_entry.stage) << " is invalid"); + } + + if (gSwitchOrch->getAclGroupOidsBindingToSwitch().empty()) + { + // Create default ACL groups binding to switch + gSwitchOrch->initAclGroupsBindToSwitch(); + } + + P4AclTableDefinition acl_table_definition(app_db_entry.acl_table_name, stage, app_db_entry.priority, + app_db_entry.size, app_db_entry.meter_unit, app_db_entry.counter_unit); + + auto group_it = gSwitchOrch->getAclGroupOidsBindingToSwitch().find(acl_table_definition.stage); + if (group_it == gSwitchOrch->getAclGroupOidsBindingToSwitch().end()) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to find ACL group binding to switch at stage " + << acl_table_definition.stage); + } + acl_table_definition.group_oid = group_it->second; + + auto build_match_rc = + buildAclTableDefinitionMatchFieldValues(app_db_entry.match_field_lookup, &acl_table_definition); + + LOG_AND_RETURN_IF_ERROR( + build_match_rc.prepend("Failed to build ACL table definition match fields with table name " + + QuotedVar(app_db_entry.acl_table_name) + ": ")); + + auto build_action_rc = buildAclTableDefinitionActionFieldValues(app_db_entry.action_field_lookup, + &acl_table_definition.rule_action_field_lookup); + + LOG_AND_RETURN_IF_ERROR( + build_action_rc.prepend("Failed to build ACL table definition action fields with table name " + + QuotedVar(app_db_entry.acl_table_name) + ": ")); + + if (gP4Orch->getAclRuleManager()->m_userDefinedTraps.empty() && + isSetUserTrapActionInAclTableDefinition(acl_table_definition.rule_action_field_lookup)) + { + // Set up User Defined Traps for QOS_QUEUE action + auto status = gP4Orch->getAclRuleManager()->setUpUserDefinedTraps(); + if (!status.ok()) + { + gP4Orch->getAclRuleManager()->cleanUpUserDefinedTraps(); + LOG_ERROR_AND_RETURN(status); + } + } + + auto build_action_color_rc = buildAclTableDefinitionActionColorFieldValues( + app_db_entry.packet_action_color_lookup, &acl_table_definition.rule_action_field_lookup, + &acl_table_definition.rule_packet_action_color_lookup); + LOG_AND_RETURN_IF_ERROR(build_action_color_rc.prepend("Failed to build ACL table definition " + "action color fields with table name " + + QuotedVar(app_db_entry.acl_table_name) + ": ")); + + if (!acl_table_definition.udf_fields_lookup.empty()) + { + LOG_AND_RETURN_IF_ERROR(createUdfGroupsAndUdfsForAclTable(acl_table_definition)); + } + + auto status = + createAclTable(acl_table_definition, &acl_table_definition.table_oid, &acl_table_definition.group_member_oid); + if (!status.ok()) + { + // Clean up newly created UDFs and UDF groups + for (auto &udf_fields : acl_table_definition.udf_fields_lookup) + { + for (auto &udf_field : fvValue(udf_fields)) + { + auto rc = removeUdf(udf_field.udf_id, udf_field.group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF %s: %s", QuotedVar(udf_field.udf_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF in recovery."); + } + rc = removeUdfGroup(udf_field.group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF group %s: %s", QuotedVar(udf_field.group_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF group in recovery."); + } + } + } + LOG_ERROR_AND_RETURN( + status.prepend("Failed to create ACL table with key " + QuotedVar(app_db_entry.acl_table_name))); + } + return status; +} + +ReturnCode AclTableManager::createDefaultUdfMatch() +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_match_oid; + std::vector udf_match_attrs; + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->create_udf_match(&udf_match_oid, gSwitchId, 0, udf_match_attrs.data()), + "Failed to create default UDF match from SAI call " + "sai_udf_api->create_udf_match"); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, udf_match_oid); + SWSS_LOG_INFO("Suceeded to create default UDF match %s with object ID %s ", QuotedVar(P4_UDF_MATCH_DEFAULT).c_str(), + sai_serialize_object_id(udf_match_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeDefaultUdfMatch() +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_match_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Default UDF match " << QuotedVar(P4_UDF_MATCH_DEFAULT) << " was not found"; + } + + // Check if there is anything referring to the UDF match before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &ref_count)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Default UDF match " << QuotedVar(P4_UDF_MATCH_DEFAULT) << " reference count was not found"; + } + if (ref_count > 0) + { + return ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "Default UDF match " << QuotedVar(P4_UDF_MATCH_DEFAULT) + << " is referenced by other objects (ref_count = " << ref_count << ")"; + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->remove_udf_match(udf_match_oid), + "Failed to remove default UDF match with id " << udf_match_oid); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT); + + SWSS_LOG_INFO("Suceeded to remove UDF match %s : %s", QuotedVar(P4_UDF_MATCH_DEFAULT).c_str(), + sai_serialize_object_id(udf_match_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::createUdfGroup(const P4UdfField &udf_field) +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_group_oid; + std::vector udf_group_attrs; + sai_attribute_t udf_group_attr; + udf_group_attr.id = SAI_UDF_GROUP_ATTR_TYPE; + udf_group_attr.value.s32 = SAI_UDF_GROUP_TYPE_GENERIC; + udf_group_attrs.push_back(udf_group_attr); + + udf_group_attr.id = SAI_UDF_GROUP_ATTR_LENGTH; + udf_group_attr.value.u16 = udf_field.length; + udf_group_attrs.push_back(udf_group_attr); + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->create_udf_group(&udf_group_oid, gSwitchId, + (uint32_t)udf_group_attrs.size(), + udf_group_attrs.data()), + "Failed to create UDF group " << QuotedVar(udf_field.group_id) + << " from SAI call sai_udf_api->create_udf_group"); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id, udf_group_oid); + SWSS_LOG_INFO("Suceeded to create UDF group %s with object ID %s ", QuotedVar(udf_field.group_id).c_str(), + sai_serialize_object_id(udf_group_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeUdfGroup(const std::string &udf_group_id) +{ + SWSS_LOG_ENTER(); + sai_object_id_t group_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id, &group_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "UDF group " << QuotedVar(udf_group_id) << " was not found"; + } + + // Check if there is anything referring to the UDF group before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id, &ref_count)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF group " << QuotedVar(udf_group_id) << " reference count was not found"; + } + if (ref_count > 0) + { + return ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "UDF group " << QuotedVar(udf_group_id) << " referenced by other objects (ref_count = " << ref_count + << ")"; + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->remove_udf_group(group_oid), + "Failed to remove UDF group with id " << QuotedVar(udf_group_id)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id); + + SWSS_LOG_NOTICE("Suceeded to remove UDF group %s: %s", QuotedVar(udf_group_id).c_str(), + sai_serialize_object_id(group_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::createUdf(const P4UdfField &udf_field) +{ + SWSS_LOG_ENTER(); + const auto &udf_id = udf_field.udf_id; + sai_object_id_t udf_group_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id, &udf_group_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF group " << QuotedVar(udf_field.group_id) << " does not exist"; + } + sai_object_id_t udf_match_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF default match " << QuotedVar(P4_UDF_MATCH_DEFAULT) << " does not exist"; + } + std::vector udf_attrs; + sai_attribute_t udf_attr; + udf_attr.id = SAI_UDF_ATTR_GROUP_ID; + udf_attr.value.oid = udf_group_oid; + udf_attrs.push_back(udf_attr); + + udf_attr.id = SAI_UDF_ATTR_MATCH_ID; + udf_attr.value.oid = udf_match_oid; + udf_attrs.push_back(udf_attr); + + udf_attr.id = SAI_UDF_ATTR_BASE; + udf_attr.value.s32 = udf_field.base; + udf_attrs.push_back(udf_attr); + + udf_attr.id = SAI_UDF_ATTR_OFFSET; + udf_attr.value.u16 = udf_field.offset; + udf_attrs.push_back(udf_attr); + + sai_object_id_t udf_oid; + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_udf_api->create_udf(&udf_oid, gSwitchId, (uint32_t)udf_attrs.size(), udf_attrs.data()), + "Failed to create UDF " << QuotedVar(udf_id) << " from SAI call sai_udf_api->create_udf"); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_UDF, udf_id, udf_oid); + // Increase UDF group and match reference count + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id); + SWSS_LOG_NOTICE("Suceeded to create UDF %s with object ID %s ", QuotedVar(udf_id).c_str(), + sai_serialize_object_id(udf_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeUdf(const std::string &udf_id, const std::string &udf_group_id) +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF, udf_id, &udf_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "UDF " << QuotedVar(udf_id) << " was not found"; + } + // Check if there is anything referring to the UDF before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_UDF, udf_id, &ref_count)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF " << QuotedVar(udf_id) << " reference count was not found"; + } + if (ref_count > 0) + { + return ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "UDF " << QuotedVar(udf_id) << " referenced by other objects (ref_count = " << ref_count << ")"; + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->remove_udf(udf_oid), "Failed to remove UDF with id " + << udf_oid + << " from SAI call sai_udf_api->remove_udf"); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_UDF, udf_id); + // Decrease UDF group and match reference count + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id); + SWSS_LOG_NOTICE("Suceeded to remove UDF %s: %s", QuotedVar(udf_id).c_str(), + sai_serialize_object_id(udf_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::createUdfGroupsAndUdfsForAclTable(const P4AclTableDefinition &acl_table_definition) +{ + ReturnCode status; + // Cache newly created UDFs + std::vector created_udf_fields; + // Cache newly created UDF groups, + std::vector created_udf_group_ids; + // Create UDF groups and UDFs + for (auto &udf_fields : acl_table_definition.udf_fields_lookup) + { + for (auto &udf_field : fvValue(udf_fields)) + { + status = createUdfGroup(udf_field); + if (!status.ok()) + { + status.prepend("Failed to create ACL UDF group with group id " + QuotedVar(udf_field.group_id) + " : "); + break; + } + created_udf_group_ids.push_back(udf_field.group_id); + status = createUdf(udf_field); + if (!status.ok()) + { + status.prepend("Failed to create ACL UDF with id " + QuotedVar(udf_field.udf_id) + ": "); + break; + } + created_udf_fields.push_back(udf_field); + } + if (!status.ok()) + break; + } + // Clean up created UDFs and UDF groups if fails to create all. + if (!status.ok()) + { + for (const auto &udf_field : created_udf_fields) + { + auto rc = removeUdf(udf_field.udf_id, udf_field.group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF %s: %s", QuotedVar(udf_field.udf_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF in recovery."); + } + } + for (const auto &udf_group_id : created_udf_group_ids) + { + auto rc = removeUdfGroup(udf_group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF group %s: %s", QuotedVar(udf_group_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF group in recovery."); + } + } + LOG_ERROR_AND_RETURN(status); + } + return ReturnCode(); +} + +ReturnCode AclTableManager::createAclTable(P4AclTableDefinition &acl_table, sai_object_id_t *acl_table_oid, + sai_object_id_t *acl_group_member_oid) +{ + // Prepare SAI ACL attributes list to create ACL table + std::vector acl_attr_list; + sai_attribute_t acl_attr; + acl_attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; + acl_attr.value.s32 = acl_table.stage; + acl_attr_list.push_back(acl_attr); + + if (acl_table.size > 0) + { + acl_attr.id = SAI_ACL_TABLE_ATTR_SIZE; + acl_attr.value.u32 = acl_table.size; + acl_attr_list.push_back(acl_attr); + } + + std::set table_match_fields_to_add; + if (!acl_table.ip_type_bit_type_lookup.empty()) + { + acl_attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE; + acl_attr.value.booldata = true; + acl_attr_list.push_back(acl_attr); + table_match_fields_to_add.insert(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE); + } + + for (const auto &match_field : acl_table.sai_match_field_lookup) + { + const auto &sai_match_field = fvValue(match_field); + // Avoid duplicate match attribute to add + if (table_match_fields_to_add.find(sai_match_field.table_attr) != table_match_fields_to_add.end()) + continue; + acl_attr.id = sai_match_field.table_attr; + acl_attr.value.booldata = true; + acl_attr_list.push_back(acl_attr); + table_match_fields_to_add.insert(sai_match_field.table_attr); + } + + for (const auto &match_fields : acl_table.composite_sai_match_fields_lookup) + { + const auto &sai_match_fields = fvValue(match_fields); + for (const auto &sai_match_field : sai_match_fields) + { + // Avoid duplicate match attribute to add + if (table_match_fields_to_add.find(sai_match_field.table_attr) != table_match_fields_to_add.end()) + continue; + acl_attr.id = sai_match_field.table_attr; + acl_attr.value.booldata = true; + acl_attr_list.push_back(acl_attr); + table_match_fields_to_add.insert(sai_match_field.table_attr); + } + } + + // Add UDF group attributes + for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) + { + acl_attr.id = SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN + fvValue(udf_group_idx); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx), &acl_attr.value.oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "THe UDF group with id " << QuotedVar(fvField(udf_group_idx)) << " was not found."); + } + acl_attr_list.push_back(acl_attr); + } + + // OA workaround to fix b/191114070: always add counter action in ACL table + // action list during creation + int32_t acl_action_list[1]; + acl_action_list[0] = SAI_ACL_ACTION_TYPE_COUNTER; + acl_attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; + acl_attr.value.s32list.count = 1; + acl_attr.value.s32list.list = acl_action_list; + acl_attr_list.push_back(acl_attr); + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_acl_api->create_acl_table(acl_table_oid, gSwitchId, (uint32_t)acl_attr_list.size(), acl_attr_list.data()), + "Failed to create ACL table " << QuotedVar(acl_table.acl_table_name)); + SWSS_LOG_NOTICE("Called SAI API to create ACL table %s ", sai_serialize_object_id(*acl_table_oid).c_str()); + auto status = createAclGroupMember(acl_table, acl_group_member_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL group member for table %s", QuotedVar(acl_table.acl_table_name).c_str()); + auto sai_status = sai_acl_api->remove_acl_table(*acl_table_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove ACL table %s SAI_STATUS: %s", QuotedVar(acl_table.acl_table_name).c_str(), + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL table in recovery."); + } + return status; + } + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table.acl_table_name, *acl_table_oid); + gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)acl_table.stage, + SAI_ACL_BIND_POINT_TYPE_SWITCH); + m_aclTablesByStage[acl_table.stage].push_back(acl_table.acl_table_name); + m_aclTableDefinitions[acl_table.acl_table_name] = acl_table; + // Add ACL table name to AclRuleManager mapping in p4orch + if (!gP4Orch->addAclTableToManagerMapping(acl_table.acl_table_name)) + { + SWSS_LOG_NOTICE("ACL table %s to AclRuleManager mapping already exists", + QuotedVar(acl_table.acl_table_name).c_str()); + } + for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx)); + } + + SWSS_LOG_NOTICE("ACL table %s was created successfully : %s", QuotedVar(acl_table.acl_table_name).c_str(), + sai_serialize_object_id(acl_table.table_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeAclTable(P4AclTableDefinition &acl_table) +{ + SWSS_LOG_ENTER(); + + auto status = removeAclGroupMember(acl_table.acl_table_name); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL table with key %s : failed to delete group " + "member %s.", + QuotedVar(acl_table.acl_table_name).c_str(), + sai_serialize_object_id(acl_table.group_member_oid).c_str()); + return status; + } + auto sai_status = sai_acl_api->remove_acl_table(acl_table.table_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + status = ReturnCode(sai_status) << "Failed to remove ACL table with key " << QuotedVar(acl_table.acl_table_name) + << " by calling sai_acl_api->remove_acl_table"; + SWSS_LOG_ERROR("%s", status.message().c_str()); + auto rc = createAclGroupMember(acl_table, &acl_table.group_member_oid); + if (!rc.ok()) + { + SWSS_LOG_ERROR("%s", rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL group member in recovery."); + } + return status; + } + for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx)); + } + + // Remove UDFs and UDF groups after ACL table deletion + std::vector removed_udf_fields; + std::vector removed_udf_group_ids; + for (const auto &udf_fields : acl_table.udf_fields_lookup) + { + for (const auto &udf_field : fvValue(udf_fields)) + { + status = removeUdf(udf_field.udf_id, udf_field.group_id); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL UDF with id %s : %s", QuotedVar(udf_field.udf_id).c_str(), + status.message().c_str()); + break; + } + removed_udf_fields.push_back(udf_field); + status = removeUdfGroup(udf_field.group_id); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL UDF group with group id %s : %s", + QuotedVar(udf_field.group_id).c_str(), status.message().c_str()); + break; + } + removed_udf_group_ids.push_back(udf_field); + } + if (!status.ok()) + { + break; + } + } + if (!status.ok()) + { + for (const auto &udf_field : removed_udf_group_ids) + { + auto rc = createUdfGroup(udf_field); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to create UDF group %s: %s", QuotedVar(udf_field.group_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create UDF group in recovery."); + } + } + for (const auto &udf_field : removed_udf_fields) + { + auto rc = createUdf(udf_field); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to create UDF %s: %s", QuotedVar(udf_field.udf_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create UDF in recovery."); + } + } + } + + gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)acl_table.stage, + SAI_ACL_BIND_POINT_TYPE_SWITCH, acl_table.table_oid); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table.acl_table_name); + // Remove ACL table name to AclRuleManager mapping in p4orch + if (!gP4Orch->removeAclTableToManagerMapping(acl_table.acl_table_name)) + { + SWSS_LOG_NOTICE("ACL table %s to AclRuleManager mapping does not exist", + QuotedVar(acl_table.acl_table_name).c_str()); + } + auto &table_keys = m_aclTablesByStage[acl_table.stage]; + auto position = std::find(table_keys.begin(), table_keys.end(), acl_table.acl_table_name); + if (position != table_keys.end()) + { + table_keys.erase(position); + } + P4AclTableDefinition rollback_acl_table = acl_table; + m_aclTableDefinitions.erase(acl_table.acl_table_name); + + if (!status.ok()) + { + auto rc = + createAclTable(rollback_acl_table, &rollback_acl_table.table_oid, &rollback_acl_table.group_member_oid); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL table: %s", rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL table in recovery."); + } + return status; + } + SWSS_LOG_NOTICE("ACL table %s(%s) was removed successfully.", QuotedVar(rollback_acl_table.acl_table_name).c_str(), + sai_serialize_object_id(rollback_acl_table.table_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::processDeleteTableRequest(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + + auto *acl_table = getAclTable(acl_table_name); + if (acl_table == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "ACL table with key " << QuotedVar(acl_table_name) << " does not exist"); + } + // Check if there is anything referring to the ACL table before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table->acl_table_name, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count of ACL table " + << QuotedVar(acl_table->acl_table_name)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table " << QuotedVar(acl_table->acl_table_name) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + return removeAclTable(*acl_table); +} + +ReturnCode AclTableManager::createAclGroupMember(const P4AclTableDefinition &acl_table, + sai_object_id_t *acl_grp_mem_oid) +{ + SWSS_LOG_ENTER(); + std::vector acl_mem_attrs; + sai_attribute_t acl_mem_attr; + acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID; + acl_mem_attr.value.oid = acl_table.group_oid; + acl_mem_attrs.push_back(acl_mem_attr); + + acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID; + acl_mem_attr.value.oid = acl_table.table_oid; + acl_mem_attrs.push_back(acl_mem_attr); + + acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY; + acl_mem_attr.value.u32 = acl_table.priority; + acl_mem_attrs.push_back(acl_mem_attr); + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_acl_api->create_acl_table_group_member(acl_grp_mem_oid, gSwitchId, (uint32_t)acl_mem_attrs.size(), + acl_mem_attrs.data()), + "Failed to create ACL group member in group " << sai_serialize_object_id(acl_table.group_oid)); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table.acl_table_name, *acl_grp_mem_oid); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE_GROUP, std::to_string(acl_table.stage)); + SWSS_LOG_NOTICE("ACL group member for table %s was created successfully: %s", + QuotedVar(acl_table.acl_table_name).c_str(), sai_serialize_object_id(*acl_grp_mem_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeAclGroupMember(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + + sai_object_id_t grp_mem_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table_name, &grp_mem_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to remove ACL group member " << sai_serialize_object_id(grp_mem_oid) + << " for table " << QuotedVar(acl_table_name) << ": invalid table key."); + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->remove_acl_table_group_member(grp_mem_oid), + "Failed to remove ACL group member " << sai_serialize_object_id(grp_mem_oid) + << " for table " << QuotedVar(acl_table_name)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table_name); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE_GROUP, + std::to_string(m_aclTableDefinitions[acl_table_name].stage).c_str()); + SWSS_LOG_NOTICE("ACL table member %s for table %s was removed successfully.", + sai_serialize_object_id(grp_mem_oid).c_str(), QuotedVar(acl_table_name).c_str()); + return ReturnCode(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_table_manager.h b/orchagent/p4orch/acl_table_manager.h new file mode 100644 index 0000000000..6243c08cb4 --- /dev/null +++ b/orchagent/p4orch/acl_table_manager.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +#include "orch.h" +#include "p4orch/acl_util.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" + +extern "C" +{ +#include "sai.h" +} + +namespace p4orch +{ +namespace test +{ +class AclManagerTest; +} // namespace test + +class AclTableManager : public ObjectManagerInterface +{ + public: + explicit AclTableManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher); + virtual ~AclTableManager(); + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + // Get ACL table definition by table name in cache. Return nullptr if not + // found. + P4AclTableDefinition *getAclTable(const std::string &acl_table_name); + + private: + // Validate ACL table definition APP_DB entry. + ReturnCode validateAclTableDefinitionAppDbEntry(const P4AclTableDefinitionAppDbEntry &app_db_entry); + + // Deserializes an entry from table APP_P4RT_ACL_TABLE_DEFINITION_NAME. + ReturnCodeOr deserializeAclTableDefinitionAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Create new ACL table definition. + ReturnCode createAclTable(P4AclTableDefinition &acl_table, sai_object_id_t *acl_table_oid, + sai_object_id_t *acl_group_member_oid); + + // Remove ACL table by table name. Caller should verify reference count is + // zero before calling the method. + ReturnCode removeAclTable(P4AclTableDefinition &acl_table); + + // Create UDF groups and UDFs for the ACL table. If any of the UDF and UDF + // group fails to create, then clean up all created ones + ReturnCode createUdfGroupsAndUdfsForAclTable(const P4AclTableDefinition &acl_table); + + // Create new ACL UDF group based on the UdfField. Callers should verify no + // UDF group with the same name exists + ReturnCode createUdfGroup(const P4UdfField &udf_field); + + // Remove ACL UDF group by group id, + ReturnCode removeUdfGroup(const std::string &udf_group_id); + + // Create the default UDF match with name P4_UDF_MATCH_DEFAULT. + // The attributes values for the UDF match are all wildcard matches. + ReturnCode createDefaultUdfMatch(); + + // Remove the default UDF match if no UDFs depend on it. + ReturnCode removeDefaultUdfMatch(); + + // Create UDF with group_oid, base and offset defined in udf_field and the + // default udf_match_oid. Callers should verify no UDF with the same name + // exists + ReturnCode createUdf(const P4UdfField &udf_fields); + + // Remove UDF by id string and group id string if no ACL rules depends on it + ReturnCode removeUdf(const std::string &udf_id, const std::string &udf_group_id); + + // Process add request on ACL table definition. If the table is + // created successfully, a new consumer will be added in + // p4orch to process requests for ACL rules for the table. + ReturnCode processAddTableRequest(const P4AclTableDefinitionAppDbEntry &app_db_entry); + + // Process delete request on ACL table definition. + ReturnCode processDeleteTableRequest(const std::string &acl_table_name); + + // Create ACL group member for given ACL table. + ReturnCode createAclGroupMember(const P4AclTableDefinition &acl_table, sai_object_id_t *acl_grp_mem_oid); + + // Remove ACL group member for given ACL table. + ReturnCode removeAclGroupMember(const std::string &acl_table_name); + + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + P4AclTableDefinitions m_aclTableDefinitions; + std::deque m_entries; + std::map> m_aclTablesByStage; + + friend class p4orch::test::AclManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_util.cpp b/orchagent/p4orch/acl_util.cpp new file mode 100644 index 0000000000..6caf67cade --- /dev/null +++ b/orchagent/p4orch/acl_util.cpp @@ -0,0 +1,874 @@ +#include "p4orch/acl_util.h" + +#include "converter.h" +#include "json.hpp" +#include "logger.h" +#include "sai_serialize.h" +#include "table.h" +#include "tokenize.h" + +namespace p4orch +{ + +std::string trim(const std::string &s) +{ + size_t end = s.find_last_not_of(WHITESPACE); + size_t start = s.find_first_not_of(WHITESPACE); + return (end == std::string::npos) ? EMPTY_STRING : s.substr(start, end - start + 1); +} + +bool parseAclTableAppDbActionField(const std::string &aggr_actions_str, std::vector *action_list, + std::vector *action_color_list) +{ + try + { + const auto &j = nlohmann::json::parse(aggr_actions_str); + if (!j.is_array()) + { + SWSS_LOG_ERROR("Invalid ACL table definition action %s, expecting an array.\n", aggr_actions_str.c_str()); + return false; + } + P4ActionParamName action_with_param; + for (auto &action_item : j) + { + auto sai_action_it = action_item.find(kAction); + if (sai_action_it == action_item.end()) + { + SWSS_LOG_ERROR("Invalid ACL table definition action %s, missing 'action':\n", aggr_actions_str.c_str()); + return false; + } + if (aclPacketActionLookup.find(sai_action_it.value()) == aclPacketActionLookup.end()) + { + action_with_param.sai_action = sai_action_it.value(); + auto action_param_it = action_item.find(kActionParamPrefix); + if (action_param_it != action_item.end() && !action_param_it.value().is_null()) + { + action_with_param.p4_param_name = action_param_it.value(); + } + action_list->push_back(action_with_param); + } + else + { + auto packet_color_it = action_item.find(kPacketColor); + P4PacketActionWithColor packet_action_with_color; + packet_action_with_color.packet_action = sai_action_it.value(); + if (packet_color_it != action_item.end() && !packet_color_it.value().is_null()) + { + packet_action_with_color.packet_color = packet_color_it.value(); + } + action_color_list->push_back(packet_action_with_color); + } + } + return true; + } + catch (std::exception &ex) + { + SWSS_LOG_ERROR("Failed to deserialize ACL table definition action fields: %s (%s)", aggr_actions_str.c_str(), + ex.what()); + return false; + } +} + +ReturnCode validateAndSetSaiMatchFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, + std::map *sai_match_field_lookup, + std::map *ip_type_bit_type_lookup) +{ + SaiMatchField sai_match_field; + auto format_str_it = match_json.find(kAclMatchFieldFormat); + if (format_str_it == match_json.end() || format_str_it.value().is_null() || !format_str_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is required and should be a string"; + } + auto format_it = formatLookup.find(format_str_it.value()); + if (format_it == formatLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is invalid, should be one of {" << P4_FORMAT_HEX_STRING << ", " << P4_FORMAT_MAC << ", " + << P4_FORMAT_IPV4 << ", " << P4_FORMAT_IPV6 << ", " << P4_FORMAT_STRING << "}"; + } + sai_match_field.format = format_it->second; + if (sai_match_field.format != Format::STRING) + { + // bitwidth is required if the format is not "STRING" + auto bitwidth_it = match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_it == match_json.end() || bitwidth_it.value().is_null() || !bitwidth_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + sai_match_field.bitwidth = bitwidth_it.value(); + } + + auto match_field_it = match_json.find(kAclMatchFieldSaiField); + if (match_field_it == match_json.end() || match_field_it.value().is_null() || !match_field_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldSaiField + << " value is required and should be a string"; + } + + std::vector tokenized_field = swss::tokenize(match_field_it.value(), kFieldDelimiter); + const auto &sai_match_field_str = tokenized_field[0]; + auto table_attr_it = aclMatchTableAttrLookup.find(sai_match_field_str); + auto rule_attr_it = aclMatchEntryAttrLookup.find(sai_match_field_str); + if (table_attr_it == aclMatchTableAttrLookup.end() || rule_attr_it == aclMatchEntryAttrLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << match_field_it.value() << " is not supported in P4Orch "; + } + const auto &expected_format_it = aclMatchTableAttrFormatLookup.find(table_attr_it->second); + if (expected_format_it == aclMatchTableAttrFormatLookup.end() || + sai_match_field.format != expected_format_it->second) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: format for field " << match_field_it.value() + << " is expected to be " << expected_format_it->second << ", but got " << format_it->first; + } + sai_match_field.table_attr = table_attr_it->second; + sai_match_field.entry_attr = rule_attr_it->second; + (*sai_match_field_lookup)[p4_match] = sai_match_field; + + if (rule_attr_it->second == SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE && tokenized_field.size() == 2) + { + // Get IP_TYPE suffix and save the bit mapping. + if (aclIpTypeBitSet.find(tokenized_field[1]) == aclIpTypeBitSet.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} has invalid IP_TYPE encode bit."; + } + (*ip_type_bit_type_lookup)[p4_match] = tokenized_field[1]; + } + SWSS_LOG_INFO("ACL table built match field %s with kind:sai_field", sai_match_field_str.c_str()); + + return ReturnCode(); +} + +ReturnCode validateAndSetCompositeElementSaiFieldJson( + const nlohmann::json &element_match_json, const std::string &p4_match, + std::map> *composite_sai_match_fields_lookup, const std::string &format_str) +{ + SaiMatchField sai_match_field; + const auto &element_str = element_match_json.dump(); + if (format_str != P4_FORMAT_IPV6) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: '" << kAclMatchFieldFormat << "' should be " << P4_FORMAT_IPV6; + } + sai_match_field.format = Format::IPV6; + + auto bitwidth_it = element_match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_it == element_match_json.end() || bitwidth_it.value().is_null() || !bitwidth_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << "element: " << element_str + << " is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + sai_match_field.bitwidth = bitwidth_it.value(); + + auto match_field_it = element_match_json.find(kAclMatchFieldSaiField); + if (match_field_it == element_match_json.end() || match_field_it.value().is_null() || + !match_field_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: " << kAclMatchFieldSaiField + << " value is required in composite elements and should be a string"; + } + const std::string &match_field_str = match_field_it.value(); + auto table_attr_it = aclCompositeMatchTableAttrLookup.find(match_field_str); + auto rule_attr_it = aclCompositeMatchEntryAttrLookup.find(match_field_str); + if (table_attr_it == aclCompositeMatchTableAttrLookup.end() || + rule_attr_it == aclCompositeMatchEntryAttrLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: not supported in P4Orch " + "as an element in composite match fields"; + } + const uint32_t expected_bitwidth = BYTE_BITWIDTH * IPV6_SINGLE_WORD_BYTES_LENGTH; + if (sai_match_field.bitwidth != expected_bitwidth) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: element.bitwidth is " + "expected to be " + << expected_bitwidth << " but got " << sai_match_field.bitwidth; + } + sai_match_field.table_attr = table_attr_it->second; + sai_match_field.entry_attr = rule_attr_it->second; + (*composite_sai_match_fields_lookup)[p4_match].push_back(sai_match_field); + SWSS_LOG_INFO("ACL table built composite match field element %s with kind:sai_field", match_field_str.c_str()); + return ReturnCode(); +} + +ReturnCode validateAndSetUdfFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, const std::string &acl_table_name, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookup) +{ + P4UdfField udf_field; + // Parse UDF bitwitdth + auto bitwidth_json_it = match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_json_it == match_json.end() || bitwidth_json_it.value().is_null() || + !bitwidth_json_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + uint32_t bitwidth = bitwidth_json_it.value(); + if (bitwidth % BYTE_BITWIDTH != 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value should be a multiple of 8."; + } + udf_field.length = (uint16_t)(bitwidth / BYTE_BITWIDTH); + + // Parse UDF offset + auto udf_offset_it = match_json.find(kAclUdfOffset); + if (udf_offset_it == match_json.end() || udf_offset_it.value().is_null() || !udf_offset_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclUdfOffset + << " value is required in composite elements and should be a number"; + } + udf_field.offset = udf_offset_it.value(); + + // Parse UDF base + auto udf_base_json_it = match_json.find(kAclUdfBase); + if (udf_base_json_it == match_json.end() || udf_base_json_it.value().is_null() || + !udf_base_json_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclUdfBase + << " value is required in composite elements and should be a string"; + } + const auto &udf_base_it = udfBaseLookup.find(udf_base_json_it.value()); + if (udf_base_it == udfBaseLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << udf_base_json_it.value() + << " is not supported in P4Orch " + "as a valid UDF base. Supported UDF bases are: " + << P4_UDF_BASE_L2 << ", " << P4_UDF_BASE_L3 << " and " << P4_UDF_BASE_L4; + } + udf_field.base = udf_base_it->second; + // Set UDF group id + udf_field.group_id = acl_table_name + "-" + p4_match + "-" + std::to_string((*udf_fields_lookup)[p4_match].size()); + udf_field.udf_id = + udf_field.group_id + "-base" + std::to_string(udf_field.base) + "-offset" + std::to_string(udf_field.offset); + (*udf_fields_lookup)[p4_match].push_back(udf_field); + // Assign UDF group to a new ACL entry attr index if it is a new group + uint16_t index = 0; + auto udf_group_attr_index_it = udf_group_attr_index_lookup->find(udf_field.group_id); + if (udf_group_attr_index_it != udf_group_attr_index_lookup->end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Error when building UDF field for ACL talbe: duplicated UDF " + "groups found for the same index."; + } + index = (uint16_t)udf_group_attr_index_lookup->size(); + (*udf_group_attr_index_lookup)[udf_field.group_id] = index; + SWSS_LOG_INFO("ACL table built composite match field elelment %s with kind:udf", udf_field.group_id.c_str()); + return ReturnCode(); +} + +ReturnCode validateAndSetCompositeMatchFieldJson( + const nlohmann::json &aggr_match_json, const std::string &p4_match, const std::string &aggr_match_str, + const std::string &acl_table_name, + std::map> *composite_sai_match_fields_lookup, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookups) +{ + auto format_str_it = aggr_match_json.find(kAclMatchFieldFormat); + if (format_str_it == aggr_match_json.end() || format_str_it.value().is_null() || !format_str_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is required and should be a string"; + } + auto format_it = formatLookup.find(format_str_it.value()); + if (format_it == formatLookup.end() || format_it->second == Format::STRING) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is invalid, should be one of {" << P4_FORMAT_HEX_STRING << ", " << P4_FORMAT_IPV6 << "}"; + } + auto bitwidth_it = aggr_match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_it == aggr_match_json.end() || bitwidth_it.value().is_null() || !bitwidth_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + uint32_t composite_bitwidth = bitwidth_it.value(); + + auto elements_it = aggr_match_json.find(kAclMatchFieldElements); + // b/175596733: temp disable verification on composite elements field until + // p4rt implementation is added. + if (elements_it == aggr_match_json.end()) + { + (*udf_fields_lookup)[p4_match]; + return ReturnCode(); + } + if (elements_it.value().is_null() || !elements_it.value().is_array()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'elements' value is " + "required and should be an array"; + } + for (const auto &element : elements_it.value()) + { + if (element.is_null() || !element.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'elements' member " + "should be an json"; + } + const auto &element_kind_it = element.find(kAclMatchFieldKind); + if (element_kind_it == element.end() || element_kind_it.value().is_null() || + !element_kind_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite element " + "'kind' value is required and should be a string"; + } + ReturnCode rc; + if (element_kind_it.value() == kAclMatchFieldSaiField) + { + rc = validateAndSetCompositeElementSaiFieldJson(element, p4_match, composite_sai_match_fields_lookup, + format_str_it.value()); + } + else if (element_kind_it.value() == kAclMatchFieldKindUdf) + { + if (format_str_it.value() != P4_FORMAT_HEX_STRING) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value should be HEX_STRING for UDF field"; + } + rc = validateAndSetUdfFieldJson(element, p4_match, aggr_match_str, acl_table_name, udf_fields_lookup, + udf_group_attr_index_lookups); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite element " + "'kind' should be either " + << kAclMatchFieldKindUdf << " or " << kAclMatchFieldSaiField; + } + if (!rc.ok()) + return rc; + } + // elements kind should be all sai_field or all udf. + auto sai_field_it = composite_sai_match_fields_lookup->find(p4_match); + auto udf_field_it = udf_fields_lookup->find(p4_match); + if (sai_field_it != composite_sai_match_fields_lookup->end() && udf_field_it != udf_fields_lookup->end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite element " + "'kind' should be consistent within all elements."; + } + // The sum of bitwidth of elements should equals overall bitwidth defined + // in composite fields + uint32_t total_bitwidth = 0; + if (sai_field_it != composite_sai_match_fields_lookup->end()) + { + // IPV6_64bit(IPV6_WORD3 and IPV6_WORD2 in elements, kind:sai_field, + // format:IPV6) + if (sai_field_it->second.size() != 2) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite match field " + "with sai_field in element kind should have 2 elements."; + } + if (!((sai_field_it->second[0].table_attr == SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3 && + sai_field_it->second[1].table_attr == SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2) || + (sai_field_it->second[0].table_attr == SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3 && + sai_field_it->second[1].table_attr == SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2))) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: For composite match " + "field " + "with element.kind == sai_field, the SAI match field " + "in elements list should be either pair {" + << P4_MATCH_DST_IPV6_WORD3 << ", " << P4_MATCH_DST_IPV6_WORD2 << "} or pair {" + << P4_MATCH_SRC_IPV6_WORD3 << ", " << P4_MATCH_SRC_IPV6_WORD2 << "} with the correct sequence"; + } + total_bitwidth = sai_field_it->second[0].bitwidth + sai_field_it->second[1].bitwidth; + } + if (udf_field_it != udf_fields_lookup->end()) + { + for (const auto &udf_field : udf_field_it->second) + { + total_bitwidth += (uint32_t)udf_field.length * BYTE_BITWIDTH; + } + } + if (total_bitwidth != composite_bitwidth) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite bitwidth " + "does not equal with the sum of elements bitwidth."; + } + return ReturnCode(); +} + +ReturnCode buildAclTableDefinitionMatchFieldValues(const std::map &match_field_lookup, + P4AclTableDefinition *acl_table) +{ + for (const auto &raw_match_field : match_field_lookup) + { + const auto &p4_match = fvField(raw_match_field); + const auto &aggr_match_str = fvValue(raw_match_field); + try + { + const auto &aggr_match_json = nlohmann::json::parse(aggr_match_str); + if (!aggr_match_json.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: expecting an json"; + } + + const auto &kind_it = aggr_match_json.find(kAclMatchFieldKind); + if (kind_it == aggr_match_json.end() || kind_it.value().is_null() || !kind_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'kind' value is " + "required and should be a string"; + } + ReturnCode rc; + if (kind_it.value() == kAclMatchFieldSaiField) + { + rc = validateAndSetSaiMatchFieldJson(aggr_match_json, p4_match, aggr_match_str, + &acl_table->sai_match_field_lookup, + &acl_table->ip_type_bit_type_lookup); + } + else if (kind_it.value() == kAclMatchFieldKindComposite) + { + rc = validateAndSetCompositeMatchFieldJson( + aggr_match_json, p4_match, aggr_match_str, acl_table->acl_table_name, + &acl_table->composite_sai_match_fields_lookup, &acl_table->udf_fields_lookup, + &acl_table->udf_group_attr_index_lookup); + } + else if (kind_it.value() == kAclMatchFieldKindUdf) + { + auto format_str_it = aggr_match_json.find(kAclMatchFieldFormat); + if (format_str_it == aggr_match_json.end() || format_str_it.value().is_null() || + !format_str_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is required and should be a string"; + } + if (format_str_it.value() != P4_FORMAT_HEX_STRING) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value should be HEX_STRING for UDF field"; + } + rc = validateAndSetUdfFieldJson(aggr_match_json, p4_match, aggr_match_str, acl_table->acl_table_name, + &acl_table->udf_fields_lookup, &acl_table->udf_group_attr_index_lookup); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'kind' is expecting " + "one of {" + << kAclMatchFieldKindComposite << ", " << kAclMatchFieldSaiField << ", " << kAclMatchFieldKindUdf + << "}."; + } + if (!rc.ok()) + return rc; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: ex" << ex.what(); + } + } + return ReturnCode(); +} + +ReturnCode buildAclTableDefinitionActionFieldValues( + const std::map> &action_field_lookup, + std::map> *aggr_sai_actions_lookup) +{ + SaiActionWithParam action_with_param; + for (const auto &aggr_action_field : action_field_lookup) + { + auto &aggr_sai_actions = (*aggr_sai_actions_lookup)[fvField(aggr_action_field)]; + for (const auto &single_action : fvValue(aggr_action_field)) + { + auto rule_action_it = aclActionLookup.find(single_action.sai_action); + if (rule_action_it == aclActionLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table action is invalid: " << single_action.sai_action; + } + action_with_param.action = rule_action_it->second; + action_with_param.param_name = single_action.p4_param_name; + aggr_sai_actions.push_back(action_with_param); + } + } + return ReturnCode(); +} + +ReturnCode buildAclTableDefinitionActionColorFieldValues( + const std::map> &action_color_lookup, + std::map> *aggr_sai_actions_lookup, + std::map> *aggr_sai_action_color_lookup) +{ + for (const auto &aggr_action_color : action_color_lookup) + { + auto &aggr_sai_actions = (*aggr_sai_actions_lookup)[fvField(aggr_action_color)]; + auto &aggr_sai_action_color = (*aggr_sai_action_color_lookup)[fvField(aggr_action_color)]; + for (const auto &action_color : fvValue(aggr_action_color)) + { + auto packet_action_it = aclPacketActionLookup.find(action_color.packet_action); + if (packet_action_it == aclPacketActionLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table packet action is invalid: " << action_color.packet_action; + } + + if (action_color.packet_color.empty()) + { + // Handle packet action without packet color, set ACL entry attribute + SaiActionWithParam action_with_param; + action_with_param.action = SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION; + action_with_param.param_name = EMPTY_STRING; + action_with_param.param_value = action_color.packet_action; + aggr_sai_actions.push_back(action_with_param); + continue; + } + + // Handle packet action with packet color, set ACL policer attribute + auto packet_color_it = aclPacketColorPolicerAttrLookup.find(action_color.packet_color); + if (packet_color_it == aclPacketColorPolicerAttrLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table packet color is invalid: " << action_color.packet_color; + } + aggr_sai_action_color[packet_color_it->second] = packet_action_it->second; + } + } + return ReturnCode(); +} + +bool isSetUserTrapActionInAclTableDefinition( + const std::map> &aggr_sai_actions_lookup) +{ + for (const auto &aggr_action : aggr_sai_actions_lookup) + { + for (const auto &sai_action : fvValue(aggr_action)) + { + if (sai_action.action == SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID) + return true; + } + } + return false; +} + +bool setMatchFieldIpType(const std::string &attr_value, sai_attribute_value_t *value, + const std::string &ip_type_bit_type) +{ + SWSS_LOG_ENTER(); + if (ip_type_bit_type == EMPTY_STRING) + { + SWSS_LOG_ERROR("Invalid IP type %s, bit type is not defined.", attr_value.c_str()); + return false; + } + // go/p4-ip-type + const auto &ip_type_bit_data_mask = swss::tokenize(attr_value, kDataMaskDelimiter); + if (ip_type_bit_data_mask.size() == 2 && swss::to_uint(trim(ip_type_bit_data_mask[1])) == 0) + { + SWSS_LOG_ERROR("Invalid IP_TYPE mask %s for bit type %s: ip type bit mask " + "should not be zero.", + attr_value.c_str(), ip_type_bit_type.c_str()); + return false; + } + int ip_type_bit_data = std::stoi(ip_type_bit_data_mask[0], nullptr, 0); + value->aclfield.mask.u32 = 0xFFFFFFFF; + if (ip_type_bit_type == P4_IP_TYPE_BIT_IP) + { + if (ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_IP; + } + else + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_NON_IP; + } + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_IPV4ANY) + { + if (ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_IPV4ANY; + } + else + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_NON_IPV4; + } + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_IPV6ANY) + { + if (ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_IPV6ANY; + } + else + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_NON_IPV6; + } + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_ARP && ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_ARP; + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_ARP_REQUEST && ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_ARP_REQUEST; + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_ARP_REPLY && ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_ARP_REPLY; + } + else + { + SWSS_LOG_ERROR("Invalid IP_TYPE bit data %s for ip type %s", attr_value.c_str(), ip_type_bit_type.c_str()); + return false; + } + return true; +} + +ReturnCode setCompositeSaiMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value) +{ + try + { + const auto &tokenized_ip = swss::tokenize(attr_value, kDataMaskDelimiter); + swss::IpAddress ip_data; + swss::IpAddress ip_mask; + if (tokenized_ip.size() == 2) + { + // data & mask + ip_data = swss::IpAddress(trim(tokenized_ip[0])); + if (ip_data.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP data type should be v6 type: " << attr_value; + } + ip_mask = swss::IpAddress(trim(tokenized_ip[1])); + if (ip_mask.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP mask type should be v6 type: " << attr_value; + } + } + else + { + // LPM annotated value + swss::IpPrefix ip_prefix(trim(attr_value)); + if (ip_prefix.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "IP type should be v6 type: " << attr_value; + } + ip_data = ip_prefix.getIp(); + ip_mask = ip_prefix.getMask(); + } + switch (attr_name) + { + case SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3: + case SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3: { + // IPv6 Address 127:96 32 bits + memcpy(&value->aclfield.data.ip6[0], &ip_data.getV6Addr()[0], IPV6_SINGLE_WORD_BYTES_LENGTH); + memcpy(&value->aclfield.mask.ip6[0], &ip_mask.getV6Addr()[0], IPV6_SINGLE_WORD_BYTES_LENGTH); + break; + } + case SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2: + case SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2: { + // IPv6 Address 95:64 32 bits + memcpy(&value->aclfield.data.ip6[IPV6_SINGLE_WORD_BYTES_LENGTH], + &ip_data.getV6Addr()[IPV6_SINGLE_WORD_BYTES_LENGTH], IPV6_SINGLE_WORD_BYTES_LENGTH); + memcpy(&value->aclfield.mask.ip6[IPV6_SINGLE_WORD_BYTES_LENGTH], + &ip_mask.getV6Addr()[IPV6_SINGLE_WORD_BYTES_LENGTH], IPV6_SINGLE_WORD_BYTES_LENGTH); + break; + } + default: { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL match field " << attr_name << " is not supported in composite match field sai_field type."; + } + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to parse match attribute " << attr_name + << " (value: " << attr_value << "). Error:" << e.what(); + } + value->aclfield.enable = true; + return ReturnCode(); +} + +ReturnCode setUdfMatchValue(const P4UdfField &udf_field, const std::string &attr_value, sai_attribute_value_t *value, + P4UdfDataMask *udf_data_mask, uint16_t bytes_offset) +{ + if (!udf_data_mask->data.empty() || !udf_data_mask->mask.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to set UDF match field " << udf_field.udf_id << " with value " << attr_value + << " in ACL rule: the UDF: duplicated UDF value found for the same " + "UDF field."; + } + try + { + // Extract UDF field values by length(in bytes) and offset(in bytes) + const std::vector &value_and_mask = swss::tokenize(attr_value, kDataMaskDelimiter); + uint32_t data_str_offset = bytes_offset * 2, mask_str_offset = bytes_offset * 2; + const auto &data = trim(value_and_mask[0]); + if (data.size() > 2 && data[0] == '0' && (data[1] == 'x' || data[1] == 'X')) + { + data_str_offset += 2; + } + std::string mask = EMPTY_STRING; + if (value_and_mask.size() > 1) + { + mask = trim(value_and_mask[1]); + if (mask.size() > 2 && mask[0] == '0' && (mask[1] == 'x' || mask[1] == 'X')) + { + mask_str_offset += 2; + } + } + for (uint16_t i = 0; i < udf_field.length; i++) + { + // Add to udf_data uint8_t list + udf_data_mask->data.push_back(std::stoul(data.substr(data_str_offset, 2), nullptr, 16) & 0xFF); + data_str_offset += 2; + if (value_and_mask.size() > 1) + { + // Add to udf_mask uint8_t list + udf_data_mask->mask.push_back((std::stoul(mask.substr(mask_str_offset, 2), nullptr, 16)) & 0xFF); + mask_str_offset += 2; + } + else + { + udf_data_mask->mask.push_back(0xFF); + } + } + value->aclfield.data.u8list.count = udf_field.length; + value->aclfield.data.u8list.list = udf_data_mask->data.data(); + value->aclfield.mask.u8list.count = udf_field.length; + value->aclfield.mask.u8list.list = udf_data_mask->mask.data(); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to set UDF match field " << udf_field.udf_id << " with value " << attr_value + << " in ACL rule: " << ex.what(); + } + value->aclfield.enable = true; + return ReturnCode(); +} + +bool isDiffActionFieldValue(const acl_entry_attr_union_t attr_name, const sai_attribute_value_t &value, + const sai_attribute_value_t &old_value, const P4AclRule &acl_rule, + const P4AclRule &old_acl_rule) +{ + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR: { + return value.aclaction.parameter.s32 != old_value.aclaction.parameter.s32; + } + case SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT: { + return value.aclaction.parameter.oid != old_value.aclaction.parameter.oid; + } + case SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP: { + return value.aclaction.parameter.ip4 != old_value.aclaction.parameter.ip4; + } + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS: + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS: { + return acl_rule.action_mirror_sessions.at(attr_name).oid != + old_acl_rule.action_mirror_sessions.at(attr_name).oid; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC: { + return memcmp(value.aclaction.parameter.mac, old_value.aclaction.parameter.mac, sizeof(sai_mac_t)); + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6: { + return memcmp(value.aclaction.parameter.ip6, old_value.aclaction.parameter.ip6, sizeof(sai_ip6_t)); + } + + case SAI_ACL_ENTRY_ATTR_ACTION_SET_TC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI: { + return value.aclaction.parameter.u8 != old_value.aclaction.parameter.u8; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID: { + return value.aclaction.parameter.u32 != old_value.aclaction.parameter.u32; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT: { + return value.aclaction.parameter.u16 != old_value.aclaction.parameter.u16; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID: { + return value.aclaction.parameter.oid != old_value.aclaction.parameter.oid; + } + case SAI_ACL_ENTRY_ATTR_ACTION_FLOOD: + case SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN: { + // parameter is not needed + return false; + } + default: { + return false; + } + } +} + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_util.h b/orchagent/p4orch/acl_util.h new file mode 100644 index 0000000000..c06849506b --- /dev/null +++ b/orchagent/p4orch/acl_util.h @@ -0,0 +1,710 @@ +#pragma once + +#include +#include +#include +#include + +#include "json.hpp" +#include "p4orch/p4orch_util.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +#include "saiextensions.h" +} + +namespace p4orch +{ + +// sai_acl_entry_attr_t or sai_acl_entry_attr_extensions_t +using acl_entry_attr_union_t = int32_t; +// sai_acl_table_attr_t or sai_acl_table_attr_extensions_t +using acl_table_attr_union_t = int32_t; + +// Describes the format of a value. +enum Format +{ + // Hex string, e.g. 0x0a8b. All lowercase, and always of length + // ceil(num_bits/4)+2 (1 character for every 4 bits, zero-padded to be + // divisible by 4, and 2 characters for the '0x' prefix). + HEX_STRING = 0, + // MAC address, e.g. 00:11:ab:cd:ef:22. All lowercase, and always 17 + // characters long. + MAC = 1, + // IPv4 address, e.g. 10.0.0.2. + IPV4 = 2, + // IPv6 address, e.g. fe80::21a:11ff:fe17:5f80. All lowercase, formatted + // according to RFC5952. This can be used for any bitwidth of 128 or less. If + // the bitwidth n is less than 128, then by convention only the upper n bits + // can be set. + IPV6 = 3, + // String format, only printable characters. + STRING = 4, +}; + +struct P4AclCounter +{ + sai_object_id_t counter_oid; + bool bytes_enabled; + bool packets_enabled; + P4AclCounter() : bytes_enabled(false), packets_enabled(false), counter_oid(SAI_NULL_OBJECT_ID) + { + } +}; + +struct P4AclMeter +{ + sai_object_id_t meter_oid; + bool enabled; + sai_meter_type_t type; + sai_policer_mode_t mode; + sai_uint64_t cir; + sai_uint64_t cburst; + sai_uint64_t pir; + sai_uint64_t pburst; + + std::map packet_color_actions; + + P4AclMeter() + : enabled(false), meter_oid(SAI_NULL_OBJECT_ID), cir(0), cburst(0), pir(0), pburst(0), + type(SAI_METER_TYPE_PACKETS), mode(SAI_POLICER_MODE_TR_TCM) + { + } +}; + +struct P4AclMirrorSession +{ + std::string name; + std::string key; // KeyGenerator::generateMirrorSessionKey(name) + sai_object_id_t oid; +}; + +struct P4UdfDataMask +{ + std::vector data; + std::vector mask; +}; + +struct P4AclRule +{ + sai_object_id_t acl_table_oid; + sai_object_id_t acl_entry_oid; + std::string acl_table_name; + std::string acl_rule_key; + std::string db_key; + + sai_uint32_t priority; + std::string p4_action; + std::map match_fvs; + std::map action_fvs; + P4AclMeter meter; + P4AclCounter counter; + + sai_uint32_t action_qos_queue_num; + std::string action_redirect_nexthop_key; + // SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS and + // SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS are allowed as key + std::map action_mirror_sessions; + // Stores mapping from SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_{number} to + // udf data and masks pairs in two uin8_t list + std::map udf_data_masks; + std::vector in_ports; + std::vector out_ports; + std::vector in_ports_oids; + std::vector out_ports_oids; +}; + +struct SaiActionWithParam +{ + acl_entry_attr_union_t action; + std::string param_name; + std::string param_value; +}; + +struct SaiMatchField +{ + acl_entry_attr_union_t entry_attr; + acl_table_attr_union_t table_attr; + uint32_t bitwidth; + Format format; +}; + +struct P4UdfField +{ + uint16_t length; // in Bytes + std::string group_id; // {ACL_TABLE_NAME}-{P4_MATCH_FIELD}-{INDEX} + std::string udf_id; // {group_id}-base{base}-offset{offset} + uint16_t offset; // in Bytes + sai_udf_base_t base; +}; + +struct P4AclTableDefinition +{ + std::string acl_table_name; + sai_object_id_t table_oid; + sai_object_id_t group_oid; + sai_object_id_t group_member_oid; + + sai_acl_stage_t stage; + sai_uint32_t size; + sai_uint32_t priority; + std::string meter_unit; + std::string counter_unit; + // go/p4-composite-fields + // Only SAI attributes for IPv6-64bit(IPV6_WORDn) are supported as sai_field + // elements in composite field + std::map> composite_sai_match_fields_lookup; + // go/gpins-acl-udf + // p4_match string to a list of P4UdfFields mapping + std::map> udf_fields_lookup; + // UDF group id to ACL entry attribute index mapping + std::map udf_group_attr_index_lookup; + std::map sai_match_field_lookup; + std::map ip_type_bit_type_lookup; + std::map> rule_action_field_lookup; + std::map> rule_packet_action_color_lookup; + + P4AclTableDefinition() = default; + P4AclTableDefinition(const std::string &acl_table_name, const sai_acl_stage_t stage, const uint32_t priority, + const uint32_t size, const std::string &meter_unit, const std::string &counter_unit) + : acl_table_name(acl_table_name), stage(stage), priority(priority), size(size), meter_unit(meter_unit), + counter_unit(counter_unit){}; +}; + +struct P4UserDefinedTrapHostifTableEntry +{ + sai_object_id_t user_defined_trap; + sai_object_id_t hostif_table_entry; + P4UserDefinedTrapHostifTableEntry() + : user_defined_trap(SAI_NULL_OBJECT_ID), hostif_table_entry(SAI_NULL_OBJECT_ID){}; +}; + +using acl_rule_attr_lookup_t = std::map; +using acl_table_attr_lookup_t = std::map; +using acl_table_attr_format_lookup_t = std::map; +using acl_packet_action_lookup_t = std::map; +using acl_packet_color_lookup_t = std::map; +using acl_packet_color_policer_attr_lookup_t = std::map; +using acl_ip_type_lookup_t = std::map; +using acl_ip_frag_lookup_t = std::map; +using udf_base_lookup_t = std::map; +using acl_packet_vlan_lookup_t = std::map; +using P4AclTableDefinitions = std::map; +using P4AclRuleTables = std::map>; + +#define P4_FORMAT_HEX_STRING "HEX_STRING" +#define P4_FORMAT_MAC "MAC" +#define P4_FORMAT_IPV4 "IPV4" +#define P4_FORMAT_IPV6 "IPV6" +#define P4_FORMAT_STRING "STRING" + +// complete p4 match fields and action list: +// https://docs.google.com/document/d/1gtxJe7aPIJgM2hTLo5gm62DuPJHB31eAyRAsV9zjwW0/edit#heading=h.dzb8jjrtxv49 +#define P4_MATCH_IN_PORT "SAI_ACL_TABLE_ATTR_FIELD_IN_PORT" +#define P4_MATCH_OUT_PORT "SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT" +#define P4_MATCH_IN_PORTS "SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS" +#define P4_MATCH_OUT_PORTS "SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS" +#define P4_MATCH_SRC_IP "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP" +#define P4_MATCH_DST_IP "SAI_ACL_TABLE_ATTR_FIELD_DST_IP" +#define P4_MATCH_INNER_SRC_IP "SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP" +#define P4_MATCH_INNER_DST_IP "SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP" +#define P4_MATCH_SRC_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6" +#define P4_MATCH_DST_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6" +#define P4_MATCH_INNER_SRC_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IPV6" +#define P4_MATCH_INNER_DST_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IPV6" +#define P4_MATCH_SRC_MAC "SAI_ACL_TABLE_ATTR_FIELD_SRC_MAC" +#define P4_MATCH_DST_MAC "SAI_ACL_TABLE_ATTR_FIELD_DST_MAC" +#define P4_MATCH_OUTER_VLAN_ID "SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID" +#define P4_MATCH_OUTER_VLAN_PRI "SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_PRI" +#define P4_MATCH_OUTER_VLAN_CFI "SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_CFI" +#define P4_MATCH_INNER_VLAN_ID "SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_ID" +#define P4_MATCH_INNER_VLAN_PRI "SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_PRI" +#define P4_MATCH_INNER_VLAN_CFI "SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_CFI" +#define P4_MATCH_L4_SRC_PORT "SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT" +#define P4_MATCH_L4_DST_PORT "SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT" +#define P4_MATCH_INNER_L4_SRC_PORT "SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_SRC_PORT" +#define P4_MATCH_INNER_L4_DST_PORT "SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_DST_PORT" +#define P4_MATCH_ETHER_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE" +#define P4_MATCH_INNER_ETHER_TYPE "SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE" +#define P4_MATCH_IP_PROTOCOL "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL" +#define P4_MATCH_INNER_IP_PROTOCOL "SAI_ACL_TABLE_ATTR_FIELD_INNER_IP_PROTOCOL" +#define P4_MATCH_IP_ID "SAI_ACL_TABLE_ATTR_FIELD_IP_IDENTIFICATION" +#define P4_MATCH_DSCP "SAI_ACL_TABLE_ATTR_FIELD_DSCP" +#define P4_MATCH_ECN "SAI_ACL_TABLE_ATTR_FIELD_ECN" +#define P4_MATCH_TTL "SAI_ACL_TABLE_ATTR_FIELD_TTL" +#define P4_MATCH_TOS "SAI_ACL_TABLE_ATTR_FIELD_TOS" +#define P4_MATCH_IP_FLAGS "SAI_ACL_TABLE_ATTR_FIELD_IP_FLAGS" +#define P4_MATCH_TCP_FLAGS "SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS" +#define P4_MATCH_IP_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE" +#define P4_MATCH_IP_FRAG "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_FRAG" +#define P4_MATCH_IPV6_FLOW_LABEL "SAI_ACL_TABLE_ATTR_FIELD_IPV6_FLOW_LABEL" +#define P4_MATCH_TRAFFIC_CLASS "SAI_ACL_TABLE_ATTR_FIELD_TC" +#define P4_MATCH_ICMP_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE" +#define P4_MATCH_ICMP_CODE "SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE" +#define P4_MATCH_ICMPV6_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE" +#define P4_MATCH_ICMPV6_CODE "SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE" +#define P4_MATCH_PACKET_VLAN "SAI_ACL_TABLE_ATTR_FIELD_PACKET_VLAN" +#define P4_MATCH_TUNNEL_VNI "SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI" +#define P4_MATCH_IPV6_NEXT_HEADER "SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER" +#define P4_MATCH_DST_IPV6_WORD3 "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3" +#define P4_MATCH_DST_IPV6_WORD2 "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2" +#define P4_MATCH_SRC_IPV6_WORD3 "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3" +#define P4_MATCH_SRC_IPV6_WORD2 "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2" + +#define P4_ACTION_PACKET_ACTION "SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION" +#define P4_ACTION_REDIRECT "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT" +// Tunnel Endpoint IP. mandatory and valid only when redirect action is to +// SAI_BRIDGE_PORT_TYPE_TUNNEL +#define P4_ACTION_ENDPOINT_IP "SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP" +#define P4_ACTION_MIRROR_INGRESS "SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS" +#define P4_ACTION_MIRROR_EGRESS "SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS" +#define P4_ACTION_FLOOD "SAI_ACL_ENTRY_ATTR_ACTION_FLOOD" +#define P4_ACTION_DECREMENT_TTL "SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL" +#define P4_ACTION_SET_TRAFFIC_CLASS "SAI_ACL_ENTRY_ATTR_ACTION_SET_TC" +#define P4_ACTION_SET_PACKET_COLOR "SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR" +#define P4_ACTION_SET_INNER_VLAN_ID "SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID" +#define P4_ACTION_SET_INNER_VLAN_PRIORITY "SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI" +#define P4_ACTION_SET_OUTER_VLAN_ID "SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID" +#define P4_ACTION_SET_OUTER_VLAN_PRIORITY "SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI" +#define P4_ACTION_SET_SRC_MAC "SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC" +#define P4_ACTION_SET_DST_MAC "SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC" +#define P4_ACTION_SET_SRC_IP "SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP" +#define P4_ACTION_SET_DST_IP "SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP" +#define P4_ACTION_SET_SRC_IPV6 "SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6" +#define P4_ACTION_SET_DST_IPV6 "SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6" +#define P4_ACTION_SET_DSCP "SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP" +#define P4_ACTION_SET_ECN "SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN" +#define P4_ACTION_SET_L4_SRC_PORT "SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT" +#define P4_ACTION_SET_L4_DST_PORT "SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT" +#define P4_ACTION_SET_DO_NOT_LEARN "SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN" +#define P4_ACTION_SET_VRF "SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF" +#define P4_ACTION_SET_QOS_QUEUE "QOS_QUEUE" + +#define P4_PACKET_ACTION_FORWARD "SAI_PACKET_ACTION_FORWARD" +#define P4_PACKET_ACTION_DROP "SAI_PACKET_ACTION_DROP" +#define P4_PACKET_ACTION_COPY "SAI_PACKET_ACTION_COPY" +#define P4_PACKET_ACTION_PUNT "SAI_PACKET_ACTION_TRAP" +#define P4_PACKET_ACTION_LOG "SAI_PACKET_ACTION_LOG" + +#define P4_PACKET_ACTION_REDIRECT "REDIRECT" + +#define P4_PACKET_COLOR_GREEN "SAI_PACKET_COLOR_GREEN" +#define P4_PACKET_COLOR_YELLOW "SAI_PACKET_COLOR_YELLOW" +#define P4_PACKET_COLOR_RED "SAI_PACKET_COLOR_RED" + +#define P4_METER_UNIT_PACKETS "PACKETS" +#define P4_METER_UNIT_BYTES "BYTES" + +#define P4_COUNTER_UNIT_PACKETS "PACKETS" +#define P4_COUNTER_UNIT_BYTES "BYTES" +#define P4_COUNTER_UNIT_BOTH "BOTH" + +// IP_TYPE encode in p4. go/p4-ip-type +#define P4_IP_TYPE_BIT_IP "IP" +#define P4_IP_TYPE_BIT_IPV4ANY "IPV4ANY" +#define P4_IP_TYPE_BIT_IPV6ANY "IPV6ANY" +#define P4_IP_TYPE_BIT_ARP "ARP" +#define P4_IP_TYPE_BIT_ARP_REQUEST "ARP_REQUEST" +#define P4_IP_TYPE_BIT_ARP_REPLY "ARP_REPLY" + +#define P4_IP_TYPE_ANY "SAI_ACL_IP_TYPE_ANY" +#define P4_IP_TYPE_IP "SAI_ACL_IP_TYPE_IP" +#define P4_IP_TYPE_NON_IP "SAI_ACL_IP_TYPE_NON_IP" +#define P4_IP_TYPE_IPV4ANY "SAI_ACL_IP_TYPE_IPV4ANY" +#define P4_IP_TYPE_NON_IPV4 "SAI_ACL_IP_TYPE_NON_IPV4" +#define P4_IP_TYPE_IPV6ANY "SAI_ACL_IP_TYPE_IPV6ANY" +#define P4_IP_TYPE_NON_IPV6 "SAI_ACL_IP_TYPE_NON_IPV6" +#define P4_IP_TYPE_ARP "SAI_ACL_IP_TYPE_ARP" +#define P4_IP_TYPE_ARP_REQUEST "SAI_ACL_IP_TYPE_ARP_REQUEST" +#define P4_IP_TYPE_ARP_REPLY "SAI_ACL_IP_TYPE_ARP_REPLY" + +#define P4_IP_FRAG_ANY "SAI_ACL_IP_FRAG_ANY" +#define P4_IP_FRAG_NON_FRAG "SAI_ACL_IP_FRAG_NON_FRAG" +#define P4_IP_FRAG_NON_FRAG_OR_HEAD "SAI_ACL_IP_FRAG_NON_FRAG_OR_HEAD" +#define P4_IP_FRAG_HEAD "SAI_ACL_IP_FRAG_HEAD" +#define P4_IP_FRAG_NON_HEAD "SAI_ACL_IP_FRAG_NON_HEAD" + +#define P4_PACKET_VLAN_UNTAG "SAI_PACKET_VLAN_UNTAG" +#define P4_PACKET_VLAN_SINGLE_OUTER_TAG "SAI_PACKET_VLAN_SINGLE_OUTER_TAG" +#define P4_PACKET_VLAN_DOUBLE_TAG "SAI_PACKET_VLAN_DOUBLE_TAG" + +#define P4_UDF_MATCH_DEFAULT "acl_default_udf_match" + +// ACL counters update interval in the COUNTERS_DB +// Value is in seconds. Should not be less than 5 seconds +// (in worst case update of 1265 counters takes almost 5 sec) +#define P4_COUNTERS_READ_INTERVAL 10 + +#define P4_COUNTER_STATS_PACKETS "packets" +#define P4_COUNTER_STATS_BYTES "bytes" +#define P4_COUNTER_STATS_GREEN_PACKETS "green_packets" +#define P4_COUNTER_STATS_GREEN_BYTES "green_bytes" +#define P4_COUNTER_STATS_YELLOW_PACKETS "yellow_packets" +#define P4_COUNTER_STATS_YELLOW_BYTES "yellow_bytes" +#define P4_COUNTER_STATS_RED_PACKETS "red_packets" +#define P4_COUNTER_STATS_RED_BYTES "red_bytes" + +#define P4_UDF_BASE_L2 "SAI_UDF_BASE_L2" +#define P4_UDF_BASE_L3 "SAI_UDF_BASE_L3" +#define P4_UDF_BASE_L4 "SAI_UDF_BASE_L4" + +#define GENL_PACKET_TRAP_GROUP_NAME_PREFIX "trap.group.cpu.queue." + +#define WHITESPACE " " +#define EMPTY_STRING "" +#define P4_CPU_QUEUE_MAX_NUM 8 +#define IPV6_SINGLE_WORD_BYTES_LENGTH 4 +#define BYTE_BITWIDTH 8 + +static const std::map formatLookup = { + {P4_FORMAT_HEX_STRING, Format::HEX_STRING}, + {P4_FORMAT_MAC, Format::MAC}, + {P4_FORMAT_IPV4, Format::IPV4}, + {P4_FORMAT_IPV6, Format::IPV6}, + {P4_FORMAT_STRING, Format::STRING}, +}; + +static const acl_table_attr_lookup_t aclMatchTableAttrLookup = { + {P4_MATCH_IN_PORT, SAI_ACL_TABLE_ATTR_FIELD_IN_PORT}, + {P4_MATCH_OUT_PORT, SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT}, + {P4_MATCH_IN_PORTS, SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS}, + {P4_MATCH_OUT_PORTS, SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS}, + {P4_MATCH_SRC_MAC, SAI_ACL_TABLE_ATTR_FIELD_SRC_MAC}, + {P4_MATCH_DST_MAC, SAI_ACL_TABLE_ATTR_FIELD_DST_MAC}, + {P4_MATCH_SRC_IP, SAI_ACL_TABLE_ATTR_FIELD_SRC_IP}, + {P4_MATCH_DST_IP, SAI_ACL_TABLE_ATTR_FIELD_DST_IP}, + {P4_MATCH_INNER_SRC_IP, SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP}, + {P4_MATCH_INNER_DST_IP, SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP}, + {P4_MATCH_SRC_IPV6, SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6}, + {P4_MATCH_DST_IPV6, SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6}, + {P4_MATCH_INNER_SRC_IPV6, SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IPV6}, + {P4_MATCH_INNER_DST_IPV6, SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IPV6}, + {P4_MATCH_OUTER_VLAN_ID, SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID}, + {P4_MATCH_OUTER_VLAN_PRI, SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_PRI}, + {P4_MATCH_OUTER_VLAN_CFI, SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_CFI}, + {P4_MATCH_INNER_VLAN_ID, SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_ID}, + {P4_MATCH_INNER_VLAN_PRI, SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_PRI}, + {P4_MATCH_INNER_VLAN_CFI, SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_CFI}, + {P4_MATCH_L4_SRC_PORT, SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT}, + {P4_MATCH_L4_DST_PORT, SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT}, + {P4_MATCH_INNER_L4_SRC_PORT, SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_SRC_PORT}, + {P4_MATCH_INNER_L4_DST_PORT, SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_DST_PORT}, + {P4_MATCH_ETHER_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE}, + {P4_MATCH_IP_PROTOCOL, SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL}, + {P4_MATCH_INNER_IP_PROTOCOL, SAI_ACL_TABLE_ATTR_FIELD_INNER_IP_PROTOCOL}, + {P4_MATCH_IP_ID, SAI_ACL_TABLE_ATTR_FIELD_IP_IDENTIFICATION}, + {P4_MATCH_DSCP, SAI_ACL_TABLE_ATTR_FIELD_DSCP}, + {P4_MATCH_ECN, SAI_ACL_TABLE_ATTR_FIELD_ECN}, + {P4_MATCH_TTL, SAI_ACL_TABLE_ATTR_FIELD_TTL}, + {P4_MATCH_TOS, SAI_ACL_TABLE_ATTR_FIELD_TOS}, + {P4_MATCH_IP_FLAGS, SAI_ACL_TABLE_ATTR_FIELD_IP_FLAGS}, + {P4_MATCH_TCP_FLAGS, SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS}, + {P4_MATCH_IP_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE}, + {P4_MATCH_IP_FRAG, SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_FRAG}, + {P4_MATCH_IPV6_FLOW_LABEL, SAI_ACL_TABLE_ATTR_FIELD_IPV6_FLOW_LABEL}, + {P4_MATCH_TRAFFIC_CLASS, SAI_ACL_TABLE_ATTR_FIELD_TC}, + {P4_MATCH_ICMP_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE}, + {P4_MATCH_ICMP_CODE, SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE}, + {P4_MATCH_ICMPV6_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE}, + {P4_MATCH_ICMPV6_CODE, SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE}, + {P4_MATCH_PACKET_VLAN, SAI_ACL_TABLE_ATTR_FIELD_PACKET_VLAN}, + {P4_MATCH_TUNNEL_VNI, SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI}, + {P4_MATCH_IPV6_NEXT_HEADER, SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER}, +}; + +static const acl_table_attr_format_lookup_t aclMatchTableAttrFormatLookup = { + {SAI_ACL_TABLE_ATTR_FIELD_IN_PORT, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_SRC_MAC, Format::MAC}, + {SAI_ACL_TABLE_ATTR_FIELD_DST_MAC, Format::MAC}, + {SAI_ACL_TABLE_ATTR_FIELD_SRC_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_DST_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_PRI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_CFI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_ID, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_PRI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_CFI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_SRC_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_DST_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_IP_PROTOCOL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IP_IDENTIFICATION, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_DSCP, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ECN, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TTL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TOS, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IP_FLAGS, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_FRAG, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IPV6_FLOW_LABEL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TC, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_PACKET_VLAN, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER, Format::HEX_STRING}, +}; + +static const acl_table_attr_lookup_t aclCompositeMatchTableAttrLookup = { + {P4_MATCH_DST_IPV6_WORD3, SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3}, + {P4_MATCH_DST_IPV6_WORD2, SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2}, + {P4_MATCH_SRC_IPV6_WORD3, SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3}, + {P4_MATCH_SRC_IPV6_WORD2, SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2}, +}; + +static const acl_rule_attr_lookup_t aclMatchEntryAttrLookup = { + {P4_MATCH_IN_PORT, SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT}, + {P4_MATCH_OUT_PORT, SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT}, + {P4_MATCH_IN_PORTS, SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS}, + {P4_MATCH_OUT_PORTS, SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS}, + {P4_MATCH_SRC_MAC, SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC}, + {P4_MATCH_DST_MAC, SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC}, + {P4_MATCH_SRC_IP, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP}, + {P4_MATCH_DST_IP, SAI_ACL_ENTRY_ATTR_FIELD_DST_IP}, + {P4_MATCH_INNER_SRC_IP, SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP}, + {P4_MATCH_INNER_DST_IP, SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IP}, + {P4_MATCH_SRC_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6}, + {P4_MATCH_DST_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6}, + {P4_MATCH_INNER_SRC_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IPV6}, + {P4_MATCH_INNER_DST_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IPV6}, + {P4_MATCH_OUTER_VLAN_ID, SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID}, + {P4_MATCH_OUTER_VLAN_PRI, SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_PRI}, + {P4_MATCH_OUTER_VLAN_CFI, SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_CFI}, + {P4_MATCH_INNER_VLAN_ID, SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_ID}, + {P4_MATCH_INNER_VLAN_PRI, SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_PRI}, + {P4_MATCH_INNER_VLAN_CFI, SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_CFI}, + {P4_MATCH_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT}, + {P4_MATCH_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_L4_DST_PORT}, + {P4_MATCH_INNER_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_SRC_PORT}, + {P4_MATCH_INNER_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT}, + {P4_MATCH_ETHER_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE}, + {P4_MATCH_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_IP_PROTOCOL}, + {P4_MATCH_INNER_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL}, + {P4_MATCH_IP_ID, SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION}, + {P4_MATCH_DSCP, SAI_ACL_ENTRY_ATTR_FIELD_DSCP}, + {P4_MATCH_ECN, SAI_ACL_ENTRY_ATTR_FIELD_ECN}, + {P4_MATCH_TTL, SAI_ACL_ENTRY_ATTR_FIELD_TTL}, + {P4_MATCH_TOS, SAI_ACL_ENTRY_ATTR_FIELD_TOS}, + {P4_MATCH_IP_FLAGS, SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS}, + {P4_MATCH_TCP_FLAGS, SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS}, + {P4_MATCH_IP_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE}, + {P4_MATCH_IP_FRAG, SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG}, + {P4_MATCH_IPV6_FLOW_LABEL, SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL}, + {P4_MATCH_TRAFFIC_CLASS, SAI_ACL_ENTRY_ATTR_FIELD_TC}, + {P4_MATCH_ICMP_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ICMP_TYPE}, + {P4_MATCH_ICMP_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMP_CODE}, + {P4_MATCH_ICMPV6_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_TYPE}, + {P4_MATCH_ICMPV6_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_CODE}, + {P4_MATCH_PACKET_VLAN, SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN}, + {P4_MATCH_TUNNEL_VNI, SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI}, + {P4_MATCH_IPV6_NEXT_HEADER, SAI_ACL_ENTRY_ATTR_FIELD_IPV6_NEXT_HEADER}, +}; + +static const acl_rule_attr_lookup_t aclCompositeMatchEntryAttrLookup = { + {P4_MATCH_DST_IPV6_WORD3, SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6_WORD3}, + {P4_MATCH_DST_IPV6_WORD2, SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6_WORD2}, + {P4_MATCH_SRC_IPV6_WORD3, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6_WORD3}, + {P4_MATCH_SRC_IPV6_WORD2, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6_WORD2}, +}; + +static const acl_packet_action_lookup_t aclPacketActionLookup = { + {P4_PACKET_ACTION_FORWARD, SAI_PACKET_ACTION_FORWARD}, {P4_PACKET_ACTION_DROP, SAI_PACKET_ACTION_DROP}, + {P4_PACKET_ACTION_COPY, SAI_PACKET_ACTION_COPY}, {P4_PACKET_ACTION_PUNT, SAI_PACKET_ACTION_TRAP}, + {P4_PACKET_ACTION_LOG, SAI_PACKET_ACTION_LOG}, +}; + +static const acl_rule_attr_lookup_t aclActionLookup = { + {P4_ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION}, + {P4_ACTION_REDIRECT, SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT}, + {P4_ACTION_ENDPOINT_IP, SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP}, + {P4_ACTION_FLOOD, SAI_ACL_ENTRY_ATTR_ACTION_FLOOD}, + {P4_ACTION_MIRROR_INGRESS, SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS}, + {P4_ACTION_MIRROR_EGRESS, SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS}, + {P4_ACTION_DECREMENT_TTL, SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL}, + {P4_ACTION_SET_TRAFFIC_CLASS, SAI_ACL_ENTRY_ATTR_ACTION_SET_TC}, + {P4_ACTION_SET_PACKET_COLOR, SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR}, + {P4_ACTION_SET_INNER_VLAN_ID, SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID}, + {P4_ACTION_SET_INNER_VLAN_PRIORITY, SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI}, + {P4_ACTION_SET_OUTER_VLAN_ID, SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID}, + {P4_ACTION_SET_OUTER_VLAN_PRIORITY, SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI}, + {P4_ACTION_SET_SRC_MAC, SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC}, + {P4_ACTION_SET_DST_MAC, SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC}, + {P4_ACTION_SET_SRC_IP, SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP}, + {P4_ACTION_SET_DST_IP, SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP}, + {P4_ACTION_SET_SRC_IPV6, SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6}, + {P4_ACTION_SET_DST_IPV6, SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6}, + {P4_ACTION_SET_DSCP, SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP}, + {P4_ACTION_SET_ECN, SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN}, + {P4_ACTION_SET_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT}, + {P4_ACTION_SET_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT}, + {P4_ACTION_SET_QOS_QUEUE, SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID}, + {P4_ACTION_SET_DO_NOT_LEARN, SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN}, + {P4_ACTION_SET_VRF, SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF}, +}; + +static const acl_packet_color_policer_attr_lookup_t aclPacketColorPolicerAttrLookup = { + {P4_PACKET_COLOR_GREEN, SAI_POLICER_ATTR_GREEN_PACKET_ACTION}, + {P4_PACKET_COLOR_YELLOW, SAI_POLICER_ATTR_YELLOW_PACKET_ACTION}, + {P4_PACKET_COLOR_RED, SAI_POLICER_ATTR_RED_PACKET_ACTION}, +}; + +static const acl_packet_color_lookup_t aclPacketColorLookup = { + {P4_PACKET_COLOR_GREEN, SAI_PACKET_COLOR_GREEN}, + {P4_PACKET_COLOR_YELLOW, SAI_PACKET_COLOR_YELLOW}, + {P4_PACKET_COLOR_RED, SAI_PACKET_COLOR_RED}, +}; + +static const std::set aclIpTypeBitSet = { + P4_IP_TYPE_BIT_IP, P4_IP_TYPE_BIT_IPV4ANY, P4_IP_TYPE_BIT_IPV6ANY, + P4_IP_TYPE_BIT_ARP, P4_IP_TYPE_BIT_ARP_REQUEST, P4_IP_TYPE_BIT_ARP_REPLY, +}; + +static const acl_ip_type_lookup_t aclIpTypeLookup = { + {P4_IP_TYPE_ANY, SAI_ACL_IP_TYPE_ANY}, + {P4_IP_TYPE_IP, SAI_ACL_IP_TYPE_IP}, + {P4_IP_TYPE_NON_IP, SAI_ACL_IP_TYPE_NON_IP}, + {P4_IP_TYPE_IPV4ANY, SAI_ACL_IP_TYPE_IPV4ANY}, + {P4_IP_TYPE_NON_IPV4, SAI_ACL_IP_TYPE_NON_IPV4}, + {P4_IP_TYPE_IPV6ANY, SAI_ACL_IP_TYPE_IPV6ANY}, + {P4_IP_TYPE_NON_IPV6, SAI_ACL_IP_TYPE_NON_IPV6}, + {P4_IP_TYPE_ARP, SAI_ACL_IP_TYPE_ARP}, + {P4_IP_TYPE_ARP_REQUEST, SAI_ACL_IP_TYPE_ARP_REQUEST}, + {P4_IP_TYPE_ARP_REPLY, SAI_ACL_IP_TYPE_ARP_REPLY}, +}; + +static const acl_ip_frag_lookup_t aclIpFragLookup = { + {P4_IP_FRAG_ANY, SAI_ACL_IP_FRAG_ANY}, + {P4_IP_FRAG_NON_FRAG, SAI_ACL_IP_FRAG_NON_FRAG}, + {P4_IP_FRAG_NON_FRAG_OR_HEAD, SAI_ACL_IP_FRAG_NON_FRAG_OR_HEAD}, + {P4_IP_FRAG_HEAD, SAI_ACL_IP_FRAG_HEAD}, + {P4_IP_FRAG_NON_HEAD, SAI_ACL_IP_FRAG_NON_HEAD}, +}; + +static const acl_packet_vlan_lookup_t aclPacketVlanLookup = { + {P4_PACKET_VLAN_UNTAG, SAI_PACKET_VLAN_UNTAG}, + {P4_PACKET_VLAN_SINGLE_OUTER_TAG, SAI_PACKET_VLAN_SINGLE_OUTER_TAG}, + {P4_PACKET_VLAN_DOUBLE_TAG, SAI_PACKET_VLAN_DOUBLE_TAG}, +}; + +static const udf_base_lookup_t udfBaseLookup = { + {P4_UDF_BASE_L2, SAI_UDF_BASE_L2}, + {P4_UDF_BASE_L3, SAI_UDF_BASE_L3}, + {P4_UDF_BASE_L4, SAI_UDF_BASE_L4}, +}; + +static std::map aclCounterColoredPacketsStatsIdMap = { + {SAI_POLICER_ATTR_GREEN_PACKET_ACTION, SAI_POLICER_STAT_GREEN_PACKETS}, + {SAI_POLICER_ATTR_YELLOW_PACKET_ACTION, SAI_POLICER_STAT_YELLOW_PACKETS}, + {SAI_POLICER_ATTR_RED_PACKET_ACTION, SAI_POLICER_STAT_RED_PACKETS}, +}; + +static std::map aclCounterColoredBytesStatsIdMap = { + {SAI_POLICER_ATTR_GREEN_PACKET_ACTION, SAI_POLICER_STAT_GREEN_BYTES}, + {SAI_POLICER_ATTR_YELLOW_PACKET_ACTION, SAI_POLICER_STAT_YELLOW_BYTES}, + {SAI_POLICER_ATTR_RED_PACKET_ACTION, SAI_POLICER_STAT_RED_BYTES}, +}; + +static std::map aclCounterStatsIdNameMap = { + {SAI_POLICER_STAT_GREEN_PACKETS, P4_COUNTER_STATS_GREEN_PACKETS}, + {SAI_POLICER_STAT_YELLOW_PACKETS, P4_COUNTER_STATS_YELLOW_PACKETS}, + {SAI_POLICER_STAT_RED_PACKETS, P4_COUNTER_STATS_RED_PACKETS}, + {SAI_POLICER_STAT_GREEN_BYTES, P4_COUNTER_STATS_GREEN_BYTES}, + {SAI_POLICER_STAT_YELLOW_BYTES, P4_COUNTER_STATS_YELLOW_BYTES}, + {SAI_POLICER_STAT_RED_BYTES, P4_COUNTER_STATS_RED_BYTES}, +}; + +// Trim tailing and leading whitespace +std::string trim(const std::string &s); + +// Parse ACL table definition APP DB entry action field to P4ActionParamName +// action_list and P4PacketActionWithColor action_color_list +bool parseAclTableAppDbActionField(const std::string &aggr_actions_str, std::vector *action_list, + std::vector *action_color_list); + +// Validate and set match field with kind:sai_field. Caller methods are +// responsible to verify the kind before calling this method +ReturnCode validateAndSetSaiMatchFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, + std::map *sai_match_field_lookup, + std::map *ip_type_bit_type_lookup); + +// Validate and set composite match field element with kind:sai_field. Composite +// SAI field only support IPv6-64bit now (IPV6_WORDn) +ReturnCode validateAndSetCompositeElementSaiFieldJson( + const nlohmann::json &element_match_json, const std::string &p4_match, + std::map> *composite_sai_match_fields_lookup, + const std::string &format_str = EMPTY_STRING); + +// Validate and set UDF match field with kind:udf. Caller methods are +// responsible for verifying the kind and format before calling this method +ReturnCode validateAndSetUdfFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, const std::string &acl_table_name, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookup); + +// Only two cases are allowed in composite fields: +// 1. IPV6_64bit(IPV6_WORD3 and IPV6_WORD2 in elements, kind:sai_field, +// format:IPV6) +// 2. Generic UDF(UDF in elements, kind:udf, format:HEX_STRING) +ReturnCode validateAndSetCompositeMatchFieldJson( + const nlohmann::json &aggr_match_json, const std::string &p4_match, const std::string &aggr_match_str, + const std::string &acl_table_name, + std::map> *composite_sai_match_fields_lookup, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookup); + +ReturnCode buildAclTableDefinitionMatchFieldValues(const std::map &match_field_lookup, + P4AclTableDefinition *acl_table); + +// Build SaiActionWithParam action map for ACL table definition +// by P4ActionParamName action map +ReturnCode buildAclTableDefinitionActionFieldValues( + const std::map> &action_field_lookup, + std::map> *aggr_sai_actions_lookup); + +bool isSetUserTrapActionInAclTableDefinition( + const std::map> &aggr_sai_actions_lookup); + +// Build packet color(sai_policer_attr_t) to packet action(sai_packet_action_t) +// map for ACL table definition by P4PacketActionWithColor action map. If packet +// color is empty, then the packet action should add as a SaiActionWithParam +ReturnCode buildAclTableDefinitionActionColorFieldValues( + const std::map> &action_color_lookup, + std::map> *aggr_sai_actions_lookup, + std::map> *aggr_sai_action_color_lookup); + +// Set IP_TYPE in match field +bool setMatchFieldIpType(const std::string &attr_value, sai_attribute_value_t *value, + const std::string &ip_type_bit_type); + +// Set composite match field with sai_field type. Currently only ACL entry +// attributes listed in aclCompositeMatchTableAttrLookup are supported +ReturnCode setCompositeSaiMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value); + +// Set composite match field with sai_field type. +ReturnCode setUdfMatchValue(const P4UdfField &udf_field, const std::string &attr_value, sai_attribute_value_t *value, + P4UdfDataMask *udf_data_mask, uint16_t bytes_offset); + +// Compares the action value difference if the action field is present in both +// new and old ACL rules. Returns true if action values are different. +bool isDiffActionFieldValue(const acl_entry_attr_union_t attr_name, const sai_attribute_value_t &value, + const sai_attribute_value_t &old_value, const P4AclRule &acl_rule, + const P4AclRule &old_acl_rule); +} // namespace p4orch diff --git a/orchagent/p4orch/mirror_session_manager.cpp b/orchagent/p4orch/mirror_session_manager.cpp new file mode 100644 index 0000000000..067bc5aa1a --- /dev/null +++ b/orchagent/p4orch/mirror_session_manager.cpp @@ -0,0 +1,726 @@ +#include "p4orch/mirror_session_manager.h" + +#include "json.hpp" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "swss/logger.h" +#include "swssnet.h" + +extern PortsOrch *gPortsOrch; +extern sai_mirror_api_t *sai_mirror_api; +extern sai_object_id_t gSwitchId; + +namespace p4orch +{ + +void MirrorSessionManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + SWSS_LOG_ENTER(); + m_entries.push_back(entry); +} + +void MirrorSessionManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeP4MirrorSessionAppDbEntry(key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + const std::string mirror_session_key = KeyGenerator::generateMirrorSessionKey(app_db_entry.mirror_session_id); + + // Fulfill the operation. + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *mirror_session_entry = getMirrorSessionEntry(mirror_session_key); + if (mirror_session_entry == nullptr) + { + // Create new mirror session. + status = processAddRequest(app_db_entry); + } + else + { + // Modify existing mirror session. + status = processUpdateRequest(app_db_entry, mirror_session_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete mirror session. + status = processDeleteRequest(mirror_session_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +ReturnCodeOr MirrorSessionManager::deserializeP4MirrorSessionAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4MirrorSessionAppDbEntry app_db_entry = {}; + + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.mirror_session_id = j[prependMatchField(p4orch::kMirrorSessionId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize mirror session id"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kPort)) + { + swss::Port port; + if (!gPortsOrch->getPort(value, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(value); + } + if (port.m_type != Port::Type::PHY) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Port " << QuotedVar(value) << "'s type " << port.m_type + << " is not physical and is invalid as destination port for " + "mirror packet."; + } + app_db_entry.port = value; + app_db_entry.has_port = true; + } + else if (field == prependParamField(p4orch::kSrcIp)) + { + try + { + app_db_entry.src_ip = swss::IpAddress(value); + app_db_entry.has_src_ip = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kDstIp)) + { + try + { + app_db_entry.dst_ip = swss::IpAddress(value); + app_db_entry.has_dst_ip = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kSrcMac)) + { + try + { + app_db_entry.src_mac = swss::MacAddress(value); + app_db_entry.has_src_mac = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kDstMac)) + { + try + { + app_db_entry.dst_mac = swss::MacAddress(value); + app_db_entry.has_dst_mac = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kTtl)) + { + try + { + app_db_entry.ttl = static_cast(std::stoul(value, 0, /*base=*/16)); + app_db_entry.has_ttl = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid TTL " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kTos)) + { + try + { + app_db_entry.tos = static_cast(std::stoul(value, 0, /*base=*/16)); + app_db_entry.has_tos = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid TOS " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == p4orch::kAction) + { + if (value != p4orch::kMirrorAsIpv4Erspan) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action value " << QuotedVar(value) << " is not mirror_as_ipv4_erspan."; + } + } + else if (field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +P4MirrorSessionEntry *MirrorSessionManager::getMirrorSessionEntry(const std::string &mirror_session_key) +{ + auto it = m_mirrorSessionTable.find(mirror_session_key); + + if (it == m_mirrorSessionTable.end()) + { + return nullptr; + } + else + { + return &it->second; + } +} + +ReturnCode MirrorSessionManager::processAddRequest(const P4MirrorSessionAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status; + // Check if all required fields for add operation are given in APP DB entry. + if (app_db_entry.has_port && app_db_entry.has_src_ip && app_db_entry.has_dst_ip && app_db_entry.has_src_mac && + app_db_entry.has_dst_mac && app_db_entry.has_ttl && app_db_entry.has_tos) + { + P4MirrorSessionEntry mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(app_db_entry.mirror_session_id), + /*mirror_session_oid=*/0, app_db_entry.mirror_session_id, app_db_entry.port, app_db_entry.src_ip, + app_db_entry.dst_ip, app_db_entry.src_mac, app_db_entry.dst_mac, app_db_entry.ttl, app_db_entry.tos); + status = createMirrorSession(std::move(mirror_session_entry)); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Mirror session entry with mirror_session_id " << QuotedVar(app_db_entry.mirror_session_id) + << " doesn't specify all required fields for ADD operation."; + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + + return status; +} + +ReturnCode MirrorSessionManager::createMirrorSession(P4MirrorSessionEntry mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the mirror session in centralized mapper. + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry.mirror_session_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Mirror session with key " + << QuotedVar(mirror_session_entry.mirror_session_key) + << " already exists in centralized mapper"); + } + + swss::Port port; + if (!gPortsOrch->getPort(mirror_session_entry.port, port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(mirror_session_entry.port)); + } + if (port.m_type != Port::Type::PHY) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Port " << QuotedVar(mirror_session_entry.port) << "'s type " << port.m_type + << " is not physical and is invalid as destination " + "port for mirror packet."); + } + + // Prepare attributes for the SAI creation call. + std::vector attrs; + sai_attribute_t attr; + + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = port.m_port_id; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TYPE; + attr.value.s32 = SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE; + attr.value.s32 = SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION; + attr.value.u8 = MIRROR_SESSION_DEFAULT_IP_HDR_VER; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = mirror_session_entry.tos; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = mirror_session_entry.ttl; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, mirror_session_entry.src_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, mirror_session_entry.dst_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, mirror_session_entry.src_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, mirror_session_entry.dst_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE; + attr.value.u16 = GRE_PROTOCOL_ERSPAN; + attrs.push_back(attr); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->create_mirror_session(&mirror_session_entry.mirror_session_oid, gSwitchId, + (uint32_t)attrs.size(), attrs.data()), + "Failed to create mirror session " << QuotedVar(mirror_session_entry.mirror_session_key)); + + // On successful creation, increment ref count. + gPortsOrch->increasePortRefCount(mirror_session_entry.port); + + // Add the key to OID map to centralized mapper. + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry.mirror_session_key, + mirror_session_entry.mirror_session_oid); + + // Add created entry to internal table. + m_mirrorSessionTable.emplace(mirror_session_entry.mirror_session_key, mirror_session_entry); + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::processUpdateRequest(const P4MirrorSessionAppDbEntry &app_db_entry, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the mirror session in mirror manager and centralized + // mapper. + if (existing_mirror_session_entry == nullptr) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("existing_mirror_session_entry is nullptr"); + } + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_MIRROR_SESSION, existing_mirror_session_entry->mirror_session_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Mirror session with key " + << QuotedVar(existing_mirror_session_entry->mirror_session_key) + << " doesn't exist in centralized mapper"); + } + + P4MirrorSessionEntry mirror_session_entry_before_update(*existing_mirror_session_entry); + + // Because SAI mirror set API sets attr one at a time, it is possible attr + // updates fail in the middle. Up on failure, all successful operations need + // to be undone. + ReturnCode ret; + bool update_fail_in_middle = false; + if (!update_fail_in_middle && app_db_entry.has_port) + { + ret = setPort(app_db_entry.port, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_src_ip) + { + ret = setSrcIp(app_db_entry.src_ip, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_dst_ip) + { + ret = setDstIp(app_db_entry.dst_ip, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_src_mac) + { + ret = setSrcMac(app_db_entry.src_mac, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_dst_mac) + { + ret = setDstMac(app_db_entry.dst_mac, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_ttl) + { + ret = setTtl(app_db_entry.ttl, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_tos) + { + ret = setTos(app_db_entry.tos, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + + if (update_fail_in_middle) + { + ReturnCode status = setMirrorSessionEntry(mirror_session_entry_before_update, existing_mirror_session_entry); + if (!status.ok()) + { + ret << "Failed to recover mirror session entry to the state before " + "update operation."; + SWSS_RAISE_CRITICAL_STATE("Failed to recover mirror session entry to the state before update " + "operation."); + } + } + + return ret; +} + +ReturnCode MirrorSessionManager::setPort(const std::string &new_port_name, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_port_name == existing_mirror_session_entry->port) + { + return ReturnCode(); + } + + swss::Port new_port; + if (!gPortsOrch->getPort(new_port_name, new_port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(new_port_name)); + } + if (new_port.m_type != Port::Type::PHY) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Port " << QuotedVar(new_port.m_alias) << "'s type " << new_port.m_type + << " is not physical and is invalid as destination " + "port for mirror packet."); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = new_port.m_port_id; + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new port " << QuotedVar(new_port.m_alias) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update ref count. + gPortsOrch->decreasePortRefCount(existing_mirror_session_entry->port); + gPortsOrch->increasePortRefCount(new_port.m_alias); + + // Update the entry in table + existing_mirror_session_entry->port = new_port_name; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setSrcIp(const swss::IpAddress &new_src_ip, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_src_ip == existing_mirror_session_entry->src_ip) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, new_src_ip); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new src_ip " << QuotedVar(new_src_ip.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->src_ip = new_src_ip; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setDstIp(const swss::IpAddress &new_dst_ip, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_dst_ip == existing_mirror_session_entry->dst_ip) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, new_dst_ip); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new dst_ip " << QuotedVar(new_dst_ip.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->dst_ip = new_dst_ip; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setSrcMac(const swss::MacAddress &new_src_mac, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_src_mac == existing_mirror_session_entry->src_mac) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, new_src_mac.getMac(), sizeof(sai_mac_t)); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new src_mac " << QuotedVar(new_src_mac.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->src_mac = new_src_mac; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setDstMac(const swss::MacAddress &new_dst_mac, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_dst_mac == existing_mirror_session_entry->dst_mac) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, new_dst_mac.getMac(), sizeof(sai_mac_t)); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new dst_mac " << QuotedVar(new_dst_mac.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->dst_mac = new_dst_mac; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setTtl(uint8_t new_ttl, P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_ttl == existing_mirror_session_entry->ttl) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = new_ttl; + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new ttl " << new_ttl << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->ttl = new_ttl; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setTos(uint8_t new_tos, P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_tos == existing_mirror_session_entry->tos) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = new_tos; + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new tos " << new_tos << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->tos = new_tos; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setMirrorSessionEntry(const P4MirrorSessionEntry &intent_mirror_session_entry, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status; + + if (intent_mirror_session_entry.port != existing_mirror_session_entry->port) + { + status = setPort(intent_mirror_session_entry.port, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.src_ip != existing_mirror_session_entry->src_ip) + { + status = setSrcIp(intent_mirror_session_entry.src_ip, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.dst_ip != existing_mirror_session_entry->dst_ip) + { + status = setDstIp(intent_mirror_session_entry.dst_ip, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.src_mac != existing_mirror_session_entry->src_mac) + { + status = setSrcMac(intent_mirror_session_entry.src_mac, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.dst_mac != existing_mirror_session_entry->dst_mac) + { + status = setDstMac(intent_mirror_session_entry.dst_mac, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.ttl != existing_mirror_session_entry->ttl) + { + status = setTtl(intent_mirror_session_entry.ttl, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.tos != existing_mirror_session_entry->tos) + { + status = setTos(intent_mirror_session_entry.tos, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + + return status; +} + +ReturnCode MirrorSessionManager::processDeleteRequest(const std::string &mirror_session_key) +{ + SWSS_LOG_ENTER(); + + const P4MirrorSessionEntry *mirror_session_entry = getMirrorSessionEntry(mirror_session_key); + if (mirror_session_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Mirror session with key " << QuotedVar(mirror_session_key) + << " does not exist in mirror session manager"); + } + + // Check if there is anything referring to the mirror session before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for mirror session " + << QuotedVar(mirror_session_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "Mirror session " << QuotedVar(mirror_session_entry->mirror_session_key) + << " referenced by other objects (ref_count = " << ref_count); + } + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_mirror_api->remove_mirror_session(mirror_session_entry->mirror_session_oid), + "Failed to remove mirror session " + << QuotedVar(mirror_session_entry->mirror_session_key)); + + // On successful deletion, decrement ref count. + gPortsOrch->decreasePortRefCount(mirror_session_entry->port); + + // Delete the key to OID map from centralized mapper. + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry->mirror_session_key); + + // Delete entry from internal table. + m_mirrorSessionTable.erase(mirror_session_entry->mirror_session_key); + + return ReturnCode(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/mirror_session_manager.h b/orchagent/p4orch/mirror_session_manager.h new file mode 100644 index 0000000000..c41dc07eb3 --- /dev/null +++ b/orchagent/p4orch/mirror_session_manager.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" +#include "swss/ipaddress.h" +#include "swss/macaddress.h" +#include "swss/rediscommand.h" +extern "C" +{ +#include "sai.h" +} + +#define MIRROR_SESSION_DEFAULT_IP_HDR_VER 4 +#define GRE_PROTOCOL_ERSPAN 0x88be + +namespace p4orch +{ +namespace test +{ +class MirrorSessionManagerTest; +} // namespace test + +struct P4MirrorSessionEntry +{ + P4MirrorSessionEntry(const std::string &mirror_session_key, sai_object_id_t mirror_session_oid, + const std::string &mirror_session_id, const std::string &port, const swss::IpAddress &src_ip, + const swss::IpAddress &dst_ip, const swss::MacAddress &src_mac, + const swss::MacAddress &dst_mac, uint8_t ttl, uint8_t tos) + : mirror_session_key(mirror_session_key), mirror_session_oid(mirror_session_oid), + mirror_session_id(mirror_session_id), port(port), src_ip(src_ip), dst_ip(dst_ip), src_mac(src_mac), + dst_mac(dst_mac), ttl(ttl), tos(tos) + { + } + + P4MirrorSessionEntry(const P4MirrorSessionEntry &) = default; + + bool operator==(const P4MirrorSessionEntry &entry) const + { + return mirror_session_key == entry.mirror_session_key && mirror_session_oid == entry.mirror_session_oid && + mirror_session_id == entry.mirror_session_id && port == entry.port && src_ip == entry.src_ip && + dst_ip == entry.dst_ip && src_mac == entry.src_mac && dst_mac == entry.dst_mac && ttl == entry.ttl && + tos == entry.tos; + } + + std::string mirror_session_key; + + // SAI OID associated with this entry. + sai_object_id_t mirror_session_oid = 0; + + // Match field in table + std::string mirror_session_id; + // Action parameters + std::string port; + swss::IpAddress src_ip; + swss::IpAddress dst_ip; + swss::MacAddress src_mac; + swss::MacAddress dst_mac; + uint8_t ttl = 0; + uint8_t tos = 0; +}; + +// MirrorSessionManager is responsible for programming mirror session intents in +// APPL_DB:FIXED_MIRROR_SESSION_TABLE to ASIC_DB. +class MirrorSessionManager : public ObjectManagerInterface +{ + public: + MirrorSessionManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + + void drain() override; + + private: + ReturnCodeOr deserializeP4MirrorSessionAppDbEntry( + const std::string &key, const std::vector &attributes); + + P4MirrorSessionEntry *getMirrorSessionEntry(const std::string &mirror_session_key); + + ReturnCode processAddRequest(const P4MirrorSessionAppDbEntry &app_db_entry); + ReturnCode createMirrorSession(P4MirrorSessionEntry mirror_session_entry); + + ReturnCode processUpdateRequest(const P4MirrorSessionAppDbEntry &app_db_entry, + P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setPort(const std::string &new_port, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setSrcIp(const swss::IpAddress &new_src_ip, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setDstIp(const swss::IpAddress &new_dst_ip, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setSrcMac(const swss::MacAddress &new_src_mac, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setDstMac(const swss::MacAddress &new_dst_mac, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setTtl(uint8_t new_ttl, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setTos(uint8_t new_tos, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setMirrorSessionEntry(const P4MirrorSessionEntry &intent_mirror_session_entry, + P4MirrorSessionEntry *existing_mirror_session_entry); + + ReturnCode processDeleteRequest(const std::string &mirror_session_key); + + std::unordered_map m_mirrorSessionTable; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + // For test purpose only + friend class p4orch::test::MirrorSessionManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/p4orch/neighbor_manager.cpp b/orchagent/p4orch/neighbor_manager.cpp new file mode 100644 index 0000000000..059aa76698 --- /dev/null +++ b/orchagent/p4orch/neighbor_manager.cpp @@ -0,0 +1,376 @@ +#include "p4orch/neighbor_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch/p4orch_util.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; + +extern sai_neighbor_api_t *sai_neighbor_api; + +extern CrmOrch *gCrmOrch; + +P4NeighborEntry::P4NeighborEntry(const std::string &router_interface_id, const swss::IpAddress &ip_address, + const swss::MacAddress &mac_address) +{ + SWSS_LOG_ENTER(); + + router_intf_id = router_interface_id; + neighbor_id = ip_address; + dst_mac_address = mac_address; + + router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_intf_id); + neighbor_key = KeyGenerator::generateNeighborKey(router_intf_id, neighbor_id); +} + +ReturnCodeOr NeighborManager::deserializeNeighborEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4NeighborAppDbEntry app_db_entry = {}; + std::string ip_address; + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.router_intf_id = j[prependMatchField(p4orch::kRouterInterfaceId)]; + ip_address = j[prependMatchField(p4orch::kNeighborId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize key"; + } + try + { + app_db_entry.neighbor_id = swss::IpAddress(ip_address); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(ip_address) << " of field " + << QuotedVar(prependMatchField(p4orch::kNeighborId)); + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kDstMac)) + { + try + { + app_db_entry.dst_mac_address = swss::MacAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + app_db_entry.is_set_dst_mac = true; + } + else if (field != p4orch::kAction && field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +ReturnCode NeighborManager::validateNeighborAppDbEntry(const P4NeighborAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + // Perform generic APP DB entry validations. Operation specific validations + // will be done by the respective request process methods. + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router interface id " << QuotedVar(app_db_entry.router_intf_id) << " does not exist"; + } + + if ((app_db_entry.is_set_dst_mac) && (app_db_entry.dst_mac_address.to_string() == "00:00:00:00:00:00")) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid dst mac address " << QuotedVar(app_db_entry.dst_mac_address.to_string()); + } + + return ReturnCode(); +} + +P4NeighborEntry *NeighborManager::getNeighborEntry(const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + if (m_neighborTable.find(neighbor_key) == m_neighborTable.end()) + return nullptr; + + return &m_neighborTable[neighbor_key]; +} + +ReturnCode NeighborManager::createNeighbor(P4NeighborEntry &neighbor_entry) +{ + SWSS_LOG_ENTER(); + + const std::string &neighbor_key = neighbor_entry.neighbor_key; + if (getNeighborEntry(neighbor_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "Neighbor entry with key " << QuotedVar(neighbor_key) << " already exists"); + } + + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Neighbor entry with key " << QuotedVar(neighbor_key) + << " already exists in centralized map"); + } + + const std::string &router_intf_key = neighbor_entry.router_intf_key; + sai_object_id_t router_intf_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &router_intf_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router intf key " << QuotedVar(router_intf_key) + << " does not exist in certralized map"); + } + + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = router_intf_oid; + + std::vector neigh_attrs; + sai_attribute_t neigh_attr; + neigh_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS; + memcpy(neigh_attr.value.mac, neighbor_entry.dst_mac_address.getMac(), sizeof(sai_mac_t)); + neigh_attrs.push_back(neigh_attr); + + // Do not program host route. + // This is mainly for neighbor with IPv6 link-local addresses. + neigh_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE; + neigh_attr.value.booldata = true; + neigh_attrs.push_back(neigh_attr); + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_neighbor_api->create_neighbor_entry(&neighbor_entry.neigh_entry, + static_cast(neigh_attrs.size()), + neigh_attrs.data()), + "Failed to create neighbor with key " << QuotedVar(neighbor_key)); + + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key); + if (neighbor_entry.neighbor_id.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEIGHBOR); + } + + m_neighborTable[neighbor_key] = neighbor_entry; + m_p4OidMapper->setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key); + return ReturnCode(); +} + +ReturnCode NeighborManager::removeNeighbor(const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + auto *neighbor_entry = getNeighborEntry(neighbor_key); + if (neighbor_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Neighbor with key " << QuotedVar(neighbor_key) << " does not exist"); + } + + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count of neighbor with key " + << QuotedVar(neighbor_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Neighbor with key " << QuotedVar(neighbor_key) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_neighbor_api->remove_neighbor_entry(&neighbor_entry->neigh_entry), + "Failed to remove neighbor with key " << QuotedVar(neighbor_key)); + + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry->router_intf_key); + if (neighbor_entry->neighbor_id.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEIGHBOR); + } + + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key); + m_neighborTable.erase(neighbor_key); + return ReturnCode(); +} + +ReturnCode NeighborManager::setDstMacAddress(P4NeighborEntry *neighbor_entry, const swss::MacAddress &mac_address) +{ + SWSS_LOG_ENTER(); + + if (neighbor_entry->dst_mac_address == mac_address) + return ReturnCode(); + + sai_attribute_t neigh_attr; + neigh_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS; + memcpy(neigh_attr.value.mac, mac_address.getMac(), sizeof(sai_mac_t)); + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_neighbor_api->set_neighbor_entry_attribute(&neighbor_entry->neigh_entry, &neigh_attr), + "Failed to set mac address " << QuotedVar(mac_address.to_string()) << " for neighbor with key " + << QuotedVar(neighbor_entry->neighbor_key)); + + neighbor_entry->dst_mac_address = mac_address; + return ReturnCode(); +} + +ReturnCode NeighborManager::processAddRequest(const P4NeighborAppDbEntry &app_db_entry, const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + // Perform operation specific validations. + if (!app_db_entry.is_set_dst_mac) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << p4orch::kDstMac + << " is mandatory to create neighbor entry. Failed to create " + "neighbor with key " + << QuotedVar(neighbor_key)); + } + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + auto status = createNeighbor(neighbor_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create neighbor with key %s", QuotedVar(neighbor_key).c_str()); + } + + return status; +} + +ReturnCode NeighborManager::processUpdateRequest(const P4NeighborAppDbEntry &app_db_entry, + P4NeighborEntry *neighbor_entry) +{ + SWSS_LOG_ENTER(); + + if (app_db_entry.is_set_dst_mac) + { + auto status = setDstMacAddress(neighbor_entry, app_db_entry.dst_mac_address); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to set destination mac address for neighbor with key %s", + QuotedVar(neighbor_entry->neighbor_key).c_str()); + return status; + } + } + + return ReturnCode(); +} + +ReturnCode NeighborManager::processDeleteRequest(const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeNeighbor(neighbor_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove neighbor with key %s", QuotedVar(neighbor_key).c_str()); + } + + return status; +} + +void NeighborManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void NeighborManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeNeighborEntry(db_key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + status = validateNeighborAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Neighbor APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *neighbor_entry = getNeighborEntry(neighbor_key); + if (neighbor_entry == nullptr) + { + // Create neighbor + status = processAddRequest(app_db_entry, neighbor_key); + } + else + { + // Modify existing neighbor + status = processUpdateRequest(app_db_entry, neighbor_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete neighbor + status = processDeleteRequest(neighbor_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} diff --git a/orchagent/p4orch/neighbor_manager.h b/orchagent/p4orch/neighbor_manager.h new file mode 100644 index 0000000000..2ede9de763 --- /dev/null +++ b/orchagent/p4orch/neighbor_manager.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + +#include "ipaddress.h" +#include "macaddress.h" +#include "orch.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "p4orch/router_interface_manager.h" +#include "response_publisher_interface.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +struct P4NeighborEntry +{ + std::string router_intf_id; + swss::IpAddress neighbor_id; + swss::MacAddress dst_mac_address; + std::string router_intf_key; + std::string neighbor_key; + sai_neighbor_entry_t neigh_entry; + + P4NeighborEntry() = default; + P4NeighborEntry(const std::string &router_interface_id, const swss::IpAddress &ip_address, + const swss::MacAddress &mac_address); +}; + +// P4NeighborTable: Neighbor key string, P4NeighborEntry +typedef std::unordered_map P4NeighborTable; + +class NeighborManager : public ObjectManagerInterface +{ + public: + NeighborManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + virtual ~NeighborManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + ReturnCodeOr deserializeNeighborEntry(const std::string &key, + const std::vector &attributes); + ReturnCode validateNeighborAppDbEntry(const P4NeighborAppDbEntry &app_db_entry); + P4NeighborEntry *getNeighborEntry(const std::string &neighbor_key); + ReturnCode createNeighbor(P4NeighborEntry &neighbor_entry); + ReturnCode removeNeighbor(const std::string &neighbor_key); + ReturnCode setDstMacAddress(P4NeighborEntry *neighbor_entry, const swss::MacAddress &mac_address); + ReturnCode processAddRequest(const P4NeighborAppDbEntry &app_db_entry, const std::string &neighbor_key); + ReturnCode processUpdateRequest(const P4NeighborAppDbEntry &app_db_entry, P4NeighborEntry *neighbor_entry); + ReturnCode processDeleteRequest(const std::string &neighbor_key); + + P4OidMapper *m_p4OidMapper; + P4NeighborTable m_neighborTable; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class NeighborManagerTest; +}; diff --git a/orchagent/p4orch/next_hop_manager.cpp b/orchagent/p4orch/next_hop_manager.cpp new file mode 100644 index 0000000000..3e2d9ff548 --- /dev/null +++ b/orchagent/p4orch/next_hop_manager.cpp @@ -0,0 +1,333 @@ +#include "p4orch/next_hop_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "ipaddress.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_next_hop_api_t *sai_next_hop_api; +extern CrmOrch *gCrmOrch; + +P4NextHopEntry::P4NextHopEntry(const std::string &next_hop_id, const std::string &router_interface_id, + const swss::IpAddress &neighbor_id) + : next_hop_id(next_hop_id), router_interface_id(router_interface_id), neighbor_id(neighbor_id) +{ + SWSS_LOG_ENTER(); + next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); +} + +void NextHopManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void NextHopManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeP4NextHopAppDbEntry(key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + const std::string next_hop_key = KeyGenerator::generateNextHopKey(app_db_entry.next_hop_id); + + // Fulfill the operation. + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *next_hop_entry = getNextHopEntry(next_hop_key); + if (next_hop_entry == nullptr) + { + // Create new next hop. + status = processAddRequest(app_db_entry); + } + else + { + // Modify existing next hop. + status = processUpdateRequest(app_db_entry, next_hop_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete next hop. + status = processDeleteRequest(next_hop_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +P4NextHopEntry *NextHopManager::getNextHopEntry(const std::string &next_hop_key) +{ + SWSS_LOG_ENTER(); + + auto it = m_nextHopTable.find(next_hop_key); + + if (it == m_nextHopTable.end()) + { + return nullptr; + } + else + { + return &it->second; + } +} + +ReturnCodeOr NextHopManager::deserializeP4NextHopAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4NextHopAppDbEntry app_db_entry = {}; + + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.next_hop_id = j[prependMatchField(p4orch::kNexthopId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize next hop id"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kRouterInterfaceId)) + { + app_db_entry.router_interface_id = value; + app_db_entry.is_set_router_interface_id = true; + } + else if (field == prependParamField(p4orch::kNeighborId)) + { + try + { + app_db_entry.neighbor_id = swss::IpAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + app_db_entry.is_set_neighbor_id = true; + } + else if (field != p4orch::kAction && field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +ReturnCode NextHopManager::processAddRequest(const P4NextHopAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + P4NextHopEntry next_hop_entry(app_db_entry.next_hop_id, app_db_entry.router_interface_id, app_db_entry.neighbor_id); + auto status = createNextHop(next_hop_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create next hop with key %s", QuotedVar(next_hop_entry.next_hop_key).c_str()); + } + return status; +} + +ReturnCode NextHopManager::createNextHop(P4NextHopEntry &next_hop_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the next hop in next hop manager and centralized + // mapper. + if (getNextHopEntry(next_hop_entry.next_hop_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "Next hop with key " << QuotedVar(next_hop_entry.next_hop_key) + << " already exists in next hop manager"); + } + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_entry.next_hop_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Next hop with key " << QuotedVar(next_hop_entry.next_hop_key) + << " already exists in centralized mapper"); + } + + // From centralized mapper, get OID of router interface that next hop depends + // on. + const auto router_interface_key = KeyGenerator::generateRouterInterfaceKey(next_hop_entry.router_interface_id); + sai_object_id_t rif_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_interface_key, &rif_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router intf " << QuotedVar(next_hop_entry.router_interface_id) << " does not exist"); + } + + // Neighbor doesn't have OID and the IP addr needed in next hop creation is + // neighbor_id, so only check neighbor existence in centralized mapper. + const auto neighbor_key = + KeyGenerator::generateNeighborKey(next_hop_entry.router_interface_id, next_hop_entry.neighbor_id); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Neighbor with key " << QuotedVar(neighbor_key) + << " does not exist in centralized mapper"); + } + + // Prepare attributes for the SAI creation call. + std::vector next_hop_attrs; + sai_attribute_t next_hop_attr; + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_IP; + next_hop_attrs.push_back(next_hop_attr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; + swss::copy(next_hop_attr.value.ipaddr, next_hop_entry.neighbor_id); + next_hop_attrs.push_back(next_hop_attr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; + next_hop_attr.value.oid = rif_oid; + next_hop_attrs.push_back(next_hop_attr); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_api->create_next_hop(&next_hop_entry.next_hop_oid, gSwitchId, + (uint32_t)next_hop_attrs.size(), + next_hop_attrs.data()), + "Failed to create next hop " << QuotedVar(next_hop_entry.next_hop_key) << " on rif " + << QuotedVar(next_hop_entry.router_interface_id)); + + // On successful creation, increment ref count. + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_interface_key); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key); + if (next_hop_entry.neighbor_id.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + + // Add created entry to internal table. + m_nextHopTable.emplace(next_hop_entry.next_hop_key, next_hop_entry); + + // Add the key to OID map to centralized mapper. + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_entry.next_hop_key, next_hop_entry.next_hop_oid); + + return ReturnCode(); +} + +ReturnCode NextHopManager::processUpdateRequest(const P4NextHopAppDbEntry &app_db_entry, P4NextHopEntry *next_hop_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Currently next hop doesn't support update. Next hop key " + << QuotedVar(next_hop_entry->next_hop_key); + SWSS_LOG_ERROR("%s", status.message().c_str()); + return status; +} + +ReturnCode NextHopManager::processDeleteRequest(const std::string &next_hop_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeNextHop(next_hop_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove next hop with key %s", QuotedVar(next_hop_key).c_str()); + } + + return status; +} + +ReturnCode NextHopManager::removeNextHop(const std::string &next_hop_key) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the next hop in next hop manager and centralized + // mapper. + auto *next_hop_entry = getNextHopEntry(next_hop_key); + if (next_hop_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Next hop with key " << QuotedVar(next_hop_key) + << " does not exist in next hop manager"); + } + + // Check if there is anything referring to the next hop before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for next hop " + << QuotedVar(next_hop_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Next hop " << QuotedVar(next_hop_entry->next_hop_key) + << " referenced by other objects (ref_count = " << ref_count); + } + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_api->remove_next_hop(next_hop_entry->next_hop_oid), + "Failed to remove next hop " << QuotedVar(next_hop_entry->next_hop_key)); + + // On successful deletion, decrement ref count. + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(next_hop_entry->router_interface_id)); + m_p4OidMapper->decreaseRefCount( + SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, + KeyGenerator::generateNeighborKey(next_hop_entry->router_interface_id, next_hop_entry->neighbor_id)); + if (next_hop_entry->neighbor_id.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + + // Remove the key to OID map to centralized mapper. + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key); + + // Remove the entry from internal table. + m_nextHopTable.erase(next_hop_key); + + return ReturnCode(); +} diff --git a/orchagent/p4orch/next_hop_manager.h b/orchagent/p4orch/next_hop_manager.h new file mode 100644 index 0000000000..7b4a318f87 --- /dev/null +++ b/orchagent/p4orch/next_hop_manager.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +#include "ipaddress.h" +#include "orch.h" +#include "p4orch/neighbor_manager.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "p4orch/router_interface_manager.h" +#include "response_publisher_interface.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +// P4NextHopEntry holds NextHopManager's internal cache of P4 next hop entry. +struct P4NextHopEntry +{ + // Key of this entry, built from next_hop_id. + std::string next_hop_key; + + // Fields from P4 table. + // Match + std::string next_hop_id; + // Action + std::string router_interface_id; + swss::IpAddress neighbor_id; + + // SAI OID associated with this entry. + sai_object_id_t next_hop_oid = 0; + + P4NextHopEntry(const std::string &next_hop_id, const std::string &router_interface_id, + const swss::IpAddress &neighbor_id); +}; + +// NextHopManager listens to changes in table APP_P4RT_NEXTHOP_TABLE_NAME and +// creates/updates/deletes next hop SAI object accordingly. +class NextHopManager : public ObjectManagerInterface +{ + public: + NextHopManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + virtual ~NextHopManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + // Gets the internal cached next hop entry by its key. + // Return nullptr if corresponding next hop entry is not cached. + P4NextHopEntry *getNextHopEntry(const std::string &next_hop_key); + + // Deserializes an entry from table APP_P4RT_NEXTHOP_TABLE_NAME. + ReturnCodeOr deserializeP4NextHopAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Processes add operation for an entry. + ReturnCode processAddRequest(const P4NextHopAppDbEntry &app_db_entry); + + // Creates an next hop in the next hop table. Return true on success. + ReturnCode createNextHop(P4NextHopEntry &next_hop_entry); + + // Processes update operation for an entry. + ReturnCode processUpdateRequest(const P4NextHopAppDbEntry &app_db_entry, P4NextHopEntry *next_hop_entry); + + // Processes delete operation for an entry. + ReturnCode processDeleteRequest(const std::string &next_hop_key); + + // Deletes an next hop in the next hop table. Return true on success. + ReturnCode removeNextHop(const std::string &next_hop_key); + + // m_nextHopTable: next_hop_key, P4NextHopEntry + std::unordered_map m_nextHopTable; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class NextHopManagerTest; +}; diff --git a/orchagent/p4orch/object_manager_interface.h b/orchagent/p4orch/object_manager_interface.h new file mode 100644 index 0000000000..ec9775f8e4 --- /dev/null +++ b/orchagent/p4orch/object_manager_interface.h @@ -0,0 +1,15 @@ +#pragma once + +#include "orch.h" + +class ObjectManagerInterface +{ + public: + virtual ~ObjectManagerInterface() = default; + + // Enqueues an entry into the manager + virtual void enqueue(const swss::KeyOpFieldsValuesTuple &entry) = 0; + + // Processes all entries in the queue + virtual void drain() = 0; +}; diff --git a/orchagent/p4orch/p4oidmapper.cpp b/orchagent/p4orch/p4oidmapper.cpp new file mode 100644 index 0000000000..f4ff6e3433 --- /dev/null +++ b/orchagent/p4orch/p4oidmapper.cpp @@ -0,0 +1,180 @@ +#include "p4oidmapper.h" + +#include +#include + +#include "logger.h" +#include "sai_serialize.h" + +extern "C" +{ +#include "sai.h" +} + +namespace +{ + +std::string convertToDBField(_In_ const sai_object_type_t object_type, _In_ const std::string &key) +{ + return sai_serialize_object_type(object_type) + ":" + key; +} + +} // namespace + +P4OidMapper::P4OidMapper() : m_db("APPL_STATE_DB", 0), m_table(&m_db, "P4RT_KEY_TO_OID") +{ +} + +bool P4OidMapper::setOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _In_ sai_object_id_t oid, + _In_ uint32_t ref_count) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) != m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d already exists in centralized mapper", key.c_str(), object_type); + return false; + } + + m_oidTables[object_type][key] = {oid, ref_count}; + m_table.hset("", convertToDBField(object_type, key), sai_serialize_object_id(oid)); + return true; +} + +bool P4OidMapper::getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ sai_object_id_t *oid) +{ + SWSS_LOG_ENTER(); + + if (oid == nullptr) + { + SWSS_LOG_ERROR("nullptr input in centralized mapper"); + return false; + } + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in centralized mapper", key.c_str(), object_type); + return false; + } + + *oid = m_oidTables[object_type][key].sai_oid; + return true; +} + +bool P4OidMapper::getRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key, + _Out_ uint32_t *ref_count) +{ + SWSS_LOG_ENTER(); + + if (ref_count == nullptr) + { + SWSS_LOG_ERROR("nullptr input in centralized mapper"); + return false; + } + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + *ref_count = m_oidTables[object_type][key].ref_count; + return true; +} + +bool P4OidMapper::eraseOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + if (m_oidTables[object_type][key].ref_count != 0) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d has non-zero reference count in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + m_oidTables[object_type].erase(key); + m_table.hdel("", convertToDBField(object_type, key)); + return true; +} + +void P4OidMapper::eraseAllOIDs(_In_ sai_object_type_t object_type) +{ + SWSS_LOG_ENTER(); + + m_oidTables[object_type].clear(); + m_table.del(""); +} + +size_t P4OidMapper::getNumEntries(_In_ sai_object_type_t object_type) +{ + SWSS_LOG_ENTER(); + + return (m_oidTables[object_type].size()); +} + +bool P4OidMapper::existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + return m_oidTables[object_type].find(key) != m_oidTables[object_type].end(); +} + +bool P4OidMapper::increaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + if (m_oidTables[object_type][key].ref_count == std::numeric_limits::max()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d reached maximum ref_count %u in " + "centralized mapper", + key.c_str(), object_type, m_oidTables[object_type][key].ref_count); + return false; + } + + m_oidTables[object_type][key].ref_count++; + return true; +} + +bool P4OidMapper::decreaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + if (m_oidTables[object_type][key].ref_count == 0) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d reached zero ref_count in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + m_oidTables[object_type][key].ref_count--; + return true; +} diff --git a/orchagent/p4orch/p4oidmapper.h b/orchagent/p4orch/p4oidmapper.h new file mode 100644 index 0000000000..6f7b86ab8f --- /dev/null +++ b/orchagent/p4orch/p4oidmapper.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +#include "dbconnector.h" +#include "table.h" + +extern "C" +{ +#include "sai.h" +} + +// Interface for mapping P4 ID to SAI OID. +// This class is not thread safe. +class P4OidMapper +{ + public: + // This is a dummy value for non-oid based objects only. + static constexpr sai_object_id_t kDummyOid = 0xdeadf00ddeadf00d; + + P4OidMapper(); + ~P4OidMapper() = default; + + // Sets oid for the given key for the specific object_type. Returns false if + // the key already exists. + bool setOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _In_ sai_object_id_t oid, + _In_ uint32_t ref_count = 0); + + // Sets dummy oid for the given key for the specific object_type. Should only + // be used for non-oid based object type. Returns false if the key + // already exists. + bool setDummyOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _In_ uint32_t ref_count = 0) + { + return setOID(object_type, key, /*oid=*/kDummyOid, ref_count); + } + + // Gets oid for the given key for the SAI object_type. + // Returns true on success. + bool getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ sai_object_id_t *oid); + + // Gets the reference count for the given key for the SAI object_type. + // Returns true on success. + bool getRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ uint32_t *ref_count); + + // Erases oid for the given key for the SAI object_type. + // This function checks if the reference count is zero or not before the + // operation. + // Returns true on success. + bool eraseOID(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + // Erases all oids for the SAI object_type. + // This function will erase all oids regardless of the reference counts. + void eraseAllOIDs(_In_ sai_object_type_t object_type); + + // Gets the number of oids for the SAI object_type. + size_t getNumEntries(_In_ sai_object_type_t object_type); + + // Checks whether OID mapping exists for the given key for the specific + // object type. + bool existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + // Increases the reference count for the given object. + // Returns true on success. + bool increaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + // Decreases the reference count for the given object. + // Returns true on success. + bool decreaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + private: + struct MapperEntry + { + sai_object_id_t sai_oid; + uint32_t ref_count; + }; + + // Buckets of map tables, one for every SAI object type. + std::unordered_map m_oidTables[SAI_OBJECT_TYPE_MAX]; + + swss::DBConnector m_db; + swss::Table m_table; +}; diff --git a/orchagent/p4orch/p4orch.cpp b/orchagent/p4orch/p4orch.cpp new file mode 100644 index 0000000000..ada1fa2c77 --- /dev/null +++ b/orchagent/p4orch/p4orch.cpp @@ -0,0 +1,237 @@ +#include "p4orch.h" + +#include +#include +#include +#include + +#include "copporch.h" +#include "logger.h" +#include "orch.h" +#include "p4orch/acl_rule_manager.h" +#include "p4orch/acl_table_manager.h" +#include "p4orch/neighbor_manager.h" +#include "p4orch/next_hop_manager.h" +#include "p4orch/route_manager.h" +#include "p4orch/router_interface_manager.h" +#include "portsorch.h" +#include "return_code.h" +#include "sai_serialize.h" +#include "timer.h" + +extern PortsOrch *gPortsOrch; +#define P4_ACL_COUNTERS_STATS_POLL_TIMER_NAME "P4_ACL_COUNTERS_STATS_POLL_TIMER" + +P4Orch::P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOrch *vrfOrch, CoppOrch *coppOrch) + : Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); + + m_routerIntfManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_neighborManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_nextHopManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_routeManager = std::make_unique(&m_p4OidMapper, vrfOrch, &m_publisher); + m_mirrorSessionManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_aclTableManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_aclRuleManager = std::make_unique(&m_p4OidMapper, vrfOrch, coppOrch, &m_publisher); + m_wcmpManager = std::make_unique(&m_p4OidMapper, &m_publisher); + + m_p4TableToManagerMap[APP_P4RT_ROUTER_INTERFACE_TABLE_NAME] = m_routerIntfManager.get(); + m_p4TableToManagerMap[APP_P4RT_NEIGHBOR_TABLE_NAME] = m_neighborManager.get(); + m_p4TableToManagerMap[APP_P4RT_NEXTHOP_TABLE_NAME] = m_nextHopManager.get(); + m_p4TableToManagerMap[APP_P4RT_IPV4_TABLE_NAME] = m_routeManager.get(); + m_p4TableToManagerMap[APP_P4RT_IPV6_TABLE_NAME] = m_routeManager.get(); + m_p4TableToManagerMap[APP_P4RT_MIRROR_SESSION_TABLE_NAME] = m_mirrorSessionManager.get(); + m_p4TableToManagerMap[APP_P4RT_ACL_TABLE_DEFINITION_NAME] = m_aclTableManager.get(); + m_p4TableToManagerMap[APP_P4RT_WCMP_GROUP_TABLE_NAME] = m_wcmpManager.get(); + + m_p4ManagerPrecedence.push_back(m_routerIntfManager.get()); + m_p4ManagerPrecedence.push_back(m_neighborManager.get()); + m_p4ManagerPrecedence.push_back(m_nextHopManager.get()); + m_p4ManagerPrecedence.push_back(m_wcmpManager.get()); + m_p4ManagerPrecedence.push_back(m_routeManager.get()); + m_p4ManagerPrecedence.push_back(m_mirrorSessionManager.get()); + m_p4ManagerPrecedence.push_back(m_aclTableManager.get()); + m_p4ManagerPrecedence.push_back(m_aclRuleManager.get()); + + // Add timer executor to update ACL counters stats in COUNTERS_DB + auto interv = timespec{.tv_sec = P4_COUNTERS_READ_INTERVAL, .tv_nsec = 0}; + m_aclCounterStatsTimer = new swss::SelectableTimer(interv); + auto executor = new swss::ExecutableTimer(m_aclCounterStatsTimer, this, P4_ACL_COUNTERS_STATS_POLL_TIMER_NAME); + Orch::addExecutor(executor); + m_aclCounterStatsTimer->start(); + + // Add port state change notification handling support + swss::DBConnector notificationsDb("ASIC_DB", 0); + m_portStatusNotificationConsumer = new swss::NotificationConsumer(¬ificationsDb, "NOTIFICATIONS"); + auto portStatusNotifier = new Notifier(m_portStatusNotificationConsumer, this, "PORT_STATUS_NOTIFICATIONS"); + Orch::addExecutor(portStatusNotifier); +} + +void P4Orch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + const std::string table_name = consumer.getTableName(); + if (table_name != APP_P4RT_TABLE_NAME) + { + SWSS_LOG_ERROR("Incorrect table name %s (expected %s)", table_name.c_str(), APP_P4RT_TABLE_NAME); + return; + } + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + const swss::KeyOpFieldsValuesTuple key_op_fvs_tuple = it->second; + const std::string key = kfvKey(key_op_fvs_tuple); + it = consumer.m_toSync.erase(it); + std::string table_name; + std::string key_content; + parseP4RTKey(key, &table_name, &key_content); + if (table_name.empty()) + { + auto status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Table name cannot be empty, but was empty in key: " << key; + SWSS_LOG_ERROR("%s", status.message().c_str()); + m_publisher.publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status); + continue; + } + if (m_p4TableToManagerMap.find(table_name) == m_p4TableToManagerMap.end()) + { + auto status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to find P4Orch Manager for " << table_name << " P4RT DB table"; + SWSS_LOG_ERROR("%s", status.message().c_str()); + m_publisher.publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status); + continue; + } + m_p4TableToManagerMap[table_name]->enqueue(key_op_fvs_tuple); + } + + for (const auto &manager : m_p4ManagerPrecedence) + { + manager->drain(); + } +} + +void P4Orch::doTask(swss::SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + if (&timer == m_aclCounterStatsTimer) + { + m_aclRuleManager->doAclCounterStatsTask(); + } + else + { + SWSS_LOG_NOTICE("Unrecognized timer passed in P4Orch::doTask(swss::SelectableTimer& " + "timer)"); + } +} + +void P4Orch::handlePortStatusChangeNotification(const std::string &op, const std::string &data) +{ + if (op == "port_state_change") + { + uint32_t count; + sai_port_oper_status_notification_t *port_oper_status = nullptr; + sai_deserialize_port_oper_status_ntf(data, count, &port_oper_status); + + for (uint32_t i = 0; i < count; i++) + { + sai_object_id_t id = port_oper_status[i].port_id; + sai_port_oper_status_t status = port_oper_status[i].port_state; + + Port port; + if (!gPortsOrch->getPort(id, port)) + { + SWSS_LOG_ERROR("Failed to get port object for port id 0x%" PRIx64, id); + continue; + } + + // Update port oper-status in local map + m_wcmpManager->updatePortOperStatusMap(port.m_alias, status); + + if (status == SAI_PORT_OPER_STATUS_UP) + { + m_wcmpManager->restorePrunedNextHops(port.m_alias); + } + else + { + m_wcmpManager->pruneNextHops(port.m_alias); + } + + sai_deserialize_free_port_oper_status_ntf(count, port_oper_status); + } + } +} + +void P4Orch::doTask(NotificationConsumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + std::string op, data; + std::vector values; + + consumer.pop(op, data, values); + + if (&consumer == m_portStatusNotificationConsumer) + { + handlePortStatusChangeNotification(op, data); + } +} + +bool P4Orch::addAclTableToManagerMapping(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + if (m_p4TableToManagerMap.find(acl_table_name) != m_p4TableToManagerMap.end()) + { + SWSS_LOG_NOTICE("Consumer for ACL table %s already exists in P4Orch", acl_table_name.c_str()); + return false; + } + m_p4TableToManagerMap[acl_table_name] = m_aclRuleManager.get(); + return true; +} + +bool P4Orch::removeAclTableToManagerMapping(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + if (m_p4TableToManagerMap.find(acl_table_name) == m_p4TableToManagerMap.end()) + { + SWSS_LOG_NOTICE("Consumer for ACL table %s does not exist in P4Orch", acl_table_name.c_str()); + return false; + } + m_p4TableToManagerMap.erase(acl_table_name); + return true; +} + +p4orch::AclTableManager *P4Orch::getAclTableManager() +{ + return m_aclTableManager.get(); +} + +p4orch::AclRuleManager *P4Orch::getAclRuleManager() +{ + return m_aclRuleManager.get(); +} + +p4orch::WcmpManager *P4Orch::getWcmpManager() +{ + return m_wcmpManager.get(); +} diff --git a/orchagent/p4orch/p4orch.h b/orchagent/p4orch/p4orch.h new file mode 100644 index 0000000000..42159f3981 --- /dev/null +++ b/orchagent/p4orch/p4orch.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "copporch.h" +#include "notificationconsumer.h" +#include "notifier.h" +#include "orch.h" +#include "p4orch/acl_rule_manager.h" +#include "p4orch/acl_table_manager.h" +#include "p4orch/mirror_session_manager.h" +#include "p4orch/neighbor_manager.h" +#include "p4orch/next_hop_manager.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/route_manager.h" +#include "p4orch/router_interface_manager.h" +#include "p4orch/wcmp_manager.h" +#include "response_publisher.h" +#include "vrforch.h" + +class P4Orch : public Orch +{ + public: + P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOrch *vrfOrch, CoppOrch *coppOrch); + // Add ACL table to ACLRuleManager mapping in P4Orch. + bool addAclTableToManagerMapping(const std::string &acl_table_name); + // Remove the ACL table name to AclRuleManager mapping in P4Orch + bool removeAclTableToManagerMapping(const std::string &acl_table_name); + p4orch::AclTableManager *getAclTableManager(); + p4orch::AclRuleManager *getAclRuleManager(); + p4orch::WcmpManager *getWcmpManager(); + + private: + void doTask(Consumer &consumer); + void doTask(swss::SelectableTimer &timer); + void doTask(swss::NotificationConsumer &consumer); + void handlePortStatusChangeNotification(const std::string &op, const std::string &data); + + // m_p4TableToManagerMap: P4 APP DB table name, P4 Object Manager + std::unordered_map m_p4TableToManagerMap; + // P4 object manager request processing order. + std::vector m_p4ManagerPrecedence; + + swss::SelectableTimer *m_aclCounterStatsTimer; + P4OidMapper m_p4OidMapper; + std::unique_ptr m_routerIntfManager; + std::unique_ptr m_neighborManager; + std::unique_ptr m_nextHopManager; + std::unique_ptr m_routeManager; + std::unique_ptr m_mirrorSessionManager; + std::unique_ptr m_aclTableManager; + std::unique_ptr m_aclRuleManager; + std::unique_ptr m_wcmpManager; + + // Notification consumer for port state change + swss::NotificationConsumer *m_portStatusNotificationConsumer; + + friend class p4orch::test::WcmpManagerTest; +}; diff --git a/orchagent/p4orch/p4orch_util.cpp b/orchagent/p4orch/p4orch_util.cpp new file mode 100644 index 0000000000..e5d4479436 --- /dev/null +++ b/orchagent/p4orch/p4orch_util.cpp @@ -0,0 +1,103 @@ +#include "p4orch/p4orch_util.h" + +#include "schema.h" + +using ::p4orch::kTableKeyDelimiter; + +// Prepends "match/" to the input string str to construct a new string. +std::string prependMatchField(const std::string &str) +{ + return std::string(p4orch::kMatchPrefix) + p4orch::kFieldDelimiter + str; +} + +// Prepends "param/" to the input string str to construct a new string. +std::string prependParamField(const std::string &str) +{ + return std::string(p4orch::kActionParamPrefix) + p4orch::kFieldDelimiter + str; +} + +void parseP4RTKey(const std::string &key, std::string *table_name, std::string *key_content) +{ + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + *table_name = ""; + *key_content = ""; + return; + } + *table_name = key.substr(0, pos); + *key_content = key.substr(pos + 1); +} + +std::string KeyGenerator::generateRouteKey(const std::string &vrf_id, const swss::IpPrefix &ip_prefix) +{ + std::map fv_map = { + {p4orch::kVrfId, vrf_id}, {ip_prefix.isV4() ? p4orch::kIpv4Dst : p4orch::kIpv6Dst, ip_prefix.to_string()}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateRouterInterfaceKey(const std::string &router_intf_id) +{ + std::map fv_map = {{p4orch::kRouterInterfaceId, router_intf_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateNeighborKey(const std::string &router_intf_id, const swss::IpAddress &neighbor_id) +{ + std::map fv_map = {{p4orch::kRouterInterfaceId, router_intf_id}, + {p4orch::kNeighborId, neighbor_id.to_string()}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateNextHopKey(const std::string &next_hop_id) +{ + std::map fv_map = {{p4orch::kNexthopId, next_hop_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateMirrorSessionKey(const std::string &mirror_session_id) +{ + std::map fv_map = {{p4orch::kMirrorSessionId, mirror_session_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateWcmpGroupKey(const std::string &wcmp_group_id) +{ + std::map fv_map = {{p4orch::kWcmpGroupId, wcmp_group_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateAclRuleKey(const std::map &match_fields, + const std::string &priority) +{ + std::map fv_map = {}; + for (const auto &match_field : match_fields) + { + fv_map.emplace(std::string(p4orch::kMatchPrefix) + p4orch::kFieldDelimiter + match_field.first, + match_field.second); + } + fv_map.emplace(p4orch::kPriority, priority); + return generateKey(fv_map); +} + +std::string KeyGenerator::generateKey(const std::map &fv_map) +{ + std::string key; + bool append_delimiter = false; + for (const auto &it : fv_map) + { + if (append_delimiter) + { + key.append(":"); + } + else + { + append_delimiter = true; + } + key.append(it.first); + key.append("="); + key.append(it.second); + } + + return key; +} diff --git a/orchagent/p4orch/p4orch_util.h b/orchagent/p4orch/p4orch_util.h new file mode 100644 index 0000000000..a3684a5fb8 --- /dev/null +++ b/orchagent/p4orch/p4orch_util.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "ipaddress.h" +#include "ipprefix.h" +#include "macaddress.h" + +namespace p4orch +{ + +// Field names in P4RT APP DB entry. +constexpr char *kRouterInterfaceId = "router_interface_id"; +constexpr char *kPort = "port"; +constexpr char *kSrcMac = "src_mac"; +constexpr char *kAction = "action"; +constexpr char *kActions = "actions"; +constexpr char *kWeight = "weight"; +constexpr char *kWatchPort = "watch_port"; +constexpr char *kNeighborId = "neighbor_id"; +constexpr char *kDstMac = "dst_mac"; +constexpr char *kNexthopId = "nexthop_id"; +constexpr char *kVrfId = "vrf_id"; +constexpr char *kIpv4Dst = "ipv4_dst"; +constexpr char *kIpv6Dst = "ipv6_dst"; +constexpr char *kWcmpGroupId = "wcmp_group_id"; +constexpr char *kSetNexthopId = "set_nexthop_id"; +constexpr char *kSetWcmpGroupId = "set_wcmp_group_id"; +constexpr char *kDrop = "drop"; +constexpr char *kStage = "stage"; +constexpr char *kSize = "size"; +constexpr char *kPriority = "priority"; +constexpr char *kPacketColor = "packet_color"; +constexpr char *kMeterUnit = "meter/unit"; +constexpr char *kCounterUnit = "counter/unit"; +constexpr char kFieldDelimiter = '/'; +constexpr char kTableKeyDelimiter = ':'; +constexpr char kDataMaskDelimiter = '&'; +constexpr char kPortsDelimiter = ','; +constexpr char *kMatchPrefix = "match"; +constexpr char *kActionParamPrefix = "param"; +constexpr char *kMeterPrefix = "meter"; +constexpr char *kMeterCir = "cir"; +constexpr char *kMeterCburst = "cburst"; +constexpr char *kMeterPir = "pir"; +constexpr char *kMeterPburst = "pburst"; +constexpr char *kControllerMetadata = "controller_metadata"; +constexpr char *kAclMatchFieldKind = "kind"; +constexpr char *kAclMatchFieldFormat = "format"; +constexpr char *kAclMatchFieldBitwidth = "bitwidth"; +constexpr char *kAclMatchFieldElements = "elements"; +constexpr char *kAclMatchFieldSaiField = "sai_field"; +constexpr char *kAclMatchFieldKindComposite = "composite"; +constexpr char *kAclMatchFieldKindUdf = "udf"; +constexpr char *kAclUdfBase = "base"; +constexpr char *kAclUdfOffset = "offset"; +constexpr char *kMirrorSessionId = "mirror_session_id"; +constexpr char *kSrcIp = "src_ip"; +constexpr char *kDstIp = "dst_ip"; +constexpr char *kTtl = "ttl"; +constexpr char *kTos = "tos"; +constexpr char *kMirrorAsIpv4Erspan = "mirror_as_ipv4_erspan"; +} // namespace p4orch + +// Prepends "match/" to the input string str to construct a new string. +std::string prependMatchField(const std::string &str); + +// Prepends "param/" to the input string str to construct a new string. +std::string prependParamField(const std::string &str); + +struct P4RouterInterfaceAppDbEntry +{ + std::string router_interface_id; + std::string port_name; + swss::MacAddress src_mac_address; + bool is_set_port_name = false; + bool is_set_src_mac = false; +}; + +struct P4NeighborAppDbEntry +{ + std::string router_intf_id; + swss::IpAddress neighbor_id; + swss::MacAddress dst_mac_address; + bool is_set_dst_mac = false; +}; + +// P4NextHopAppDbEntry holds entry deserialized from table +// APP_P4RT_NEXTHOP_TABLE_NAME. +struct P4NextHopAppDbEntry +{ + // Key + std::string next_hop_id; + // Fields + std::string router_interface_id; + swss::IpAddress neighbor_id; + bool is_set_router_interface_id = false; + bool is_set_neighbor_id = false; +}; + +struct P4MirrorSessionAppDbEntry +{ + // Key (match field) + std::string mirror_session_id; + + // fields (action parameters) + std::string port; + bool has_port = false; + + swss::IpAddress src_ip; + bool has_src_ip = false; + + swss::IpAddress dst_ip; + bool has_dst_ip = false; + + swss::MacAddress src_mac; + bool has_src_mac = false; + + swss::MacAddress dst_mac; + bool has_dst_mac = false; + + uint8_t ttl = 0; + bool has_ttl = false; + + uint8_t tos = 0; + bool has_tos = false; +}; + +struct P4ActionParamName +{ + std::string sai_action; + std::string p4_param_name; +}; + +struct P4PacketActionWithColor +{ + std::string packet_action; + std::string packet_color; +}; + +struct P4AclTableDefinitionAppDbEntry +{ + // Key + std::string acl_table_name; + // Fields + std::string stage; + uint32_t size; + uint32_t priority; + std::map match_field_lookup; + std::map> action_field_lookup; + std::map> packet_action_color_lookup; + std::string meter_unit; + std::string counter_unit; +}; + +struct P4AclMeterAppDb +{ + bool enabled; + uint64_t cir; + uint64_t cburst; + uint64_t pir; + uint64_t pburst; + + P4AclMeterAppDb() : enabled(false) + { + } +}; + +struct P4AclRuleAppDbEntry +{ + // Key + std::string acl_table_name; + std::map match_fvs; + uint32_t priority; + std::string db_key; + // Fields + std::string action; + std::map action_param_fvs; + P4AclMeterAppDb meter; +}; + +// Get the table name and key content from the given P4RT key. +// Outputs will be empty strings in case of error. +// Example: FIXED_NEIGHBOR_TABLE:{content} +// Table name: FIXED_NEIGHBOR_TABLE +// Key content: {content} +void parseP4RTKey(const std::string &key, std::string *table_name, std::string *key_content); + +// class KeyGenerator includes member functions to generate keys for entries +// stored in P4 Orch managers. +class KeyGenerator +{ + public: + static std::string generateRouteKey(const std::string &vrf_id, const swss::IpPrefix &ip_prefix); + + static std::string generateRouterInterfaceKey(const std::string &router_intf_id); + + static std::string generateNeighborKey(const std::string &router_intf_id, const swss::IpAddress &neighbor_id); + + static std::string generateNextHopKey(const std::string &next_hop_id); + + static std::string generateMirrorSessionKey(const std::string &mirror_session_id); + + static std::string generateWcmpGroupKey(const std::string &wcmp_group_id); + + static std::string generateAclRuleKey(const std::map &match_fields, + const std::string &priority); + + // Generates key used by object managers and centralized mapper. + // Takes map of as input and returns a concatenated string + // of the form id1=value1:id2=value2... + static std::string generateKey(const std::map &fv_map); +}; + +// Inserts single quote for a variable name. +// Returns a string. +template std::string QuotedVar(T name) +{ + std::ostringstream ss; + ss << std::quoted(name, '\''); + return ss.str(); +} diff --git a/orchagent/p4orch/route_manager.cpp b/orchagent/p4orch/route_manager.cpp new file mode 100644 index 0000000000..7732a143e5 --- /dev/null +++ b/orchagent/p4orch/route_manager.cpp @@ -0,0 +1,579 @@ +#include "p4orch/route_manager.h" + +#include +#include +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "swssnet.h" + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; + +extern sai_route_api_t *sai_route_api; + +extern CrmOrch *gCrmOrch; + +namespace +{ + +// This function will perform a route update. A route update will have two +// attribute update. If the second attribut update fails, the function will try +// to revert the first attribute. If the revert fails, the function will raise +// critical state. +ReturnCode UpdateRouteAttrs(sai_packet_action_t old_action, sai_packet_action_t new_action, sai_object_id_t old_nexthop, + sai_object_id_t new_nexthop, const std::string &route_entry_key, + sai_route_entry_t *rotue_entry) +{ + SWSS_LOG_ENTER(); + // For drop action, we will update the action attribute first. + bool action_first = (new_action == SAI_PACKET_ACTION_DROP); + + // First attribute + sai_attribute_t route_attr; + route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION : SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + if (action_first) + { + route_attr.value.s32 = new_action; + } + else + { + route_attr.value.oid = new_nexthop; + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr), + "Failed to set SAI attribute " + << (action_first ? "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION" + : "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID") + << " when updating route " << QuotedVar(route_entry_key)); + + // Second attribute + route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID : SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + if (action_first) + { + route_attr.value.oid = new_nexthop; + } + else + { + route_attr.value.s32 = new_action; + } + ReturnCode status; + auto sai_status = sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr); + if (sai_status == SAI_STATUS_SUCCESS) + { + return ReturnCode(); + } + status = ReturnCode(sai_status) << "Failed to set SAI attribute " + << (action_first ? "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID" + : "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION") + << " when updating route " << QuotedVar(route_entry_key); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", status.message().c_str(), sai_serialize_status(sai_status).c_str()); + + // Revert the first attribute + route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION : SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + if (action_first) + { + route_attr.value.s32 = old_action; + } + else + { + route_attr.value.oid = old_nexthop; + } + sai_status = sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + // Raise critical state if we fail to recover. + std::stringstream msg; + msg << "Failed to revert route attribute " + << (action_first ? "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION" : "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID") + << " for route " << QuotedVar(route_entry_key); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", msg.str().c_str(), sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE(msg.str()); + } + + return status; +} + +} // namespace + +bool RouteManager::mergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret) +{ + SWSS_LOG_ENTER(); + + *ret = src; + ret->sai_route_entry = dest.sai_route_entry; + if (ret->action.empty()) + { + ret->action = dest.action; + } + if (ret->action != dest.action || ret->nexthop_id != dest.nexthop_id || ret->wcmp_group != dest.wcmp_group) + { + return true; + } + return false; +} + +ReturnCodeOr RouteManager::deserializeRouteEntry(const std::string &key, + const std::vector &attributes, + const std::string &table_name) +{ + SWSS_LOG_ENTER(); + + P4RouteEntry route_entry = {}; + std::string route_prefix; + try + { + nlohmann::json j = nlohmann::json::parse(key); + route_entry.vrf_id = j[prependMatchField(p4orch::kVrfId)]; + if (table_name == APP_P4RT_IPV4_TABLE_NAME) + { + if (j.find(prependMatchField(p4orch::kIpv4Dst)) != j.end()) + { + route_prefix = j[prependMatchField(p4orch::kIpv4Dst)]; + } + else + { + route_prefix = "0.0.0.0/0"; + } + } + else + { + if (j.find(prependMatchField(p4orch::kIpv6Dst)) != j.end()) + { + route_prefix = j[prependMatchField(p4orch::kIpv6Dst)]; + } + else + { + route_prefix = "::/0"; + } + } + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize route key"; + } + try + { + route_entry.route_prefix = swss::IpPrefix(route_prefix); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid IP prefix " << QuotedVar(route_prefix); + } + + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == p4orch::kAction) + { + route_entry.action = value; + } + else if (field == prependParamField(p4orch::kNexthopId)) + { + route_entry.nexthop_id = value; + } + else if (field == prependParamField(p4orch::kWcmpGroupId)) + { + route_entry.wcmp_group = value; + } + else if (field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in " << table_name; + } + } + + return route_entry; +} + +P4RouteEntry *RouteManager::getRouteEntry(const std::string &route_entry_key) +{ + SWSS_LOG_ENTER(); + + if (m_routeTable.find(route_entry_key) == m_routeTable.end()) + return nullptr; + + return &m_routeTable[route_entry_key]; +} + +ReturnCode RouteManager::validateRouteEntry(const P4RouteEntry &route_entry) +{ + SWSS_LOG_ENTER(); + + if (!route_entry.nexthop_id.empty()) + { + auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Nexthop ID " << QuotedVar(route_entry.nexthop_id) << " does not exist"; + } + } + if (!route_entry.wcmp_group.empty()) + { + auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "WCMP group " << QuotedVar(route_entry.wcmp_group) << " does not exist"; + } + } + if (!route_entry.vrf_id.empty() && !m_vrfOrch->isVRFexists(route_entry.vrf_id)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "No VRF found with name " << QuotedVar(route_entry.vrf_id); + } + return ReturnCode(); +} + +ReturnCode RouteManager::validateSetRouteEntry(const P4RouteEntry &route_entry) +{ + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + bool exist_in_mapper = m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + if (route_entry_ptr == nullptr && exist_in_mapper) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Route entry does not exist in manager but exists in the " + "centralized map"; + } + if (route_entry_ptr != nullptr && !exist_in_mapper) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Route entry exists in manager but does not exist in the " + "centralized map"; + } + std::string action = route_entry.action; + // If action is empty, this could be an update. + if (action.empty()) + { + if (route_entry_ptr == nullptr) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Empty action for route"; + } + action = route_entry_ptr->action; + } + if (action == p4orch::kSetNexthopId) + { + if (route_entry.nexthop_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Empty nexthop_id for route with nexthop_id action"; + } + if (!route_entry.wcmp_group.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty wcmp_group_id for route with nexthop_id action"; + } + } + else if (action == p4orch::kSetWcmpGroupId) + { + if (!route_entry.nexthop_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty nexthop_id for route with wcmp_group action"; + } + if (route_entry.wcmp_group.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Empty wcmp_group_id for route with wcmp_group action"; + } + } + else if (action == p4orch::kDrop) + { + if (!route_entry.nexthop_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty nexthop_id for route with drop action"; + } + if (!route_entry.wcmp_group.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty wcmp_group_id for route with drop action"; + } + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid action " << QuotedVar(action); + } + return ReturnCode(); +} + +ReturnCode RouteManager::validateDelRouteEntry(const P4RouteEntry &route_entry) +{ + if (getRouteEntry(route_entry.route_entry_key) == nullptr) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Route entry does not exist"; + } + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Route entry does not exist in the centralized map"); + } + if (!route_entry.action.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty action for Del route"; + } + if (!route_entry.nexthop_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty nexthop_id for Del route"; + } + if (!route_entry.wcmp_group.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty wcmp_group for Del route"; + } + return ReturnCode(); +} + +ReturnCode RouteManager::createRouteEntry(const P4RouteEntry &route_entry) +{ + SWSS_LOG_ENTER(); + + sai_route_entry_t sai_route_entry; + sai_route_entry.vr_id = m_vrfOrch->getVRFid(route_entry.vrf_id); + sai_route_entry.switch_id = gSwitchId; + copy(sai_route_entry.destination, route_entry.route_prefix); + if (route_entry.action == p4orch::kSetNexthopId) + { + auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); + sai_object_id_t next_hop_oid; + m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key, &next_hop_oid); + sai_attribute_t route_attr; + route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + route_attr.value.oid = next_hop_oid; + // Default SAI_ROUTE_ATTR_PACKET_ACTION is SAI_PACKET_ACTION_FORWARD. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->create_route_entry(&sai_route_entry, /*size=*/1, &route_attr), + "Failed to create route " << QuotedVar(route_entry.route_entry_key) + << " with next hop " + << QuotedVar(route_entry.nexthop_id)); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key); + } + else if (route_entry.action == p4orch::kSetWcmpGroupId) + { + auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); + sai_object_id_t wcmp_group_oid; + m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &wcmp_group_oid); + sai_attribute_t route_attr; + route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + route_attr.value.oid = wcmp_group_oid; + // Default SAI_ROUTE_ATTR_PACKET_ACTION is SAI_PACKET_ACTION_FORWARD. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->create_route_entry(&sai_route_entry, /*size=*/1, &route_attr), + "Failed to create route " << QuotedVar(route_entry.route_entry_key) + << " with wcmp group " + << QuotedVar(route_entry.wcmp_group)); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + } + else + { + sai_attribute_t route_attr; + route_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + route_attr.value.s32 = SAI_PACKET_ACTION_DROP; + CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->create_route_entry(&sai_route_entry, /*size=*/1, &route_attr), + "Failed to create route " << QuotedVar(route_entry.route_entry_key) + << " with action drop"); + } + + m_routeTable[route_entry.route_entry_key] = route_entry; + m_routeTable[route_entry.route_entry_key].sai_route_entry = sai_route_entry; + m_p4OidMapper->setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + if (route_entry.route_prefix.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + m_vrfOrch->increaseVrfRefCount(route_entry.vrf_id); + return ReturnCode(); +} + +ReturnCodeOr RouteManager::getNexthopOid(const P4RouteEntry &route_entry) +{ + sai_object_id_t oid = SAI_NULL_OBJECT_ID; + if (route_entry.action == p4orch::kSetNexthopId) + { + auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key, &oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Nexthop " << QuotedVar(route_entry.nexthop_id) + << " does not exist"); + } + } + else if (route_entry.action == p4orch::kSetWcmpGroupId) + { + auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("WCMP group " << QuotedVar(route_entry.wcmp_group) + << " does not exist"); + } + } + return oid; +} + +ReturnCode RouteManager::updateRouteEntry(const P4RouteEntry &route_entry) +{ + SWSS_LOG_ENTER(); + + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + P4RouteEntry new_route_entry; + if (!mergeRouteEntry(*route_entry_ptr, route_entry, &new_route_entry)) + { + return ReturnCode(); + } + + ASSIGN_OR_RETURN(sai_object_id_t old_nexthop, getNexthopOid(*route_entry_ptr)); + ASSIGN_OR_RETURN(sai_object_id_t new_nexthop, getNexthopOid(new_route_entry)); + RETURN_IF_ERROR(UpdateRouteAttrs( + (route_entry_ptr->action == p4orch::kDrop) ? SAI_PACKET_ACTION_DROP : SAI_PACKET_ACTION_FORWARD, + (new_route_entry.action == p4orch::kDrop) ? SAI_PACKET_ACTION_DROP : SAI_PACKET_ACTION_FORWARD, old_nexthop, + new_nexthop, new_route_entry.route_entry_key, &new_route_entry.sai_route_entry)); + + if (new_route_entry.action == p4orch::kSetNexthopId) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(new_route_entry.nexthop_id)); + } + if (new_route_entry.action == p4orch::kSetWcmpGroupId) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(new_route_entry.wcmp_group)); + } + + if (route_entry_ptr->action == p4orch::kSetNexthopId) + { + if (new_route_entry.action != p4orch::kSetNexthopId || + new_route_entry.nexthop_id != route_entry_ptr->nexthop_id) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(route_entry_ptr->nexthop_id)); + } + } + if (route_entry_ptr->action == p4orch::kSetWcmpGroupId) + { + if (new_route_entry.action != p4orch::kSetWcmpGroupId || + new_route_entry.wcmp_group != route_entry_ptr->wcmp_group) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry_ptr->wcmp_group)); + } + } + m_routeTable[route_entry.route_entry_key] = new_route_entry; + return ReturnCode(); +} + +ReturnCode RouteManager::deleteRouteEntry(const P4RouteEntry &route_entry) +{ + SWSS_LOG_ENTER(); + + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->remove_route_entry(&route_entry_ptr->sai_route_entry), + "Failed to delete route " << QuotedVar(route_entry.route_entry_key)); + + if (route_entry_ptr->action == p4orch::kSetNexthopId) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(route_entry_ptr->nexthop_id)); + } + if (route_entry_ptr->action == p4orch::kSetWcmpGroupId) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry_ptr->wcmp_group)); + } + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + if (route_entry.route_prefix.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + m_vrfOrch->decreaseVrfRefCount(route_entry.vrf_id); + m_routeTable.erase(route_entry.route_entry_key); + return ReturnCode(); +} + +void RouteManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void RouteManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto route_entry_or = deserializeRouteEntry(key, attributes, table_name); + if (!route_entry_or.ok()) + { + status = route_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &route_entry = *route_entry_or; + + status = validateRouteEntry(route_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Route APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + status = validateSetRouteEntry(route_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Set Route APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + } + else if (getRouteEntry(route_entry.route_entry_key) == nullptr) + { + status = createRouteEntry(route_entry); + } + else + { + status = updateRouteEntry(route_entry); + } + } + else if (operation == DEL_COMMAND) + { + status = validateDelRouteEntry(route_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Del Route APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + } + else + { + status = deleteRouteEntry(route_entry); + } + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} diff --git a/orchagent/p4orch/route_manager.h b/orchagent/p4orch/route_manager.h new file mode 100644 index 0000000000..6e494709b3 --- /dev/null +++ b/orchagent/p4orch/route_manager.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +#include "ipprefix.h" +#include "orch.h" +#include "p4orch/next_hop_manager.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" +#include "vrforch.h" +extern "C" +{ +#include "sai.h" +} + +struct P4RouteEntry +{ + std::string route_entry_key; // Unique key of a route entry. + std::string vrf_id; + swss::IpPrefix route_prefix; + std::string action; + std::string nexthop_id; + std::string wcmp_group; + sai_route_entry_t sai_route_entry; +}; + +// P4RouteTable: Route ID, P4RouteEntry +typedef std::unordered_map P4RouteTable; + +class RouteManager : public ObjectManagerInterface +{ + public: + RouteManager(P4OidMapper *p4oidMapper, VRFOrch *vrfOrch, ResponsePublisherInterface *publisher) : m_vrfOrch(vrfOrch) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + virtual ~RouteManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + // Applies route entry updates from src to dest. The merged result will be + // stored in ret. + // The src should have passed all validation checks. + // Return true if there are updates, false otherwise. + bool mergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret); + + // Converts db table entry into P4RouteEntry. + ReturnCodeOr deserializeRouteEntry(const std::string &key, + const std::vector &attributes, + const std::string &table_name); + + // Gets the internal cached route entry by its key. + // Return nullptr if corresponding route entry is not cached. + P4RouteEntry *getRouteEntry(const std::string &route_entry_key); + + // Validated non-empty fields in a route entry. + ReturnCode validateRouteEntry(const P4RouteEntry &route_entry); + + // Performs route entry validation for SET command. + ReturnCode validateSetRouteEntry(const P4RouteEntry &route_entry); + + // Performs route entry validation for DEL command. + ReturnCode validateDelRouteEntry(const P4RouteEntry &route_entry); + + // Creates a route entry. + // Returns a SWSS status code. + ReturnCode createRouteEntry(const P4RouteEntry &route_entry); + + // Updates a route entry. + // Returns a SWSS status code. + ReturnCode updateRouteEntry(const P4RouteEntry &route_entry); + + // Deletes a route entry. + // Returns a SWSS status code. + ReturnCode deleteRouteEntry(const P4RouteEntry &route_entry); + + // Returns the nexthop OID for a given route entry. + // This method will raise critical state if the OID cannot be found. So this + // should only be called after validation. + ReturnCodeOr getNexthopOid(const P4RouteEntry &route_entry); + + P4RouteTable m_routeTable; + P4OidMapper *m_p4OidMapper; + VRFOrch *m_vrfOrch; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class RouteManagerTest; +}; diff --git a/orchagent/p4orch/router_interface_manager.cpp b/orchagent/p4orch/router_interface_manager.cpp new file mode 100644 index 0000000000..ea9abf083a --- /dev/null +++ b/orchagent/p4orch/router_interface_manager.cpp @@ -0,0 +1,398 @@ +#include "p4orch/router_interface_manager.h" + +#include +#include +#include +#include +#include + +#include "directory.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "vrforch.h" + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; + +extern sai_router_interface_api_t *sai_router_intfs_api; + +extern PortsOrch *gPortsOrch; +extern Directory gDirectory; + +namespace +{ + +ReturnCode validateRouterInterfaceAppDbEntry(const P4RouterInterfaceAppDbEntry &app_db_entry) +{ + // Perform generic APP DB entry validations. Operation specific validations + // will be done by the respective request process methods. + + if (app_db_entry.is_set_port_name) + { + Port port; + if (!gPortsOrch->getPort(app_db_entry.port_name, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Port " << QuotedVar(app_db_entry.port_name) << " does not exist"; + } + } + + if ((app_db_entry.is_set_src_mac) && (app_db_entry.src_mac_address.to_string() == "00:00:00:00:00:00")) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid source mac address " << QuotedVar(app_db_entry.src_mac_address.to_string()); + } + + return ReturnCode(); +} + +} // namespace + +ReturnCodeOr RouterInterfaceManager::deserializeRouterIntfEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4RouterInterfaceAppDbEntry app_db_entry = {}; + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.router_interface_id = j[prependMatchField(p4orch::kRouterInterfaceId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize router interface id"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kPort)) + { + app_db_entry.port_name = value; + app_db_entry.is_set_port_name = true; + } + else if (field == prependParamField(p4orch::kSrcMac)) + { + try + { + app_db_entry.src_mac_address = swss::MacAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + app_db_entry.is_set_src_mac = true; + } + else if (field != p4orch::kAction && field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +P4RouterInterfaceEntry *RouterInterfaceManager::getRouterInterfaceEntry(const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + if (m_routerIntfTable.find(router_intf_key) == m_routerIntfTable.end()) + return nullptr; + + return &m_routerIntfTable[router_intf_key]; +} + +ReturnCode RouterInterfaceManager::createRouterInterface(const std::string &router_intf_key, + P4RouterInterfaceEntry &router_intf_entry) +{ + SWSS_LOG_ENTER(); + + if (getRouterInterfaceEntry(router_intf_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "Router interface " << QuotedVar(router_intf_entry.router_interface_id) + << " already exists"); + } + + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Router interface " << QuotedVar(router_intf_key) + << " already exists in the centralized map"); + } + + Port port; + if (!gPortsOrch->getPort(router_intf_entry.port_name, port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(router_intf_entry.port_name)); + } + + std::vector attrs; + sai_attribute_t attr; + + // Map all P4 router interfaces to default VRF as virtual router is mandatory + // parameter for creation of router interfaces in SAI. + attr.id = SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID; + attr.value.oid = gVirtualRouterId; + attrs.push_back(attr); + + // If mac address is not set then swss::MacAddress initializes mac address + // to 00:00:00:00:00:00. + if (router_intf_entry.src_mac_address.to_string() != "00:00:00:00:00:00") + { + attr.id = SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, router_intf_entry.src_mac_address.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + } + + attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE; + switch (port.m_type) + { + case Port::PHY: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attrs.push_back(attr); + attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + attr.value.oid = port.m_port_id; + break; + case Port::LAG: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attrs.push_back(attr); + attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + attr.value.oid = port.m_lag_id; + break; + case Port::VLAN: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_VLAN; + attrs.push_back(attr); + attr.id = SAI_ROUTER_INTERFACE_ATTR_VLAN_ID; + attr.value.oid = port.m_vlan_info.vlan_oid; + break; + // TODO: add support for PORT::SUBPORT + default: + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unsupported port type: " << port.m_type); + } + attrs.push_back(attr); + + // Configure port MTU on router interface + attr.id = SAI_ROUTER_INTERFACE_ATTR_MTU; + attr.value.u32 = port.m_mtu; + attrs.push_back(attr); + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->create_router_interface(&router_intf_entry.router_interface_oid, gSwitchId, + (uint32_t)attrs.size(), attrs.data()), + "Failed to create router interface " << QuotedVar(router_intf_entry.router_interface_id)); + + gPortsOrch->increasePortRefCount(router_intf_entry.port_name); + gDirectory.get()->increaseVrfRefCount(gVirtualRouterId); + + m_routerIntfTable[router_intf_key] = router_intf_entry; + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, router_intf_entry.router_interface_oid); + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::removeRouterInterface(const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + auto *router_intf_entry = getRouterInterfaceEntry(router_intf_key); + if (router_intf_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router interface entry with key " << QuotedVar(router_intf_key) << " does not exist"); + } + + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for router interface " + << QuotedVar(router_intf_entry->router_interface_id)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Router interface " << QuotedVar(router_intf_entry->router_interface_id) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->remove_router_interface(router_intf_entry->router_interface_oid), + "Failed to remove router interface " << QuotedVar(router_intf_entry->router_interface_id)); + + gPortsOrch->decreasePortRefCount(router_intf_entry->port_name); + gDirectory.get()->decreaseVrfRefCount(gVirtualRouterId); + + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key); + m_routerIntfTable.erase(router_intf_key); + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::setSourceMacAddress(P4RouterInterfaceEntry *router_intf_entry, + const swss::MacAddress &mac_address) +{ + SWSS_LOG_ENTER(); + + if (router_intf_entry->src_mac_address == mac_address) + return ReturnCode(); + + sai_attribute_t attr; + attr.id = SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, mac_address.getMac(), sizeof(sai_mac_t)); + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->set_router_interface_attribute(router_intf_entry->router_interface_oid, &attr), + "Failed to set mac address " << QuotedVar(mac_address.to_string()) << " on router interface " + << QuotedVar(router_intf_entry->router_interface_id)); + + router_intf_entry->src_mac_address = mac_address; + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::processAddRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + // Perform operation specific validations. + if (!app_db_entry.is_set_port_name) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << p4orch::kPort + << " is mandatory to create router interface. Failed to create " + "router interface " + << QuotedVar(app_db_entry.router_interface_id)); + } + + P4RouterInterfaceEntry router_intf_entry(app_db_entry.router_interface_id, app_db_entry.port_name, + app_db_entry.src_mac_address); + auto status = createRouterInterface(router_intf_key, router_intf_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create router interface with key %s", QuotedVar(router_intf_key).c_str()); + } + + return status; +} + +ReturnCode RouterInterfaceManager::processUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + P4RouterInterfaceEntry *router_intf_entry) +{ + SWSS_LOG_ENTER(); + + // TODO: port_id is a create_only parameter in SAI. In order + // to update port name, current interface needs to be deleted and a new + // interface with updated parameters needs to be created. + if (app_db_entry.is_set_port_name && router_intf_entry->port_name != app_db_entry.port_name) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Updating port name for existing router interface is not " + "supported. Cannot update port name to " + << QuotedVar(app_db_entry.port_name) << " for router interface " + << QuotedVar(router_intf_entry->router_interface_id)); + } + + if (app_db_entry.is_set_src_mac) + { + auto status = setSourceMacAddress(router_intf_entry, app_db_entry.src_mac_address); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update source mac address with key %s", + QuotedVar(router_intf_entry->router_interface_id).c_str()); + return status; + } + } + + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::processDeleteRequest(const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeRouterInterface(router_intf_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove router interface with key %s", QuotedVar(router_intf_key).c_str()); + } + + return status; +} + +void RouterInterfaceManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void RouterInterfaceManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeRouterIntfEntry(db_key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + status = validateRouterInterfaceAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Router Interface APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *router_intf_entry = getRouterInterfaceEntry(router_intf_key); + if (router_intf_entry == nullptr) + { + // Create router interface + status = processAddRequest(app_db_entry, router_intf_key); + } + else + { + // Modify existing router interface + status = processUpdateRequest(app_db_entry, router_intf_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete router interface + status = processDeleteRequest(router_intf_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} diff --git a/orchagent/p4orch/router_interface_manager.h b/orchagent/p4orch/router_interface_manager.h new file mode 100644 index 0000000000..a300b2a7a4 --- /dev/null +++ b/orchagent/p4orch/router_interface_manager.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +#include "macaddress.h" +#include "orch.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +struct P4RouterInterfaceEntry +{ + std::string router_interface_id; + std::string port_name; + swss::MacAddress src_mac_address; + sai_object_id_t router_interface_oid = 0; + + P4RouterInterfaceEntry() = default; + P4RouterInterfaceEntry(const std::string &router_intf_id, const std::string &port, + const swss::MacAddress &mac_address) + : router_interface_id(router_intf_id), port_name(port), src_mac_address(mac_address) + { + } +}; + +// P4RouterInterfaceTable: Router Interface key, P4RouterInterfaceEntry +typedef std::unordered_map P4RouterInterfaceTable; + +class RouterInterfaceManager : public ObjectManagerInterface +{ + public: + RouterInterfaceManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + virtual ~RouterInterfaceManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + ReturnCodeOr deserializeRouterIntfEntry( + const std::string &key, const std::vector &attributes); + P4RouterInterfaceEntry *getRouterInterfaceEntry(const std::string &router_intf_key); + ReturnCode createRouterInterface(const std::string &router_intf_key, P4RouterInterfaceEntry &router_intf_entry); + ReturnCode removeRouterInterface(const std::string &router_intf_key); + ReturnCode setSourceMacAddress(P4RouterInterfaceEntry *router_intf_entry, const swss::MacAddress &mac_address); + ReturnCode processAddRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, const std::string &router_intf_key); + ReturnCode processUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + P4RouterInterfaceEntry *router_intf_entry); + ReturnCode processDeleteRequest(const std::string &router_intf_key); + + P4RouterInterfaceTable m_routerIntfTable; + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class RouterInterfaceManagerTest; +}; diff --git a/orchagent/p4orch/tests/Makefile.am b/orchagent/p4orch/tests/Makefile.am new file mode 100644 index 0000000000..489acd8f99 --- /dev/null +++ b/orchagent/p4orch/tests/Makefile.am @@ -0,0 +1,88 @@ +ORCHAGENT_DIR = $(top_srcdir)/orchagent +P4ORCH_DIR = $(ORCHAGENT_DIR)/p4orch +INCLUDES = -I $(top_srcdir) -I $(ORCHAGENT_DIR) -I $(P4ORCH_DIR) -I $(top_srcdir)/lib -I $(ORCHAGENT_DIR)/flex_counter + +CFLAGS_SAI = -I /usr/include/sai + +bin_PROGRAMS = p4orch_tests p4orch_tests_asan p4orch_tests_tsan p4orch_tests_usan + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g -DNDEBUG +endif + +CFLAGS_GTEST = +LDADD_GTEST = -lgtest -lgtest_main -lgmock -lgmock_main +CFLAGS_COVERAGE = --coverage -fprofile-arcs -ftest-coverage +LDADD_COVERAGE = -lgcov +CFLAGS_ASAN = -fsanitize=address +CFLAGS_TSAN = -fsanitize=thread +CFLAGS_USAN = -fsanitize=undefined + +p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ + $(ORCHAGENT_DIR)/vrforch.cpp \ + $(ORCHAGENT_DIR)/vxlanorch.cpp \ + $(ORCHAGENT_DIR)/copporch.cpp \ + $(ORCHAGENT_DIR)/switchorch.cpp \ + $(ORCHAGENT_DIR)/request_parser.cpp \ + $(ORCHAGENT_DIR)/flex_counter/flex_counter_manager.cpp \ + $(P4ORCH_DIR)/p4oidmapper.cpp \ + $(P4ORCH_DIR)/p4orch.cpp \ + $(P4ORCH_DIR)/p4orch_util.cpp \ + $(P4ORCH_DIR)/router_interface_manager.cpp \ + $(P4ORCH_DIR)/neighbor_manager.cpp \ + $(P4ORCH_DIR)/next_hop_manager.cpp \ + $(P4ORCH_DIR)/route_manager.cpp \ + $(P4ORCH_DIR)/acl_util.cpp \ + $(P4ORCH_DIR)/acl_table_manager.cpp \ + $(P4ORCH_DIR)/acl_rule_manager.cpp \ + $(P4ORCH_DIR)/wcmp_manager.cpp \ + $(P4ORCH_DIR)/mirror_session_manager.cpp \ + $(top_srcdir)/tests/mock_tests/fake_response_publisher.cpp \ + fake_portorch.cpp \ + fake_crmorch.cpp \ + fake_dbconnector.cpp \ + fake_producertable.cpp \ + fake_consumerstatetable.cpp \ + fake_subscriberstatetable.cpp \ + fake_notificationconsumer.cpp \ + fake_table.cpp \ + p4oidmapper_test.cpp \ + p4orch_util_test.cpp \ + return_code_test.cpp \ + route_manager_test.cpp \ + next_hop_manager_test.cpp \ + wcmp_manager_test.cpp \ + acl_manager_test.cpp \ + router_interface_manager_test.cpp \ + neighbor_manager_test.cpp \ + mirror_session_manager_test.cpp \ + test_main.cpp \ + mock_sai_acl.cpp \ + mock_sai_hostif.cpp \ + mock_sai_serialize.cpp \ + mock_sai_switch.cpp \ + mock_sai_udf.cpp + +p4orch_tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_COVERAGE) $(CFLAGS_SAI) +p4orch_tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_COVERAGE) $(CFLAGS_SAI) +p4orch_tests_LDADD = $(LDADD_GTEST) $(LDADD_COVERAGE) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq + +p4orch_tests_asan_SOURCES = $(p4orch_tests_SOURCES) +p4orch_tests_asan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_ASAN) $(CFLAGS_SAI) +p4orch_tests_asan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_ASAN) $(CFLAGS_SAI) +p4orch_tests_asan_LDFLAGS = $(CFLAGS_ASAN) +p4orch_tests_asan_LDADD = $(LDADD_GTEST) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq + +p4orch_tests_tsan_SOURCES = $(p4orch_tests_SOURCES) +p4orch_tests_tsan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_TSAN) $(CFLAGS_SAI) +p4orch_tests_tsan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_TSAN) $(CFLAGS_SAI) +p4orch_tests_tsan_LDFLAGS = $(CFLAGS_TSAN) +p4orch_tests_tsan_LDADD = $(LDADD_GTEST) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq + +p4orch_tests_usan_SOURCES = $(p4orch_tests_SOURCES) +p4orch_tests_usan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_USAN) $(CFLAGS_SAI) +p4orch_tests_usan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_USAN) $(CFLAGS_SAI) +p4orch_tests_usan_LDFLAGS = $(CFLAGS_USAN) +p4orch_tests_usan_LDADD = $(LDADD_GTEST) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq diff --git a/orchagent/p4orch/tests/acl_manager_test.cpp b/orchagent/p4orch/tests/acl_manager_test.cpp new file mode 100644 index 0000000000..64ba37e5a3 --- /dev/null +++ b/orchagent/p4orch/tests/acl_manager_test.cpp @@ -0,0 +1,4253 @@ +#include +#include + +#include +#include +#include + +#include "acl_rule_manager.h" +#include "acl_table_manager.h" +#include "acl_util.h" +#include "acltable.h" +#include "json.hpp" +#include "mock_sai_acl.h" +#include "mock_sai_hostif.h" +#include "mock_sai_policer.h" +#include "mock_sai_serialize.h" +#include "mock_sai_switch.h" +#include "mock_sai_udf.h" +#include "p4orch.h" +#include "return_code.h" +#include "switchorch.h" +#include "table.h" +#include "tokenize.h" +#include "vrforch.h" + +extern swss::DBConnector *gAppDb; +extern swss::DBConnector *gStateDb; +extern swss::DBConnector *gCountersDb; +extern swss::DBConnector *gConfigDb; +extern sai_acl_api_t *sai_acl_api; +extern sai_policer_api_t *sai_policer_api; +extern sai_hostif_api_t *sai_hostif_api; +extern sai_switch_api_t *sai_switch_api; +extern sai_udf_api_t *sai_udf_api; +extern int gBatchSize; +extern VRFOrch *gVrfOrch; +extern P4Orch *gP4Orch; +extern SwitchOrch *gSwitchOrch; +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVrfOid; +extern sai_object_id_t gTrapGroupStartOid; +extern sai_object_id_t gHostifStartOid; +extern sai_object_id_t gUserDefinedTrapStartOid; +extern char *gVrfName; +extern char *gMirrorSession1; +extern sai_object_id_t kMirrorSessionOid1; +extern char *gMirrorSession2; +extern sai_object_id_t kMirrorSessionOid2; +extern bool gIsNatSupported; + +namespace p4orch +{ +namespace test +{ + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +namespace +{ +constexpr sai_object_id_t kAclGroupIngressOid = 0xb00000000058f; +constexpr sai_object_id_t kAclGroupEgressOid = 0xb000000000591; +constexpr sai_object_id_t kAclGroupLookupOid = 0xb000000000592; +constexpr sai_object_id_t kAclTableIngressOid = 0x7000000000606; +constexpr sai_object_id_t kAclGroupMemberIngressOid = 0xc000000000607; +constexpr sai_object_id_t kAclIngressRuleOid1 = 1001; +constexpr sai_object_id_t kAclIngressRuleOid2 = 1002; +constexpr sai_object_id_t kAclMeterOid1 = 2001; +constexpr sai_object_id_t kAclMeterOid2 = 2002; +constexpr sai_object_id_t kAclCounterOid1 = 3001; +constexpr sai_object_id_t kUdfGroupOid1 = 4001; +constexpr sai_object_id_t kUdfMatchOid1 = 5001; +constexpr char *kAclIngressTableName = "ACL_PUNT_TABLE"; + +// Check the ACL stage sai_attribute_t list for ACL table group +bool MatchSaiAttributeAclGroupStage(const sai_acl_stage_t expected_stage, const sai_attribute_t *attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + for (int i = 0; i < 3; ++i) + { + switch (attr_list[i].id) + { + case SAI_ACL_TABLE_GROUP_ATTR_ACL_STAGE: + if (attr_list[i].value.s32 != expected_stage) + { + return false; + } + break; + case SAI_ACL_TABLE_GROUP_ATTR_TYPE: + if (attr_list[i].value.s32 != SAI_ACL_TABLE_GROUP_TYPE_PARALLEL) + { + return false; + } + break; + case SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST: + if (attr_list[i].value.s32list.count != 1 || + attr_list[i].value.s32list.list[0] != SAI_ACL_BIND_POINT_TYPE_SWITCH) + { + return false; + } + break; + default: + return false; + } + } + return true; +} + +// Check the ACL stage sai_attribute_t list for ACL table +bool MatchSaiAttributeAclTableStage(const sai_acl_stage_t expected_stage, const sai_attribute_t *attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + if (attr_list[0].id != SAI_ACL_TABLE_GROUP_ATTR_ACL_STAGE || attr_list[0].value.s32 != expected_stage) + { + return false; + } + + return true; +} + +bool MatchSaiSwitchAttrByAclStage(const sai_switch_attr_t expected_switch_attr, const sai_object_id_t group_oid, + const sai_attribute_t *attr) +{ + if (attr->id != expected_switch_attr || attr->value.oid != group_oid) + { + return false; + } + return true; +} + +std::string BuildMatchFieldJsonStrKindSaiField(std::string sai_field, std::string format = P4_FORMAT_HEX_STRING, + uint32_t bitwidth = 0) +{ + nlohmann::json match_json; + match_json[kAclMatchFieldKind] = kAclMatchFieldSaiField; + match_json[kAclMatchFieldSaiField] = sai_field; + match_json[kAclMatchFieldFormat] = format; + if (format != P4_FORMAT_STRING) + { + match_json[kAclMatchFieldBitwidth] = bitwidth; + } + return match_json.dump(); +} + +std::string BuildMatchFieldJsonStrKindComposite(std::vector elements, + std::string format = P4_FORMAT_HEX_STRING, uint32_t bitwidth = 0) +{ + nlohmann::json match_json; + match_json[kAclMatchFieldKind] = kAclMatchFieldKindComposite; + for (const auto element : elements) + { + match_json[kAclMatchFieldElements].push_back(element); + } + match_json[kAclMatchFieldFormat] = format; + match_json[kAclMatchFieldBitwidth] = bitwidth; + return match_json.dump(); +} + +std::string BuildMatchFieldJsonStrKindUdf(std::string base, uint32_t offset, std::string format = P4_FORMAT_HEX_STRING, + uint32_t bitwidth = 0) +{ + nlohmann::json match_json; + match_json[kAclMatchFieldKind] = kAclMatchFieldKindUdf; + match_json[kAclUdfBase] = base; + match_json[kAclUdfOffset] = offset; + match_json[kAclMatchFieldFormat] = format; + match_json[kAclMatchFieldBitwidth] = bitwidth; + return match_json.dump(); +} + +// Check if P4AclTableDefinitionAppDbEntry to P4AclTableDefinition mapping is as +// expected +void IsExpectedAclTableDefinitionMapping(const P4AclTableDefinition &acl_table_def, + const P4AclTableDefinitionAppDbEntry &app_db_entry) +{ + EXPECT_EQ(app_db_entry.acl_table_name, acl_table_def.acl_table_name); + EXPECT_EQ(app_db_entry.priority, acl_table_def.priority); + EXPECT_EQ(app_db_entry.size, acl_table_def.size); + EXPECT_EQ(app_db_entry.meter_unit, acl_table_def.meter_unit); + EXPECT_EQ(app_db_entry.counter_unit, acl_table_def.counter_unit); + for (const auto &raw_match_field : app_db_entry.match_field_lookup) + { + const auto &p4_match = fvField(raw_match_field); + const auto &aggr_match_str = fvValue(raw_match_field); + try + { + auto aggr_match_json = nlohmann::json::parse(aggr_match_str); + ASSERT_TRUE(aggr_match_json.is_object()); + auto kind = aggr_match_json[kAclMatchFieldKind]; + ASSERT_TRUE(!kind.is_null() && kind.is_string()); + if (kind == kAclMatchFieldKindComposite) + { + auto format_str = aggr_match_json[kAclMatchFieldFormat]; + ASSERT_FALSE(format_str.is_null() || !format_str.is_string()); + auto format_it = formatLookup.find(format_str); + ASSERT_NE(formatLookup.end(), format_it); + if (format_it->second != Format::STRING) + { + // bitwidth is required if the format is not "STRING" + auto bitwidth = aggr_match_json[kAclMatchFieldBitwidth]; + ASSERT_FALSE(bitwidth.is_null() || !bitwidth.is_number()); + } + auto elements = aggr_match_json[kAclMatchFieldElements]; + ASSERT_TRUE(!elements.is_null() && elements.is_array()); + if (elements[0][kAclMatchFieldKind] == kAclMatchFieldSaiField) + { + const auto &composite_sai_match_it = acl_table_def.composite_sai_match_fields_lookup.find(p4_match); + ASSERT_NE(acl_table_def.composite_sai_match_fields_lookup.end(), composite_sai_match_it); + for (const auto &element : composite_sai_match_it->second) + { + EXPECT_EQ(BYTE_BITWIDTH * IPV6_SINGLE_WORD_BYTES_LENGTH, element.bitwidth); + } + } + else if (elements[0][kAclMatchFieldKind] == kAclMatchFieldKindUdf) + { + const auto &composite_udf_match_it = acl_table_def.udf_fields_lookup.find(p4_match); + ASSERT_NE(acl_table_def.udf_fields_lookup.end(), composite_udf_match_it); + for (size_t i = 0; i < composite_udf_match_it->second.size(); i++) + { + EXPECT_EQ(elements[i][kAclMatchFieldBitwidth], + composite_udf_match_it->second[i].length * BYTE_BITWIDTH); + EXPECT_EQ(elements[i][kAclUdfOffset], composite_udf_match_it->second[i].offset); + EXPECT_EQ(app_db_entry.acl_table_name + "-" + p4_match + "-" + std::to_string(i), + composite_udf_match_it->second[i].group_id); + ASSERT_NE(udfBaseLookup.end(), udfBaseLookup.find(elements[i][kAclUdfBase])); + EXPECT_EQ(udfBaseLookup.find(elements[i][kAclUdfBase])->second, + composite_udf_match_it->second[i].base); + } + } + else + { + FAIL() << "Invalid kind for composite field element: " << elements[0][kAclMatchFieldKind]; + } + } + else if (kind == kAclMatchFieldKindUdf) + { + const auto &udf_match_it = acl_table_def.udf_fields_lookup.find(p4_match); + ASSERT_NE(acl_table_def.udf_fields_lookup.end(), udf_match_it); + EXPECT_EQ(1, udf_match_it->second.size()); + EXPECT_EQ(aggr_match_json[kAclMatchFieldBitwidth], udf_match_it->second[0].length * BYTE_BITWIDTH); + EXPECT_EQ(aggr_match_json[kAclUdfOffset], udf_match_it->second[0].offset); + EXPECT_EQ(app_db_entry.acl_table_name + "-" + p4_match + "-0", udf_match_it->second[0].group_id); + ASSERT_NE(udfBaseLookup.end(), udfBaseLookup.find(aggr_match_json[kAclUdfBase])); + EXPECT_EQ(udfBaseLookup.find(aggr_match_json[kAclUdfBase])->second, udf_match_it->second[0].base); + } + else + { + EXPECT_EQ(kAclMatchFieldSaiField, kind); + auto match_field = aggr_match_json[kAclMatchFieldSaiField]; + ASSERT_TRUE(!match_field.is_null() && match_field.is_string()); + auto field_suffix = swss::tokenize(match_field, kFieldDelimiter); + const auto &sai_field = field_suffix[0]; + ASSERT_NE(aclMatchEntryAttrLookup.end(), aclMatchEntryAttrLookup.find(sai_field)); + ASSERT_NE(aclMatchTableAttrLookup.end(), aclMatchTableAttrLookup.find(sai_field)); + EXPECT_EQ(aclMatchEntryAttrLookup.find(sai_field)->second, + acl_table_def.sai_match_field_lookup.find(p4_match)->second.entry_attr); + EXPECT_EQ(aclMatchTableAttrLookup.find(sai_field)->second, + acl_table_def.sai_match_field_lookup.find(p4_match)->second.table_attr); + } + } + catch (std::exception &ex) + { + FAIL() << "Exception when parsing match field. ex: " << ex.what(); + } + } + for (const auto &action_field : app_db_entry.action_field_lookup) + { + const auto &sai_action_param_it = acl_table_def.rule_action_field_lookup.find(fvField(action_field)); + ASSERT_NE(acl_table_def.rule_action_field_lookup.end(), sai_action_param_it); + + for (size_t i = 0; i < fvValue(action_field).size(); ++i) + { + ASSERT_NE(aclActionLookup.end(), aclActionLookup.find(fvValue(action_field)[i].sai_action)); + EXPECT_EQ(sai_action_param_it->second[i].action, + aclActionLookup.find(fvValue(action_field)[i].sai_action)->second); + } + } + + for (const auto &packet_action_color : app_db_entry.packet_action_color_lookup) + { + const auto &sai_action_color_it = + acl_table_def.rule_packet_action_color_lookup.find(fvField(packet_action_color)); + ASSERT_NE(acl_table_def.rule_packet_action_color_lookup.end(), sai_action_color_it); + for (size_t i = 0; i < fvValue(packet_action_color).size(); ++i) + { + if (fvValue(packet_action_color)[i].packet_color.empty()) + { + // Not a colored packet action, should be ACL entry attribute + // SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION instead of ACL policy + // attribute + const auto rule_action_it = acl_table_def.rule_action_field_lookup.find(fvField(packet_action_color)); + ASSERT_NE(acl_table_def.rule_action_field_lookup.end(), rule_action_it); + bool found_packet_action = false; + for (const auto &action_with_param : rule_action_it->second) + { + if (action_with_param.param_value == fvValue(packet_action_color)[i].packet_action) + { + // Only one packet action is allowed and no parameter should be + // added for SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION attribute. + // Return false if multiple packet actions are found or + // parameter name is not empty + EXPECT_FALSE(found_packet_action || !action_with_param.param_name.empty()); + found_packet_action = true; + } + } + // No packet action was found, return false. + EXPECT_TRUE(found_packet_action); + continue; + } + const auto &packet_color_policer_attr_it = + aclPacketColorPolicerAttrLookup.find(fvValue(packet_action_color)[i].packet_color); + const auto &packet_action_it = aclPacketActionLookup.find(fvValue(packet_action_color)[i].packet_action); + ASSERT_NE(aclPacketColorPolicerAttrLookup.end(), packet_color_policer_attr_it); + ASSERT_NE(aclPacketActionLookup.end(), packet_action_it); + + const auto &sai_packet_action_it = sai_action_color_it->second.find(packet_color_policer_attr_it->second); + ASSERT_NE(sai_action_color_it->second.end(), sai_packet_action_it); + EXPECT_EQ(sai_packet_action_it->second, packet_action_it->second); + } + } +} + +// Check if P4AclRuleAppDbEntry to P4AclRule field mapping is as +// expected given table definition. Specific match and action value +// validation should be done in caller method +void IsExpectedAclRuleMapping(const P4AclRule *acl_rule, const P4AclRuleAppDbEntry &app_db_entry, + const P4AclTableDefinition &table_def) +{ + // Check table name and priority + EXPECT_EQ(app_db_entry.acl_table_name, acl_rule->acl_table_name); + EXPECT_EQ(app_db_entry.priority, acl_rule->priority); + // Check match field + for (const auto &app_db_match_fv : app_db_entry.match_fvs) + { + // TODO: Fake UDF field until SAI supports it + if (table_def.udf_fields_lookup.find(fvField(app_db_match_fv)) != table_def.udf_fields_lookup.end()) + continue; + const auto &composite_sai_match_fields_it = + table_def.composite_sai_match_fields_lookup.find(fvField(app_db_match_fv)); + if (composite_sai_match_fields_it != table_def.composite_sai_match_fields_lookup.end()) + { + for (const auto &composite_sai_match_field : composite_sai_match_fields_it->second) + { + const auto &match_fv_it = acl_rule->match_fvs.find(composite_sai_match_field.entry_attr); + ASSERT_NE(acl_rule->match_fvs.end(), match_fv_it); + EXPECT_TRUE(match_fv_it->second.aclfield.enable); + } + continue; + } + const auto &match_field_it = table_def.sai_match_field_lookup.find(fvField(app_db_match_fv)); + ASSERT_NE(table_def.sai_match_field_lookup.end(), match_field_it); + const auto &match_fv_it = acl_rule->match_fvs.find(match_field_it->second.entry_attr); + ASSERT_NE(acl_rule->match_fvs.end(), match_fv_it); + EXPECT_TRUE(match_fv_it->second.aclfield.enable); + } + // Check action field + ASSERT_EQ(acl_rule->p4_action, app_db_entry.action); + const auto &actions_field_it = table_def.rule_action_field_lookup.find(app_db_entry.action); + const auto &packet_action_color_it = table_def.rule_packet_action_color_lookup.find(app_db_entry.action); + ASSERT_NE(table_def.rule_action_field_lookup.end(), actions_field_it); + ASSERT_NE(table_def.rule_packet_action_color_lookup.end(), packet_action_color_it); + if (actions_field_it != table_def.rule_action_field_lookup.end()) + { + for (const auto &action_field : actions_field_it->second) + { + ASSERT_NE(acl_rule->action_fvs.find(action_field.action), acl_rule->action_fvs.end()); + } + } + // Check meter field value + if (packet_action_color_it != table_def.rule_packet_action_color_lookup.end() && + !packet_action_color_it->second.empty()) + { + ASSERT_EQ(acl_rule->meter.packet_color_actions, packet_action_color_it->second); + } + if (!table_def.meter_unit.empty()) + { + EXPECT_TRUE(acl_rule->meter.enabled); + EXPECT_EQ(SAI_POLICER_MODE_TR_TCM, acl_rule->meter.mode); + EXPECT_EQ(app_db_entry.meter.cir, acl_rule->meter.cir); + EXPECT_EQ(app_db_entry.meter.cburst, acl_rule->meter.cburst); + EXPECT_EQ(app_db_entry.meter.pir, acl_rule->meter.pir); + EXPECT_EQ(app_db_entry.meter.pburst, acl_rule->meter.pburst); + if (table_def.meter_unit == P4_METER_UNIT_BYTES) + { + EXPECT_EQ(SAI_METER_TYPE_BYTES, acl_rule->meter.type); + } + if (table_def.meter_unit == P4_METER_UNIT_PACKETS) + { + EXPECT_EQ(SAI_METER_TYPE_PACKETS, acl_rule->meter.type); + } + } + // Check counter field value + if (table_def.counter_unit.empty()) + { + EXPECT_FALSE(acl_rule->counter.packets_enabled); + EXPECT_FALSE(acl_rule->counter.bytes_enabled); + return; + } + if (table_def.counter_unit == P4_COUNTER_UNIT_BOTH) + { + EXPECT_TRUE(acl_rule->counter.bytes_enabled && acl_rule->counter.packets_enabled); + } + else if (table_def.counter_unit == P4_COUNTER_UNIT_BYTES) + { + EXPECT_TRUE(acl_rule->counter.bytes_enabled && !acl_rule->counter.packets_enabled); + } + else + { + EXPECT_TRUE(table_def.counter_unit == P4_COUNTER_UNIT_PACKETS && !acl_rule->counter.bytes_enabled && + acl_rule->counter.packets_enabled); + } +} + +std::vector getDefaultTableDefFieldValueTuples() +{ + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kStage, STAGE_INGRESS}); + attributes.push_back(swss::FieldValueTuple{kSize, "123"}); + attributes.push_back(swss::FieldValueTuple{kPriority, "234"}); + attributes.push_back(swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}); + attributes.push_back(swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC, P4_FORMAT_MAC)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6, P4_FORMAT_IPV6)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_ip", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IP)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_ipv4", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV4ANY)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_ipv6", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV6ANY)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_arp", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_arp_request", BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + + P4_IP_TYPE_BIT_ARP_REQUEST)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_arp_reply", BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + + P4_IP_TYPE_BIT_ARP_REPLY)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_next_header", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER)}); + attributes.push_back(swss::FieldValueTuple{"match/ttl", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL)}); + attributes.push_back( + swss::FieldValueTuple{"match/icmp_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/l4_dst_port", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT)}); + attributes.push_back(swss::FieldValueTuple{"action/copy_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_" + "PACKET_" + "COLOR_GREEN\"},{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\":\"traffic_class\"}]"}); + attributes.push_back(swss::FieldValueTuple{"action/punt_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_TRAP\"},{\"action\":\"SAI_ACL_" + "ENTRY_" + "ATTR_ACTION_SET_TC\",\"param\":\"traffic_class\"}]"}); + return attributes; +} + +P4AclTableDefinitionAppDbEntry getDefaultAclTableDefAppDbEntry() +{ + P4AclTableDefinitionAppDbEntry app_db_entry; + app_db_entry.acl_table_name = kAclIngressTableName; + app_db_entry.size = 123; + app_db_entry.stage = STAGE_INGRESS; + app_db_entry.priority = 234; + app_db_entry.meter_unit = P4_METER_UNIT_BYTES; + app_db_entry.counter_unit = P4_COUNTER_UNIT_BYTES; + // Match field mapping from P4 program to SAI entry attribute + app_db_entry.match_field_lookup["ether_type"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE); + app_db_entry.match_field_lookup["ether_dst"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC, P4_FORMAT_MAC); + app_db_entry.match_field_lookup["ether_src"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_MAC, P4_FORMAT_MAC); + app_db_entry.match_field_lookup["ipv6_dst"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["ipv6_next_header"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER); + app_db_entry.match_field_lookup["ttl"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL); + app_db_entry.match_field_lookup["in_ports"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IN_PORTS, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["in_port"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IN_PORT, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["out_ports"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUT_PORTS, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["out_port"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUT_PORT, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["is_ip"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IP); + app_db_entry.match_field_lookup["is_ipv4"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV4ANY); + app_db_entry.match_field_lookup["is_ipv6"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV6ANY); + app_db_entry.match_field_lookup["is_arp"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP); + app_db_entry.match_field_lookup["is_arp_request"] = BuildMatchFieldJsonStrKindSaiField( + std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP_REQUEST); + app_db_entry.match_field_lookup["is_arp_reply"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP_REPLY); + app_db_entry.match_field_lookup["tcp_flags"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TCP_FLAGS); + app_db_entry.match_field_lookup["ip_flags"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_FLAGS); + app_db_entry.match_field_lookup["l4_src_port"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_SRC_PORT); + app_db_entry.match_field_lookup["l4_dst_port"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT); + app_db_entry.match_field_lookup["ip_id"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_ID); + app_db_entry.match_field_lookup["inner_l4_src_port"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_L4_SRC_PORT); + app_db_entry.match_field_lookup["inner_l4_dst_port"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_L4_DST_PORT); + app_db_entry.match_field_lookup["dscp"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DSCP); + app_db_entry.match_field_lookup["inner_ip_src"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_SRC_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["inner_ip_dst"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_DST_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["inner_ipv6_src"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_SRC_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["inner_ipv6_dst"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_DST_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["ip_src"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["ip_dst"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["ipv6_src"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["tc"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TRAFFIC_CLASS); + app_db_entry.match_field_lookup["icmp_type"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE); + app_db_entry.match_field_lookup["icmp_code"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_CODE); + app_db_entry.match_field_lookup["tos"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TOS); + app_db_entry.match_field_lookup["icmpv6_type"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMPV6_TYPE); + app_db_entry.match_field_lookup["icmpv6_code"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMPV6_CODE); + app_db_entry.match_field_lookup["ecn"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ECN); + app_db_entry.match_field_lookup["inner_ip_protocol"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_IP_PROTOCOL); + app_db_entry.match_field_lookup["ip_protocol"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_PROTOCOL); + app_db_entry.match_field_lookup["ipv6_flow_label"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_FLOW_LABEL); + app_db_entry.match_field_lookup["tunnel_vni"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TUNNEL_VNI); + app_db_entry.match_field_lookup["ip_frag"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_FRAG, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["packet_vlan"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_PACKET_VLAN, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["outer_vlan_pri"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUTER_VLAN_PRI); + app_db_entry.match_field_lookup["outer_vlan_id"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUTER_VLAN_ID); + app_db_entry.match_field_lookup["outer_vlan_cfi"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUTER_VLAN_CFI); + app_db_entry.match_field_lookup["inner_vlan_pri"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_VLAN_PRI); + app_db_entry.match_field_lookup["inner_vlan_id"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_VLAN_ID); + app_db_entry.match_field_lookup["inner_vlan_cfi"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_VLAN_CFI); + app_db_entry.match_field_lookup["src_ipv6_64bit"] = BuildMatchFieldJsonStrKindComposite( + {nlohmann::json::parse(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IPV6_WORD3, P4_FORMAT_IPV6, 32)), + nlohmann::json::parse(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IPV6_WORD2, P4_FORMAT_IPV6, 32))}, + P4_FORMAT_IPV6, 64); + app_db_entry.match_field_lookup["arp_tpa"] = BuildMatchFieldJsonStrKindComposite( + {nlohmann::json::parse(BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 24, P4_FORMAT_HEX_STRING, 16)), + nlohmann::json::parse(BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 26, P4_FORMAT_HEX_STRING, 16))}, + P4_FORMAT_HEX_STRING, 32); + app_db_entry.match_field_lookup["udf2"] = + BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 56, P4_FORMAT_HEX_STRING, 16); + + // Action field mapping, from P4 action to SAI action + app_db_entry.action_field_lookup["set_packet_action"].push_back( + {.sai_action = P4_ACTION_PACKET_ACTION, .p4_param_name = "packet_action"}); + app_db_entry.action_field_lookup["copy_and_set_tc"].push_back( + {.sai_action = P4_ACTION_SET_TRAFFIC_CLASS, .p4_param_name = "traffic_class"}); + app_db_entry.action_field_lookup["punt_and_set_tc"].push_back( + {.sai_action = P4_ACTION_SET_TRAFFIC_CLASS, .p4_param_name = "traffic_class"}); + app_db_entry.packet_action_color_lookup["copy_and_set_tc"].push_back( + {.packet_action = P4_PACKET_ACTION_COPY, .packet_color = P4_PACKET_COLOR_GREEN}); + app_db_entry.packet_action_color_lookup["punt_and_set_tc"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = EMPTY_STRING}); + app_db_entry.packet_action_color_lookup["punt_non_green_pk"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = P4_PACKET_COLOR_YELLOW}); + app_db_entry.packet_action_color_lookup["punt_non_green_pk"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = P4_PACKET_COLOR_RED}); + app_db_entry.action_field_lookup["redirect"].push_back( + {.sai_action = P4_ACTION_REDIRECT, .p4_param_name = "target"}); + app_db_entry.action_field_lookup["endpoint_ip"].push_back( + {.sai_action = P4_ACTION_ENDPOINT_IP, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["mirror_ingress"].push_back( + {.sai_action = P4_ACTION_MIRROR_INGRESS, .p4_param_name = "target"}); + app_db_entry.action_field_lookup["mirror_egress"].push_back( + {.sai_action = P4_ACTION_MIRROR_EGRESS, .p4_param_name = "target"}); + app_db_entry.action_field_lookup["set_packet_color"].push_back( + {.sai_action = P4_ACTION_SET_PACKET_COLOR, .p4_param_name = "packet_color"}); + app_db_entry.action_field_lookup["set_src_mac"].push_back( + {.sai_action = P4_ACTION_SET_SRC_MAC, .p4_param_name = "mac_address"}); + app_db_entry.action_field_lookup["set_dst_mac"].push_back( + {.sai_action = P4_ACTION_SET_DST_MAC, .p4_param_name = "mac_address"}); + app_db_entry.action_field_lookup["set_src_ip"].push_back( + {.sai_action = P4_ACTION_SET_SRC_IP, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_dst_ip"].push_back( + {.sai_action = P4_ACTION_SET_DST_IP, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_src_ipv6"].push_back( + {.sai_action = P4_ACTION_SET_SRC_IPV6, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_dst_ipv6"].push_back( + {.sai_action = P4_ACTION_SET_DST_IPV6, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_dscp_and_ecn"].push_back( + {.sai_action = P4_ACTION_SET_DSCP, .p4_param_name = "dscp"}); + app_db_entry.action_field_lookup["set_dscp_and_ecn"].push_back( + {.sai_action = P4_ACTION_SET_ECN, .p4_param_name = "ecn"}); + app_db_entry.action_field_lookup["set_inner_vlan"].push_back( + {.sai_action = P4_ACTION_SET_INNER_VLAN_PRIORITY, .p4_param_name = "vlan_pri"}); + app_db_entry.action_field_lookup["set_inner_vlan"].push_back( + {.sai_action = P4_ACTION_SET_INNER_VLAN_ID, .p4_param_name = "vlan_id"}); + app_db_entry.action_field_lookup["set_outer_vlan"].push_back( + {.sai_action = P4_ACTION_SET_OUTER_VLAN_PRIORITY, .p4_param_name = "vlan_pri"}); + app_db_entry.action_field_lookup["set_outer_vlan"].push_back( + {.sai_action = P4_ACTION_SET_OUTER_VLAN_ID, .p4_param_name = "vlan_id"}); + app_db_entry.action_field_lookup["set_l4_src_port"].push_back( + {.sai_action = P4_ACTION_SET_L4_SRC_PORT, .p4_param_name = "port"}); + app_db_entry.action_field_lookup["set_l4_dst_port"].push_back( + {.sai_action = P4_ACTION_SET_L4_DST_PORT, .p4_param_name = "port"}); + app_db_entry.action_field_lookup["flood"].push_back({.sai_action = P4_ACTION_FLOOD, .p4_param_name = EMPTY_STRING}); + app_db_entry.action_field_lookup["decrement_ttl"].push_back( + {.sai_action = P4_ACTION_DECREMENT_TTL, .p4_param_name = EMPTY_STRING}); + app_db_entry.action_field_lookup["do_not_learn"].push_back( + {.sai_action = P4_ACTION_SET_DO_NOT_LEARN, .p4_param_name = EMPTY_STRING}); + app_db_entry.action_field_lookup["set_vrf"].push_back({.sai_action = P4_ACTION_SET_VRF, .p4_param_name = "vrf"}); + app_db_entry.action_field_lookup["qos_queue"].push_back( + {.sai_action = P4_ACTION_SET_QOS_QUEUE, .p4_param_name = "cpu_queue"}); + return app_db_entry; +} + +std::vector getDefaultRuleFieldValueTuples() +{ + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kAction, "copy_and_set_tc"}); + attributes.push_back(swss::FieldValueTuple{"param/traffic_class", "0x20"}); + attributes.push_back(swss::FieldValueTuple{"meter/cir", "80"}); + attributes.push_back(swss::FieldValueTuple{"meter/cburst", "80"}); + attributes.push_back(swss::FieldValueTuple{"meter/pir", "200"}); + attributes.push_back(swss::FieldValueTuple{"meter/pburst", "200"}); + attributes.push_back(swss::FieldValueTuple{"controller_metadata", "..."}); + return attributes; +} + +P4AclRuleAppDbEntry getDefaultAclRuleAppDbEntryWithoutAction() +{ + P4AclRuleAppDbEntry app_db_entry; + app_db_entry.acl_table_name = kAclIngressTableName; + app_db_entry.priority = 100; + // ACL rule match fields + app_db_entry.match_fvs["ether_type"] = "0x0800"; + app_db_entry.match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53"; + app_db_entry.match_fvs["ether_dst"] = "AA:BB:CC:DD:EE:FF"; + app_db_entry.match_fvs["ether_src"] = "AA:BB:CC:DD:EE:FF"; + app_db_entry.match_fvs["ipv6_next_header"] = "1"; + app_db_entry.match_fvs["src_ipv6_64bit"] = "fdf8:f53b:82e4::"; + app_db_entry.match_fvs["arp_tpa"] = "0xff112231"; + app_db_entry.match_fvs["udf2"] = "0x9876 & 0xAAAA"; + app_db_entry.db_key = "ACL_PUNT_TABLE:{\"match/ether_type\": \"0x0800\",\"match/ipv6_dst\": " + "\"fdf8:f53b:82e4::53\",\"match/ether_dst\": \"AA:BB:CC:DD:EE:FF\", " + "\"match/ether_src\": \"AA:BB:CC:DD:EE:FF\", \"match/ipv6_next_header\": " + "\"1\", \"match/src_ipv6_64bit\": " + "\"fdf8:f53b:82e4::\",\"match/arp_tpa\": \"0xff11223\",\"match/udf2\": " + "\"0x9876 & 0xAAAA\",\"priority\":100}"; + // ACL meter fields + app_db_entry.meter.enabled = true; + app_db_entry.meter.cir = 80; + app_db_entry.meter.cburst = 80; + app_db_entry.meter.pir = 200; + app_db_entry.meter.pburst = 200; + return app_db_entry; +} + +const std::string concatTableNameAndRuleKey(const std::string &table_name, const std::string &rule_key) +{ + return table_name + kTableKeyDelimiter + rule_key; +} + +} // namespace + +class AclManagerTest : public ::testing::Test +{ + protected: + AclManagerTest() + { + setUpMockApi(); + setUpCoppOrch(); + setUpSwitchOrch(); + setUpP4Orch(); + // const auto& acl_groups = gSwitchOrch->getAclGroupOidsBindingToSwitch(); + // EXPECT_EQ(3, acl_groups.size()); + // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_INGRESS)); + // EXPECT_EQ(kAclGroupIngressOid, acl_groups.at(SAI_ACL_STAGE_INGRESS)); + // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_EGRESS)); + // EXPECT_EQ(kAclGroupEgressOid, acl_groups.at(SAI_ACL_STAGE_EGRESS)); + // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_PRE_INGRESS)); + // EXPECT_EQ(kAclGroupLookupOid, acl_groups.at(SAI_ACL_STAGE_PRE_INGRESS)); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(gMirrorSession1), + kMirrorSessionOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(gMirrorSession2), + kMirrorSessionOid2); + } + + ~AclManagerTest() + { + cleanupAclManagerTest(); + } + + void cleanupAclManagerTest() + { + EXPECT_CALL(mock_sai_udf_, remove_udf_match(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + delete gP4Orch; + delete copp_orch_; + delete gSwitchOrch; + } + + void setUpMockApi() + { + mock_sai_acl = &mock_sai_acl_; + mock_sai_serialize = &mock_sai_serialize_; + mock_sai_policer = &mock_sai_policer_; + mock_sai_hostif = &mock_sai_hostif_; + mock_sai_switch = &mock_sai_switch_; + mock_sai_udf = &mock_sai_udf_; + sai_acl_api->create_acl_table = create_acl_table; + sai_acl_api->remove_acl_table = remove_acl_table; + sai_acl_api->create_acl_table_group = create_acl_table_group; + sai_acl_api->remove_acl_table_group = remove_acl_table_group; + sai_acl_api->create_acl_table_group_member = create_acl_table_group_member; + sai_acl_api->remove_acl_table_group_member = remove_acl_table_group_member; + sai_acl_api->get_acl_counter_attribute = get_acl_counter_attribute; + sai_acl_api->create_acl_entry = create_acl_entry; + sai_acl_api->remove_acl_entry = remove_acl_entry; + sai_acl_api->set_acl_entry_attribute = set_acl_entry_attribute; + sai_acl_api->create_acl_counter = create_acl_counter; + sai_acl_api->remove_acl_counter = remove_acl_counter; + sai_policer_api->create_policer = create_policer; + sai_policer_api->remove_policer = remove_policer; + sai_policer_api->get_policer_stats = get_policer_stats; + sai_policer_api->set_policer_attribute = set_policer_attribute; + sai_hostif_api->create_hostif_table_entry = mock_create_hostif_table_entry; + sai_hostif_api->remove_hostif_table_entry = mock_remove_hostif_table_entry; + sai_hostif_api->create_hostif_trap_group = mock_create_hostif_trap_group; + sai_hostif_api->create_hostif_trap = mock_create_hostif_trap; + sai_hostif_api->create_hostif = mock_create_hostif; + sai_hostif_api->remove_hostif = mock_remove_hostif; + sai_hostif_api->create_hostif_user_defined_trap = mock_create_hostif_user_defined_trap; + sai_hostif_api->remove_hostif_user_defined_trap = mock_remove_hostif_user_defined_trap; + sai_switch_api->get_switch_attribute = mock_get_switch_attribute; + sai_switch_api->set_switch_attribute = mock_set_switch_attribute; + sai_udf_api->remove_udf = remove_udf; + sai_udf_api->create_udf = create_udf; + sai_udf_api->remove_udf_group = remove_udf_group; + sai_udf_api->create_udf_group = create_udf_group; + sai_udf_api->remove_udf_match = remove_udf_match; + sai_udf_api->create_udf_match = create_udf_match; + } + + void setUpCoppOrch() + { + // init copp orch + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, get_switch_attribute(_, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + copp_orch_ = new CoppOrch(gAppDb, APP_COPP_TABLE_NAME); + // add trap group and genetlink for each CPU queue + swss::Table app_copp_table(gAppDb, APP_COPP_TABLE_NAME); + for (uint64_t queue_num = 1; queue_num <= P4_CPU_QUEUE_MAX_NUM; queue_num++) + { + std::vector attrs; + attrs.push_back({"queue", std::to_string(queue_num)}); + attrs.push_back({"genetlink_name", "genl_packet"}); + attrs.push_back({"genetlink_mcgrp_name", "packets"}); + app_copp_table.set(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(queue_num), attrs); + } + sai_object_id_t trap_group_oid = gTrapGroupStartOid; + sai_object_id_t hostif_oid = gHostifStartOid; + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap_group(_, _, _, _)) + .Times(P4_CPU_QUEUE_MAX_NUM) + .WillRepeatedly( + DoAll(Invoke([&trap_group_oid](sai_object_id_t *oid, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) { *oid = ++trap_group_oid; }), + Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, create_hostif(_, _, _, _)) + .Times(P4_CPU_QUEUE_MAX_NUM) + .WillRepeatedly( + DoAll(Invoke([&hostif_oid](sai_object_id_t *oid, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) { *oid = ++hostif_oid; }), + Return(SAI_STATUS_SUCCESS))); + + copp_orch_->addExistingData(&app_copp_table); + static_cast(copp_orch_)->doTask(); + } + + void setUpSwitchOrch() + { + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + TableConnector stateDbSwitchTable(gStateDb, "SWITCH_CAPABILITY"); + TableConnector app_switch_table(gAppDb, APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(gConfigDb, CFG_ASIC_SENSORS_TABLE_NAME); + std::vector switch_tables = {conf_asic_sensors, app_switch_table}; + gSwitchOrch = new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable); + } + + void setUpP4Orch() + { + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + EXPECT_CALL(mock_sai_acl_, + create_acl_table_group( + _, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiAttributeAclGroupStage, SAI_ACL_STAGE_INGRESS, std::placeholders::_1)))) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclGroupIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, + create_acl_table_group( + _, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiAttributeAclGroupStage, SAI_ACL_STAGE_EGRESS, std::placeholders::_1)))) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclGroupEgressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, + create_acl_table_group(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiAttributeAclGroupStage, SAI_ACL_STAGE_PRE_INGRESS, + std::placeholders::_1)))) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclGroupLookupOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_switch_, + set_switch_attribute(Eq(gSwitchId), + Truly(std::bind(MatchSaiSwitchAttrByAclStage, SAI_SWITCH_ATTR_INGRESS_ACL, + kAclGroupIngressOid, std::placeholders::_1)))) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, + set_switch_attribute(Eq(gSwitchId), + Truly(std::bind(MatchSaiSwitchAttrByAclStage, SAI_SWITCH_ATTR_EGRESS_ACL, + kAclGroupEgressOid, std::placeholders::_1)))) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, + set_switch_attribute(Eq(gSwitchId), + Truly(std::bind(MatchSaiSwitchAttrByAclStage, SAI_SWITCH_ATTR_PRE_INGRESS_ACL, + kAclGroupLookupOid, std::placeholders::_1)))) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + std::vector p4_tables; + gP4Orch = new P4Orch(gAppDb, p4_tables, gVrfOrch, copp_orch_); + acl_table_manager_ = gP4Orch->getAclTableManager(); + acl_rule_manager_ = gP4Orch->getAclRuleManager(); + p4_oid_mapper_ = acl_table_manager_->m_p4OidMapper; + } + + void AddDefaultUserTrapsSaiCalls(sai_object_id_t *user_defined_trap_oid) + { + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .Times(P4_CPU_QUEUE_MAX_NUM) + .WillRepeatedly(DoAll(Invoke([user_defined_trap_oid]( + sai_object_id_t *oid, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) { *oid = ++(*user_defined_trap_oid); }), + Return(SAI_STATUS_SUCCESS))); + } + + void AddDefaultIngressTable() + { + const auto &app_db_entry = getDefaultAclTableDefAppDbEntry(); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_entry)); + ASSERT_NO_FATAL_FAILURE( + IsExpectedAclTableDefinitionMapping(*GetAclTable(app_db_entry.acl_table_name), app_db_entry)); + } + + void DrainTableTuples() + { + acl_table_manager_->drain(); + } + void EnqueueTableTuple(const swss::KeyOpFieldsValuesTuple &entry) + { + acl_table_manager_->enqueue(entry); + } + + void DrainRuleTuples() + { + acl_rule_manager_->drain(); + } + void EnqueueRuleTuple(const swss::KeyOpFieldsValuesTuple &entry) + { + acl_rule_manager_->enqueue(entry); + } + + ReturnCodeOr DeserializeAclTableDefinitionAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return acl_table_manager_->deserializeAclTableDefinitionAppDbEntry(key, attributes); + } + + ReturnCodeOr DeserializeAclRuleAppDbEntry(const std::string &acl_table_name, + const std::string &key, + const std::vector &attributes) + { + return acl_rule_manager_->deserializeAclRuleAppDbEntry(acl_table_name, key, attributes); + } + + P4AclTableDefinition *GetAclTable(const std::string &acl_table_name) + { + return acl_table_manager_->getAclTable(acl_table_name); + } + + P4AclRule *GetAclRule(const std::string &acl_table_name, const std::string &acl_rule_key) + { + return acl_rule_manager_->getAclRule(acl_table_name, acl_rule_key); + } + + ReturnCode ProcessAddTableRequest(const P4AclTableDefinitionAppDbEntry &app_db_entry) + { + return acl_table_manager_->processAddTableRequest(app_db_entry); + } + + ReturnCode ProcessDeleteTableRequest(const std::string &acl_table_name) + { + return acl_table_manager_->processDeleteTableRequest(acl_table_name); + } + + ReturnCode ProcessAddRuleRequest(const std::string &acl_rule_key, const P4AclRuleAppDbEntry &app_db_entry) + { + return acl_rule_manager_->processAddRuleRequest(acl_rule_key, app_db_entry); + } + + ReturnCode ProcessUpdateRuleRequest(const P4AclRuleAppDbEntry &app_db_entry, const P4AclRule &old_acl_rule) + { + return acl_rule_manager_->processUpdateRuleRequest(app_db_entry, old_acl_rule); + } + + ReturnCode ProcessDeleteRuleRequest(const std::string &acl_table_name, const std::string &acl_rule_key) + { + return acl_rule_manager_->processDeleteRuleRequest(acl_table_name, acl_rule_key); + } + + void DoAclCounterStatsTask() + { + acl_rule_manager_->doAclCounterStatsTask(); + } + + StrictMock mock_sai_acl_; + StrictMock mock_sai_serialize_; + StrictMock mock_sai_policer_; + StrictMock mock_sai_hostif_; + StrictMock mock_sai_switch_; + StrictMock mock_sai_udf_; + CoppOrch *copp_orch_; + P4OidMapper *p4_oid_mapper_; + p4orch::AclTableManager *acl_table_manager_; + p4orch::AclRuleManager *acl_rule_manager_; +}; + +TEST_F(AclManagerTest, DrainTableTuplesToProcessSetDelRequestSucceeds) +{ + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + EnqueueTableTuple( + swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, getDefaultTableDefFieldValueTuples()})); + + // Drain table tuples to process SET request + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, Eq(gSwitchId), Gt(2), + Truly(std::bind(MatchSaiAttributeAclTableStage, SAI_ACL_STAGE_INGRESS, + std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, Eq(gSwitchId), Eq(3), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + DrainTableTuples(); + EXPECT_NE(nullptr, GetAclTable(kAclIngressTableName)); + + // Drain table tuples to process DEL request + EXPECT_CALL(mock_sai_acl_, remove_acl_table(Eq(kAclTableIngressOid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(Eq(kAclGroupMemberIngressOid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, DEL_COMMAND, {}})); + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, DrainTableTuplesToProcessUpdateRequestExpectFails) +{ + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + auto attributes = getDefaultTableDefFieldValueTuples(); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + + // Drain table tuples to process SET request, create a table with priority + // 234 + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, Eq(gSwitchId), Gt(2), + Truly(std::bind(MatchSaiAttributeAclTableStage, SAI_ACL_STAGE_INGRESS, + std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, Eq(gSwitchId), Eq(3), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + DrainTableTuples(); + EXPECT_NE(nullptr, GetAclTable(kAclIngressTableName)); + + // Drain table tuples to process SET request, try to update table priority + // to 100: should fail to update. + attributes.push_back(swss::FieldValueTuple{kPriority, "100"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + DrainTableTuples(); + EXPECT_EQ(234, GetAclTable(kAclIngressTableName)->priority); +} + +TEST_F(AclManagerTest, DrainTableTuplesWithInvalidTableNameOpsFails) +{ + auto p4rtAclTableName = std::string("UNDEFINED") + kTableKeyDelimiter + kAclIngressTableName; + EnqueueTableTuple( + swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, getDefaultTableDefFieldValueTuples()})); + // Drain table tuples to process SET request on invalid ACL definition table + // name: "UNDEFINED" + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + p4rtAclTableName = std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, "UPDATE", getDefaultTableDefFieldValueTuples()})); + // Drain table tuples to process invalid operation: "UPDATE" + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, DrainTableTuplesWithInvalidFieldFails) +{ + auto attributes = getDefaultTableDefFieldValueTuples(); + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + + // Invalid attribute field + attributes.push_back(swss::FieldValueTuple{"undefined", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + // Invalid attribute field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"undefined/undefined", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + // Invalid meter unit value + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"meter/unit", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + // Invalid counter unit value + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"counter/unit", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableSucceeds) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto acl_table = GetAclTable(kAclIngressTableName); + EXPECT_NE(nullptr, acl_table); +} + +TEST_F(AclManagerTest, CreatePuntTableFailsWhenUserTrapsSaiCallFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gUserDefinedTrapStartOid + 1), Return(SAI_STATUS_SUCCESS))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gHostifStartOid + 1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_table_entry(Eq(gHostifStartOid + 1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_user_defined_trap(Eq(gUserDefinedTrapStartOid + 1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + // The user defined traps fail to create + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + sai_object_id_t oid; + EXPECT_FALSE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(gUserDefinedTrapStartOid + 1), &oid)); + + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gUserDefinedTrapStartOid + 1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_user_defined_trap(Eq(gUserDefinedTrapStartOid + 1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + // The hostif table entry fails to create + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gUserDefinedTrapStartOid + 1), Return(SAI_STATUS_SUCCESS))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gHostifStartOid + 1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_table_entry(Eq(gHostifStartOid + 1))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // The 2nd user defined trap fails to create, the 1st hostif table entry fails + // to remove + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, DISABLED_CreatePuntTableFailsWhenUserTrapGroupOrHostifNotFound) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + const auto skip_cpu_queue = 1; + // init copp orch + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, get_switch_attribute(_, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + swss::Table app_copp_table(gAppDb, APP_COPP_TABLE_NAME); + // Clean up APP_COPP_TABLE_NAME table entries + for (int queue_num = 1; queue_num <= P4_CPU_QUEUE_MAX_NUM; queue_num++) + { + app_copp_table.del(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(queue_num)); + } + cleanupAclManagerTest(); + copp_orch_ = new CoppOrch(gAppDb, APP_COPP_TABLE_NAME); + setUpSwitchOrch(); + // Update p4orch to use new copp orch + setUpP4Orch(); + // Fail to create ACL table because the trap group is absent + EXPECT_EQ("Trap group was not found given trap group name: " + std::string(GENL_PACKET_TRAP_GROUP_NAME_PREFIX) + + std::to_string(skip_cpu_queue), + ProcessAddTableRequest(app_db_entry).message()); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Create the trap group for CPU queue 1 without host interface(genl + // attributes) + std::vector attrs; + attrs.push_back({"queue", std::to_string(skip_cpu_queue)}); + // Add one COPP_TABLE entry with trap group info, without hostif info + app_copp_table.set(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(skip_cpu_queue), attrs); + copp_orch_->addExistingData(&app_copp_table); + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gTrapGroupStartOid + skip_cpu_queue), Return(SAI_STATUS_SUCCESS))); + static_cast(copp_orch_)->doTask(); + // Fail to create ACL table because the host interface is absent + EXPECT_EQ("Hostif object id was not found given trap group - " + std::string(GENL_PACKET_TRAP_GROUP_NAME_PREFIX) + + std::to_string(skip_cpu_queue), + ProcessAddTableRequest(app_db_entry).message()); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenCapabilityExceeds) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)).WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateTableGroupMember) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenAclTableRecoveryFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfGroupRecoveryFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + // (TODO): Expect critical state x3. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfRecoveryFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // UDF recovery failure will also cause UDF group recovery failure since the + // reference count will not be zero if UDF failed to be removed. + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state x6. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateUdf) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + // Fail to create the first UDF, and success to remove the first UDF + // group + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); + EXPECT_FALSE( + p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); + + // Fail to create the second UDF group, and success to remove the first UDF + // group and UDF + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); + EXPECT_FALSE( + p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); + + // Fail to create the second UDF group, and fail to remove the first UDF + // group + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0"); + + // Fail to create the second UDF group, and fail to remove the first UDF and + // UDF group + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state x2. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidStageFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + // Invalid stage + app_db_entry.stage = "RANDOM"; + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidSaiMatchFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + // Invalid SAI match field + app_db_entry.match_field_lookup["random"] = BuildMatchFieldJsonStrKindSaiField("RANDOM"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + app_db_entry.match_field_lookup.erase("random"); + + // Invalid match field str - should be JSON str + app_db_entry.match_field_lookup["ether_type"] = P4_MATCH_ETHER_TYPE; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - should be object instead of array + app_db_entry.match_field_lookup["ether_type"] = "[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"HEX_STRING\",\"bitwidth\":8}]"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing kind + app_db_entry.match_field_lookup["ether_type"] = "{\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE\",\"format\":" + "\"HEX_STRING\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid kind + app_db_entry.match_field_lookup["ether_type"] = + "{\"kind\":\"field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_ETHER_" + "TYPE\",\"format\":\"HEX_STRING\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing format + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid format + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"INVALID_TYPE\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - not expected format for the field + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"IPV4\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing bitwidth + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"HEX_STRING\"}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing sai_field + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"format\":\"HEX_STRING\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Unsupported IP_TYPE bit type + app_db_entry.match_field_lookup.erase("ether_type"); + app_db_entry.match_field_lookup["is_non_ip"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + "NONIP"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidCompositeSaiMatchFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + + // Invalid match field str - missing format + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid format + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid total bitwidth + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":65," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.bitwidth + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\"},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.bitwidth + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":63," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":31},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element kind + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"bitwidth\":32},{\"kind\":\"sai_" + "field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\"," + "\"bitwidth\":32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid elements length + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid first element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid second element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidCompositeUdfMatchFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + + // Invalid match field str - missing format + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV4\",\"bitwidth\":16,\"offset\":24},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid format + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"IP\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV6\",\"bitwidth\":16,\"offset\":24},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid total bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":33," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV4\",\"bitwidth\":16,\"offset\":24},{\"kind\":" + "\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.kind + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":32," + "\"elements\":[{\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV4\",\"bitwidth\":15,\"offset\":24},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - inconsistent element.kind + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"offset\":" + "24},{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":31," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":15,\"offset\":24},{\"kind\":\"udf\",\"base\":\"SAI_UDF_" + "BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.base + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE\",\"bitwidth\":" + "16,\"offset\":24},{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.base + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"bitwidth\":16,\"offset\":24},{" + "\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.offset + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":16},{\"kind\":" + "\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - elements is empty + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element, should be an object + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[\"group-1\"]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\"," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":16,\"offset\":24},{\"kind\":" + "\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidActionFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + + // Invalid action field + app_db_entry.action_field_lookup["random_action"].push_back( + {.sai_action = "RANDOM_ACTION", .p4_param_name = "DUMMY"}); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidPacketColorFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + + // Invalid packet_color field + app_db_entry.packet_action_color_lookup["invalid_packet_color"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = "DUMMY"}); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid packet_action field + app_db_entry.packet_action_color_lookup.erase("invalid_packet_color"); + app_db_entry.packet_action_color_lookup["invalid_packet_action"].push_back( + {.packet_action = "PUNT", .packet_color = P4_PACKET_COLOR_GREEN}); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, DeserializeValidAclTableDefAppDbSucceeds) +{ + auto app_db_entry_or = + DeserializeAclTableDefinitionAppDbEntry(kAclIngressTableName, getDefaultTableDefFieldValueTuples()); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(kAclIngressTableName, app_db_entry.acl_table_name); + EXPECT_EQ(123, app_db_entry.size); + EXPECT_EQ(STAGE_INGRESS, app_db_entry.stage); + EXPECT_EQ(234, app_db_entry.priority); + EXPECT_EQ(P4_METER_UNIT_BYTES, app_db_entry.meter_unit); + EXPECT_EQ(P4_COUNTER_UNIT_BOTH, app_db_entry.counter_unit); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE), + app_db_entry.match_field_lookup.find("ether_type")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC, P4_FORMAT_MAC), + app_db_entry.match_field_lookup.find("ether_dst")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6, P4_FORMAT_IPV6), + app_db_entry.match_field_lookup.find("ipv6_dst")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER), + app_db_entry.match_field_lookup.find("ipv6_next_header")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL), app_db_entry.match_field_lookup.find("ttl")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE), + app_db_entry.match_field_lookup.find("icmp_type")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT), + app_db_entry.match_field_lookup.find("l4_dst_port")->second); + EXPECT_EQ(P4_ACTION_SET_TRAFFIC_CLASS, + app_db_entry.action_field_lookup.find("copy_and_set_tc")->second[0].sai_action); + EXPECT_EQ("traffic_class", app_db_entry.action_field_lookup.find("copy_and_set_tc")->second[0].p4_param_name); + EXPECT_EQ(P4_ACTION_SET_TRAFFIC_CLASS, + app_db_entry.action_field_lookup.find("punt_and_set_tc")->second[0].sai_action); + EXPECT_EQ("traffic_class", app_db_entry.action_field_lookup.find("punt_and_set_tc")->second[0].p4_param_name); + EXPECT_EQ(P4_PACKET_ACTION_COPY, + app_db_entry.packet_action_color_lookup.find("copy_and_set_tc")->second[0].packet_action); + EXPECT_EQ(P4_PACKET_COLOR_GREEN, + app_db_entry.packet_action_color_lookup.find("copy_and_set_tc")->second[0].packet_color); + EXPECT_EQ(P4_PACKET_ACTION_PUNT, + app_db_entry.packet_action_color_lookup.find("punt_and_set_tc")->second[0].packet_action); + EXPECT_EQ(EMPTY_STRING, app_db_entry.packet_action_color_lookup.find("punt_and_set_tc")->second[0].packet_color); +} + +TEST_F(AclManagerTest, DeserializeAclTableDefAppDbWithInvalidJsonFails) +{ + std::string acl_table_name = kAclIngressTableName; + auto attributes = getDefaultTableDefFieldValueTuples(); + + // Invalid action JSON + attributes.push_back(swss::FieldValueTuple{"action/drop_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\";\"packet_color\":\"SAI_PACKET_" + "COLOR_GREEN\"};{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\";" + "\"param\":\"traffic_class\"}]"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); + + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"action/drop_and_set_tc", + "[{\"action\"-\"SAI_PACKET_ACTION_COPY\",\"packet_color\"-\"SAI_PACKET_" + "COLOR_GREEN\"},{\"action\"-\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\"-\"traffic_class\"}]"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); + + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"action/drop_and_set_tc", "[\"action\":\"SAI_PACKET_ACTION_COPY\"]"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclTableDefAppDbWithInvalidSizeFails) +{ + std::string acl_table_name = kAclIngressTableName; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kStage, STAGE_INGRESS}); + attributes.push_back(swss::FieldValueTuple{kPriority, "234"}); + attributes.push_back(swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}); + attributes.push_back(swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_next_header", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER)}); + attributes.push_back(swss::FieldValueTuple{"match/ttl", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL)}); + attributes.push_back( + swss::FieldValueTuple{"match/icmp_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/l4_dst_port", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT)}); + attributes.push_back(swss::FieldValueTuple{"action/copy_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_PACKET_" + "COLOR_GREEN\"},{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\":\"traffic_class\"}]"}); + attributes.push_back(swss::FieldValueTuple{"action/punt_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_TRAP\"},{\"action\":\"SAI_ACL_ENTRY_" + "ATTR_ACTION_SET_TC\",\"param\":\"traffic_class\"}]"}); + + // Invalid table size + attributes.push_back(swss::FieldValueTuple{kSize, "-123"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclTableDefAppDbWithInvalidPriorityFails) +{ + std::string acl_table_name = kAclIngressTableName; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kStage, STAGE_INGRESS}); + attributes.push_back(swss::FieldValueTuple{kSize, "234"}); + attributes.push_back(swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}); + attributes.push_back(swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_next_header", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER)}); + attributes.push_back(swss::FieldValueTuple{"match/ttl", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL)}); + attributes.push_back( + swss::FieldValueTuple{"match/icmp_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/l4_dst_port", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT)}); + attributes.push_back(swss::FieldValueTuple{"action/copy_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_PACKET_" + "COLOR_GREEN\"},{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\":\"traffic_class\"}]"}); + attributes.push_back(swss::FieldValueTuple{"action/punt_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_TRAP\"},{\"action\":\"SAI_ACL_ENTRY_" + "ATTR_ACTION_SET_TC\",\"param\":\"traffic_class\"}]"}); + + // Invalid table priority + attributes.push_back(swss::FieldValueTuple{kPriority, "-123"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); +} + +TEST_F(AclManagerTest, RemoveIngressPuntTableSucceeds) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) +{ + // Create ACL table + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to remove ACL rule when sai_acl_api->remove_acl_entry() fails + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + // Fails to remove ACL rule when rule does not exist + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, kAclIngressRuleOid1); + + // Fails to remove ACL rule when reference count > 0 + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + + // Fails to remove ACL rule when sai_policer_api->remove_policer() fails + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when sai_policer_api->remove_policer() fails and + // recovery fails + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_FAILURE))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when the counter does not exist. + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key, kAclCounterOid1, 1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, kAclMeterOid1); + + // Fails to remove ACL rule when sai_acl_api->remove_acl_counter() fails + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when sai_acl_api->remove_acl_counter() fails and + // ACL rule recovery fails. + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_FAILURE))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when sai_acl_api->remove_acl_counter() fails and + // meter recovery fails + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_FAILURE))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when the meter does not exist. + // The previous test fails to recover the ACL meter, and hence the meter does + // not exist. + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, kAclMeterOid1); +} + +TEST_F(AclManagerTest, RemoveNonExistingPuntTableFails) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenAclRuleExists) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to remove ACL table when the table is nonempty + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenTableDoesNotExist) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenTableRefCountIsNotZero) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableRaisesCriticalStateWhenAclGroupMemberRecoveryFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenAclTableGroupMemberDoesNotExist) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, kAclIngressTableName); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveAclTableGroupMemberSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveUdfSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveUdfGroupSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsRaisesCriticalStateWhenUdfRecoveryFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsRaisesCriticalStateWhenUdfGroupRecoveryFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // If UDF group recovery fails, UDF recovery and ACL table recovery will also + // fail since they depend on the UDF group. + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state x3. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenUdfHasNonZeroRefCount) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24"); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenUdfGroupHasNonZeroRefCount) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0"); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclGroupsSucceedsAfterCleanup) +{ + // Create ACL table + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key1 = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key1, app_db_entry)); + // Insert the second ACL rule + app_db_entry.match_fvs["tc"] = "1"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + auto acl_rule_key2 = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key2, app_db_entry)); + // There are 3 groups created, only group in INGRESS stage is nonempty. + // Other groups can be deleted in below RemoveAllGroups() + EXPECT_CALL(mock_sai_switch_, set_switch_attribute(Eq(gSwitchId), _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + + // Remove ACL groups + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid2))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid2))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + // Remove rules + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key1)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key2)); + // Remove table + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetRequestSucceeds) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + app_db_entry.counter_unit = P4_COUNTER_UNIT_PACKETS; + app_db_entry.meter_unit = P4_METER_UNIT_PACKETS; + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_entry)); + ASSERT_NO_FATAL_FAILURE( + IsExpectedAclTableDefinitionMapping(*GetAclTable(app_db_entry.acl_table_name), app_db_entry)); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + const auto &rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, getDefaultRuleFieldValueTuples()})); + + // Update request on exact rule without change will not need SAI call + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, getDefaultRuleFieldValueTuples()})); + + // Drain rule tuples to process SET request + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + DrainRuleTuples(); + + const auto &acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + const auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + EXPECT_EQ(kAclIngressRuleOid1, acl_rule->acl_entry_oid); +} + +TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetDelRequestSucceeds) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + const auto &rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + + // Drain ACL rule tuple to process SET request + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + DrainRuleTuples(); + // Populate counter stats + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) { + counters[0] = 100; // green_bytes + }), + Return(SAI_STATUS_SUCCESS))); + DoAclCounterStatsTask(); + auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); + std::vector values; + EXPECT_TRUE(counters_table->get(rule_tuple_key, values)); + + const auto &acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + const auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + EXPECT_EQ(kAclIngressRuleOid1, acl_rule->acl_entry_oid); + EXPECT_EQ(rule_tuple_key, acl_rule->db_key); + + // Drain ACL rule tuple to process DEL request + attributes.clear(); + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, DEL_COMMAND, attributes})); + + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + DrainRuleTuples(); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetRequestInvalidTableNameRuleKeyFails) +{ + auto attributes = getDefaultRuleFieldValueTuples(); + auto acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + auto rule_tuple_key = std::string("INVALID_TABLE_NAME") + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + // Drain rule tuple to process SET request with invalid ACL table name: + // "INVALID_TABLE_NAME" + DrainRuleTuples(); + + auto acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + EXPECT_EQ(nullptr, GetAclRule("INVALID_TABLE_NAME", acl_rule_key)); + + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\"}"; + rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53"; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + // Drain rule tuple to process SET request without priority field in rule + // JSON key + DrainRuleTuples(); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidPriorityFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + + // ACL rule json key has invalid priority + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":-15}"; + + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(kAclIngressTableName, acl_rule_json_key, attributes).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidMeterFieldFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + std::string acl_table_name = kAclIngressTableName; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kAction, "copy_and_set_tc"}); + attributes.push_back(swss::FieldValueTuple{"param/traffic_class", "0x20"}); + attributes.push_back(swss::FieldValueTuple{"meter/cburst", "80"}); + attributes.push_back(swss::FieldValueTuple{"meter/pir", "200"}); + attributes.push_back(swss::FieldValueTuple{"meter/pburst", "200"}); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + + // ACL rule has invalid cir value in meter field + attributes.push_back(swss::FieldValueTuple{"meter/cir", "-80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); + + // ACL rule has invalid meter field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"meter/undefined", "80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); + + // ACL rule has invalid field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"undefined/undefined", "80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); + + // ACL rule has invalid meter field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"undefined", "80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); +} + +TEST_F(AclManagerTest, DrainRuleTuplesWithInvalidCommand) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + const auto &rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, "INVALID_COMMAND", attributes})); + DrainRuleTuples(); + const auto &acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidMatchFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + // ACL rule json key has invalid match field + auto acl_rule_json_key = "{\"undefined/undefined\":\"0x0800\",\"priority\":15}"; + EXPECT_FALSE( + DeserializeAclRuleAppDbEntry(kAclIngressTableName, acl_rule_json_key, getDefaultRuleFieldValueTuples()).ok()); + // ACL rule json key is missing match prefix + acl_rule_json_key = "{\"ipv6_dst\":\"0x0800\",\"priority\":15}"; + EXPECT_FALSE( + DeserializeAclRuleAppDbEntry(kAclIngressTableName, acl_rule_json_key, getDefaultRuleFieldValueTuples()).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidJsonFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + // ACL rule json key is an invalid JSON + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(kAclIngressTableName, "{\"undefined\"}", attributes).ok()); + EXPECT_FALSE( + DeserializeAclRuleAppDbEntry(kAclIngressTableName, "[{\"ipv6_dst\":\"0x0800\",\"priority\":15}]", attributes) + .ok()); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidSaiMatchFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // ACL rule has invalid in/out port(s) + app_db_entry.match_fvs["in_port"] = "Eth0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("in_port"); + app_db_entry.match_fvs["out_port"] = "Eth0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("out_port"); + app_db_entry.match_fvs["in_ports"] = "Eth0,Eth1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["in_ports"] = ""; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("in_ports"); + app_db_entry.match_fvs["out_ports"] = ""; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["out_ports"] = "Eth0,Eth1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("out_ports"); + + // ACL rule has invalid ipv6_dst + app_db_entry.match_fvs["ipv6_dst"] = "10.0.0.2"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "10.0.0.2 & 255.255.255.0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53 & 255.255.255.0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "null"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53"; + + // ACL rule has invalid ip_src + app_db_entry.match_fvs["ip_src"] = "fdf8:f53b:82e4::53"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "fdf8:f53b:82e4::53 & ffff:ffff:ffff::"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "10.0.0.2 & ffff:ffff:ffff::"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "null"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "10.0.0.2"; + + // ACL rule has invalid ether_type + app_db_entry.match_fvs["ether_type"] = "0x88800"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ether_type"] = "0x0800"; + + // ACL rule has invalid ip_frag + app_db_entry.match_fvs["ip_frag"] = "invalid"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("ip_frag"); + + // ACL rule has invalid packet_vlan + app_db_entry.match_fvs["packet_vlan"] = "invalid"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("packet_vlan"); + + // ACL rule has invalid UDF field: should be HEX_STRING + app_db_entry.match_fvs["arp_tpa"] = "invalid"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // ACL rule has invalid UDF field: invalid HEX_STRING length + app_db_entry.match_fvs["arp_tpa"] = "0xff"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // ACL table misses UDF group definition + const auto &acl_table_acl_tableapp_db_entry = getDefaultAclTableDefAppDbEntry(); + auto *acl_table = GetAclTable(acl_table_acl_tableapp_db_entry.acl_table_name); + std::map saved_udf_group_attr_index_lookup = acl_table->udf_group_attr_index_lookup; + acl_table->udf_group_attr_index_lookup.clear(); + app_db_entry.match_fvs["arp_tpa"] = "0xff112231"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("arp_tpa"); + acl_table->udf_group_attr_index_lookup = saved_udf_group_attr_index_lookup; + + // ACL rule has undefined match field + app_db_entry.match_fvs["undefined"] = "1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("undefined"); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidCompositeSaiMatchFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // ACL rule has invalid src_ipv6_64bit(composite SAI field) - should be ipv6 + // address + app_db_entry.match_fvs["src_ipv6_64bit"] = "Eth0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["src_ipv6_64bit"] = "10.0.0.1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["src_ipv6_64bit"] = "10.0.0.1 & ffff:ffff::"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["src_ipv6_64bit"] = "fdf8:f53b:82e4:: & 255.255.255.255"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, AclRuleWithValidMatchFields) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + + // Match fields registered in table definition + app_db_entry.match_fvs["ether_dst"] = "AA:BB:CC:DD:EE:FF & FF:FF:FF:FF:FF:FF"; + app_db_entry.match_fvs["ttl"] = "0x2 & 0xFF"; + app_db_entry.match_fvs["in_ports"] = "Ethernet1,Ethernet2"; + app_db_entry.match_fvs["in_port"] = "Ethernet3"; + app_db_entry.match_fvs["out_ports"] = "Ethernet4,Ethernet5"; + app_db_entry.match_fvs["out_port"] = "Ethernet6"; + app_db_entry.match_fvs["tcp_flags"] = " 0x2 & 0x3F "; + app_db_entry.match_fvs["ip_flags"] = "0x2"; + app_db_entry.match_fvs["l4_src_port"] = "0x2e90 & 0xFFF0"; + app_db_entry.match_fvs["l4_dst_port"] = "0x2e98"; + app_db_entry.match_fvs["ip_id"] = "2"; + app_db_entry.match_fvs["inner_l4_src_port"] = "1212"; + app_db_entry.match_fvs["inner_l4_dst_port"] = "1212"; + app_db_entry.match_fvs["dscp"] = "8"; + app_db_entry.match_fvs["inner_ip_src"] = "192.50.128.0"; + app_db_entry.match_fvs["inner_ip_dst"] = "192.50.128.0/17"; + app_db_entry.match_fvs["inner_ipv6_src"] = "1234:5678::"; + app_db_entry.match_fvs["inner_ipv6_dst"] = "2001:db8:3c4d:15::/64"; + app_db_entry.match_fvs["ip_src"] = " 192.50.128.0 & 255.255.255.0 "; + app_db_entry.match_fvs["ip_dst"] = "192.50.128.0/17"; + app_db_entry.match_fvs["ipv6_src"] = "2001:db8:3c4d:15::/64"; + app_db_entry.match_fvs["tc"] = "1"; + app_db_entry.match_fvs["icmp_type"] = "9"; // RA + app_db_entry.match_fvs["icmp_code"] = "0"; // Normal RA + app_db_entry.match_fvs["tos"] = "32"; + app_db_entry.match_fvs["icmpv6_type"] = "134"; // RA + app_db_entry.match_fvs["icmpv6_code"] = "0"; // Normal RA + app_db_entry.match_fvs["ecn"] = "0"; + app_db_entry.match_fvs["inner_ip_protocol"] = "0x01"; // ICMP + app_db_entry.match_fvs["ip_protocol"] = "0x6"; // TCP + app_db_entry.match_fvs["ipv6_flow_label"] = "0x88 & 0xFFFFFFFF"; + app_db_entry.match_fvs["tunnel_vni"] = "88"; + app_db_entry.match_fvs["ip_frag"] = P4_IP_FRAG_HEAD; + app_db_entry.match_fvs["packet_vlan"] = P4_PACKET_VLAN_SINGLE_OUTER_TAG; + app_db_entry.match_fvs["outer_vlan_pri"] = "100"; + app_db_entry.match_fvs["outer_vlan_id"] = "100"; + app_db_entry.match_fvs["outer_vlan_cfi"] = "100"; + app_db_entry.match_fvs["inner_vlan_pri"] = "200"; + app_db_entry.match_fvs["inner_vlan_id"] = "200"; + app_db_entry.match_fvs["inner_vlan_cfi"] = "200"; + + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + auto *acl_table = GetAclTable(kAclIngressTableName); + ASSERT_NE(nullptr, acl_rule); + ASSERT_NE(nullptr, acl_table); + EXPECT_NO_FATAL_FAILURE(IsExpectedAclRuleMapping(acl_rule, app_db_entry, *acl_table)); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + + // Check match field value + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].aclfield.data.objlist.count); + EXPECT_EQ(0x112233, acl_rule->in_ports_oids[0]); + EXPECT_EQ(0x1fed3, acl_rule->in_ports_oids[1]); + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS].aclfield.data.objlist.count); + EXPECT_EQ(0x9988, acl_rule->out_ports_oids[0]); + EXPECT_EQ(0x56789abcdef, acl_rule->out_ports_oids[1]); + EXPECT_EQ(0xaabbccdd, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT].aclfield.data.oid); + EXPECT_EQ(0x56789abcdff, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT].aclfield.data.oid); + EXPECT_EQ(0x2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS].aclfield.data.u8); + EXPECT_EQ(0x3F, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS].aclfield.mask.u8); + EXPECT_EQ(0x2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS].aclfield.data.u8); + EXPECT_EQ(0x3F, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS].aclfield.mask.u8); + EXPECT_EQ(8, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DSCP].aclfield.data.u8); + EXPECT_EQ(0x3F, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DSCP].aclfield.mask.u8); + EXPECT_EQ(0x0800, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE].aclfield.data.u16); + EXPECT_EQ(0xFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE].aclfield.mask.u16); + EXPECT_EQ(0x2e90, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT].aclfield.data.u16); + EXPECT_EQ(0xFFF0, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT].aclfield.mask.u16); + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION].aclfield.data.u16); + EXPECT_EQ(0xFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION].aclfield.mask.u16); + EXPECT_EQ(100, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID].aclfield.data.u16); + EXPECT_EQ(0xFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID].aclfield.mask.u16); + // 192.50.128.0 + EXPECT_EQ(swss::IpPrefix("192.50.128.0").getIp().getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP].aclfield.data.ip4); + EXPECT_EQ(swss::IpPrefix("192.50.128.0").getMask().getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP].aclfield.mask.ip4); + // 192.50.128.0 & 255.255.255.0 + EXPECT_EQ(swss::IpAddress("192.50.128.0").getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP].aclfield.data.ip4); + EXPECT_EQ(swss::IpAddress("255.255.255.0").getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP].aclfield.mask.ip4); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6].aclfield.data.ip6, + swss::IpPrefix("2001:db8:3c4d:15::/64").getIp().getV6Addr(), 16)); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6].aclfield.mask.ip6, + swss::IpPrefix("2001:db8:3c4d:15::/64").getMask().getV6Addr(), 16)); + + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC].aclfield.data.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC].aclfield.data.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC].aclfield.mask.mac, + swss::MacAddress("FF:FF:FF:FF:FF:FF").getMac(), 6)); + EXPECT_EQ(1, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TC].aclfield.data.u8); + EXPECT_EQ(0xFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TC].aclfield.mask.u8); + EXPECT_EQ(0x88, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL].aclfield.data.u32); + EXPECT_EQ(0xFFFFFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL].aclfield.mask.u32); + EXPECT_EQ(SAI_ACL_IP_FRAG_HEAD, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG].aclfield.data.u32); + EXPECT_EQ(SAI_PACKET_VLAN_SINGLE_OUTER_TAG, + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN].aclfield.data.u32); + + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_EQ(0x20, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); +} + +TEST_F(AclManagerTest, AclRuleWithValidAction) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // Redirect action + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = "Ethernet1"; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(/*port_oid=*/0x112233, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Redirect action + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = "Ethernet7"; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(/*port_oid=*/0x1234, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Set up an next hop mapping + const std::string next_hop_id = "ju1u32m1.atl11:qe-3/7"; + const auto &next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, + /*next_hop_oid=*/1); + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(/*next_hop_oid=*/1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Set endpoint Ip action + app_db_entry.action = "endpoint_ip"; + app_db_entry.action_param_fvs["ip_address"] = "127.0.0.1"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(swss::IpAddress("127.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip4); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + // Install rule + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Mirror ingress action + app_db_entry.action = "mirror_ingress"; + app_db_entry.action_param_fvs["target"] = gMirrorSession1; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].oid); + EXPECT_EQ(gMirrorSession1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].name); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Mirror egress action + app_db_entry.action = "mirror_egress"; + app_db_entry.action_param_fvs["target"] = gMirrorSession2; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_EQ(acl_rule->action_fvs.end(), acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS)); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS].oid); + EXPECT_EQ(gMirrorSession2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS].name); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set Packet Color + app_db_entry.action = "set_packet_color"; + app_db_entry.action_param_fvs["packet_color"] = "SAI_PACKET_COLOR_YELLOW"; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_COLOR_YELLOW, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR].aclaction.parameter.s32); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set Src Mac + app_db_entry.action = "set_src_mac"; + app_db_entry.action_param_fvs["mac_address"] = "AA:BB:CC:DD:EE:FF"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC].aclaction.parameter.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set src IP + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.1"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(swss::IpAddress("10.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP].aclaction.parameter.ip4); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set IPv6 Dst + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set DSCP and ECN + app_db_entry.action = "set_dscp_and_ecn"; + app_db_entry.action_param_fvs["dscp"] = "8"; + app_db_entry.action_param_fvs["ecn"] = "0"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(8, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP].aclaction.parameter.u8); + EXPECT_EQ(0, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN].aclaction.parameter.u8); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set Inner VLAN + app_db_entry.action = "set_inner_vlan"; + app_db_entry.action_param_fvs["vlan_pri"] = "100"; + app_db_entry.action_param_fvs["vlan_id"] = "100"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID].aclaction.parameter.u32); + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI].aclaction.parameter.u8); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set L4Src Port + app_db_entry.action = "set_l4_src_port"; + app_db_entry.action_param_fvs["port"] = "1212"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1212, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT].aclaction.parameter.u16); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Flood action + app_db_entry.action = "flood"; + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_FLOOD].aclaction.enable); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set user defined trap for QOS_QUEUE + int queue_num = 2; + app_db_entry.action = "qos_queue"; + app_db_entry.action_param_fvs["cpu_queue"] = std::to_string(queue_num); + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(gUserDefinedTrapStartOid + queue_num, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, AclRuleWithVrfAction) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // Set vrf + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = gVrfName; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(gVrfOid, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, AclRuleWithIpTypeBitEncoding) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + // Set src IP + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.1"; + + // Successful cases + // Wildcard match on IP_TYPE: SAI_ACL_IP_TYPE_ANY + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_NE(acl_rule->match_fvs.end(), acl_rule->match_fvs.find(SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE)); + EXPECT_EQ(SAI_ACL_IP_TYPE_ANY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ip { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_IP + app_db_entry.match_fvs["is_ip"] = "0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_IP, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ip { value: 0x0 mask: 0x1 } = SAI_ACL_IP_TYPE_NON_IP + app_db_entry.match_fvs["is_ip"] = "0x0 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_NON_IP, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv4 { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_IPV4ANY + app_db_entry.match_fvs.erase("is_ip"); + app_db_entry.match_fvs["is_ipv4"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_IPV4ANY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv4 { value: 0x0 mask: 0x1 } = SAI_ACL_IP_TYPE_NON_IPV4 + app_db_entry.match_fvs["is_ipv4"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_NON_IPV4, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv6 { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_IPV6ANY + app_db_entry.match_fvs.erase("is_ipv4"); + app_db_entry.match_fvs["is_ipv6"] = "0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_IPV6ANY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv6 { value: 0x0 mask: 0x1 } = SAI_ACL_IP_TYPE_NON_IPV6 + app_db_entry.match_fvs["is_ipv6"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_NON_IPV6, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_arp { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_ARP + app_db_entry.match_fvs.erase("is_ipv6"); + app_db_entry.match_fvs["is_arp"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_ARP, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_arp_request { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_ARP_REQUEST + app_db_entry.match_fvs.erase("is_arp"); + app_db_entry.match_fvs["is_arp_request"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_ARP_REQUEST, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_arp_reply { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_ARP_REPLY + app_db_entry.match_fvs.erase("is_arp_request"); + app_db_entry.match_fvs["is_arp_reply"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_ARP_REPLY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Failed cases + // is_arp_reply { value: 0x0 mask: 0x1 } = N/A + app_db_entry.match_fvs["is_arp_reply"] = "0x0 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); + + // is_arp_request { value: 0x0 mask: 0x1 } = N/A + app_db_entry.match_fvs.erase("is_arp_reply"); + app_db_entry.match_fvs["is_arp_request"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); + + // is_arp { value: 0x0 mask: 0x1 } = N/A + app_db_entry.match_fvs.erase("is_arp_request"); + app_db_entry.match_fvs["is_arp"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); + + // is_ip { value: 0x1 mask: 0x0 } = N/A + app_db_entry.match_fvs.erase("is_arp"); + app_db_entry.match_fvs["is_ip"] = "0x1 & 0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); +} + +TEST_F(AclManagerTest, UpdateAclRuleWithActionMeterChange) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + sai_object_id_t meter_oid; + uint32_t ref_cnt; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update action parameter value + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + app_db_entry.meter.cburst = 500; + app_db_entry.meter.cir = 500; + app_db_entry.meter.pburst = 600; + app_db_entry.meter.pir = 600; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(5) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(acl_rule->action_fvs.end(), acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION)); + EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_FALSE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_NE(acl_rule->meter.packet_color_actions.find(SAI_POLICER_ATTR_GREEN_PACKET_ACTION), + acl_rule->meter.packet_color_actions.end()); + EXPECT_EQ(SAI_PACKET_ACTION_COPY, acl_rule->meter.packet_color_actions[SAI_POLICER_ATTR_GREEN_PACKET_ACTION]); + EXPECT_EQ(500, acl_rule->meter.cburst); + EXPECT_EQ(500, acl_rule->meter.cir); + EXPECT_EQ(600, acl_rule->meter.pburst); + EXPECT_EQ(600, acl_rule->meter.pir); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update ACL rule : disable rate limiting, packet action is still existing. + app_db_entry.meter.enabled = false; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(4) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_FALSE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_NE(acl_rule->meter.packet_color_actions.find(SAI_POLICER_ATTR_GREEN_PACKET_ACTION), + acl_rule->meter.packet_color_actions.end()); + EXPECT_EQ(SAI_PACKET_ACTION_COPY, acl_rule->meter.packet_color_actions[SAI_POLICER_ATTR_GREEN_PACKET_ACTION]); + EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_FALSE(acl_rule->meter.enabled); + EXPECT_EQ(0, acl_rule->meter.cburst); + EXPECT_EQ(0, acl_rule->meter.cir); + EXPECT_EQ(0, acl_rule->meter.pburst); + EXPECT_EQ(0, acl_rule->meter.pir); + + // Update meter: enable rate limiting and reset green packet action + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.meter.enabled = true; + // Update meter and rule: reset color packet action and update entry + // attribute + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(5) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_EQ(500, acl_rule->meter.cburst); + EXPECT_EQ(500, acl_rule->meter.cir); + EXPECT_EQ(600, acl_rule->meter.pburst); + EXPECT_EQ(600, acl_rule->meter.pir); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update ACL rule : disable meter + app_db_entry.meter.enabled = false; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_FALSE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(0, acl_rule->meter.cburst); + EXPECT_EQ(0, acl_rule->meter.cir); + EXPECT_EQ(0, acl_rule->meter.pburst); + EXPECT_EQ(0, acl_rule->meter.pir); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); + + // Update ACL rule : enable meter + app_db_entry.meter.enabled = true; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(2, acl_rule->action_fvs.size()); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid2, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_TRUE(acl_rule->meter.enabled); + + // Redirect action + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = "Ethernet1"; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ( + /*port_oid=*/0x112233, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + + // Set up an next hop mapping + const std::string next_hop_id = "ju1u32m1.atl11:qe-3/7"; + const auto &next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, + /*next_hop_oid=*/1); + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(/*next_hop_oid=*/1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_EQ(next_hop_key, acl_rule->action_redirect_nexthop_key); + + // Set endpoint Ip action + app_db_entry.action = "endpoint_ip"; + app_db_entry.action_param_fvs["ip_address"] = "127.0.0.1"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(swss::IpAddress("127.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip4); + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(1) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + + // Set src Mac + app_db_entry.action = "set_src_mac"; + app_db_entry.action_param_fvs["mac_address"] = "AA:BB:CC:DD:EE:FF"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC].aclaction.parameter.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + app_db_entry.action_param_fvs["mac_address"] = "11:22:33:44:55:66"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC].aclaction.parameter.mac, + swss::MacAddress("11:22:33:44:55:66").getMac(), 6)); + + // Set Inner VLAN + app_db_entry.action = "set_inner_vlan"; + app_db_entry.action_param_fvs["vlan_pri"] = "100"; + app_db_entry.action_param_fvs["vlan_id"] = "100"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(2, acl_rule->action_fvs.size()); + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID].aclaction.parameter.u32); + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI].aclaction.parameter.u8); + app_db_entry.action_param_fvs["vlan_pri"] = "150"; + app_db_entry.action_param_fvs["vlan_id"] = "150"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(2, acl_rule->action_fvs.size()); + EXPECT_EQ(150, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID].aclaction.parameter.u32); + EXPECT_EQ(150, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI].aclaction.parameter.u8); + + // Set src IP + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.1"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(swss::IpAddress("10.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP].aclaction.parameter.ip4); + app_db_entry.action_param_fvs["ip_address"] = "10.10.10.1"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(1) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(swss::IpAddress("10.10.10.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP].aclaction.parameter.ip4); + + // Set IPv6 Dst + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b::53"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(1) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b::53").getV6Addr(), 16)); + + // Mirror ingress action + app_db_entry.action = "mirror_ingress"; + app_db_entry.action_param_fvs["target"] = gMirrorSession1; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].oid); + EXPECT_EQ(gMirrorSession1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].name); + + app_db_entry.action_param_fvs["target"] = gMirrorSession2; + + // Update rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].oid); + EXPECT_EQ(gMirrorSession2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].name); + + // Flood action + app_db_entry.action = "flood"; + + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_FLOOD].aclaction.enable); + + // QOS_QUEUE action to set user defined trap for CPU queue number 3 + int queue_num = 3; + app_db_entry.action = "qos_queue"; + app_db_entry.action_param_fvs["cpu_queue"] = std::to_string(queue_num); + + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.enable); + EXPECT_EQ(gUserDefinedTrapStartOid + queue_num, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.parameter.oid); + + // QOS_QUEUE action to set user defined trap CPU queue number 4 + queue_num = 4; + app_db_entry.action_param_fvs["cpu_queue"] = std::to_string(queue_num); + + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.enable); + EXPECT_EQ(gUserDefinedTrapStartOid + queue_num, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.parameter.oid); +} + +TEST_F(AclManagerTest, UpdateAclRuleWithVrfActionChange) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Set VRF + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = gVrfName; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(gVrfOid, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF].aclaction.parameter.oid); + app_db_entry.action_param_fvs["vrf"] = ""; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(gVirtualRouterId, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF].aclaction.parameter.oid); +} + +TEST_F(AclManagerTest, UpdateAclRuleFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + sai_object_id_t meter_oid; + uint32_t ref_cnt; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Update packet action: Copy green packet. Fails when update meter + // attribute fails + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action and rate limiting. Fails when update meter attribute + // fails plue meter attribute recovery fails. + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + app_db_entry.meter.cburst = 500; + app_db_entry.meter.cir = 500; + app_db_entry.meter.pburst = 600; + app_db_entry.meter.pir = 600; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet. Fails when action param is + // missing + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs.erase("traffic_class"); + app_db_entry.meter.cburst = 80; + app_db_entry.meter.cir = 80; + app_db_entry.meter.pburst = 200; + app_db_entry.meter.pir = 200; + // Update meter attribute for green packet action + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet. Fails when updating ACL rule + // fails + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet. Fails when updating ACL rule + // fails and meter recovery fails + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Remove meter in ACL rule: fails when deleting meter fails plus ACL rule + // recovery fails. + app_db_entry.meter.enabled = false; + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Remove meter + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_TRUE(acl_rule->meter.enabled); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Successfully remove meter + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key)); + + // Add meter in ACL rule with packet color action + app_db_entry.meter.enabled = true; + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid2))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action and meter + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); + + // Add meter in ACL rule with packet color action. Fails when updating ACL + // rule fails and meter recovery fails + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid2))).WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action and meter + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // ACL rule has redirect action with invalid next_hop_id + const std::string next_hop_id = "ju1u32m1.atl11:qe-3/7"; + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("target"); + // ACL rule has redirect action with wrong port type + app_db_entry.action_param_fvs["target"] = "Ethernet8"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("target"); + // ACL rule has invalid action + app_db_entry.action = "set_tc"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // ACL rule has invalid IP address + app_db_entry.action = "endpoint_ip"; + app_db_entry.action_param_fvs["ip_address"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("ip_address"); + // ACL rule is missing action parameter + app_db_entry.action = "set_src_ip"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // ACL rule with invalid action parameter field "ipv4", should be "ip" + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ipv4"] = "10.0.0.1"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("ipv4"); + // ACL rule has invalid MAC address + app_db_entry.action = "set_src_mac"; + app_db_entry.action_param_fvs["mac_address"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("mac_address"); + // ACL rule with invalid packet action value + app_db_entry.action = "set_packet_action"; + app_db_entry.action_param_fvs["packet_action"] = "PUNT"; // Invalid packet action str + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("packet_action"); + // ACL rule with invalid packet color value + app_db_entry.action = "set_packet_color"; + app_db_entry.action_param_fvs["packet_color"] = "YELLOW"; // Invalid color str + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("packet_color"); + // Invalid Mirror ingress session + app_db_entry.action = "mirror_ingress"; + app_db_entry.action_param_fvs["target"] = "Session"; + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("target"); + // Invalid cpu queue number + app_db_entry.action = "qos_queue"; + app_db_entry.action_param_fvs["cpu_queue"] = "10"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs["cpu_queue"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("cpu_queue"); + // ACL rule has invalid dscp + app_db_entry.action = "set_dscp_and_ecn"; + app_db_entry.action_param_fvs["dscp"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("dscp"); + // ACL rule has invalid vlan id + app_db_entry.action = "set_inner_vlan"; + app_db_entry.action_param_fvs["vlan_id"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("vlan_id"); + // ACL rule has invalid port number + app_db_entry.action = "set_l4_src_port"; + app_db_entry.action_param_fvs["port"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("port"); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidVrfActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // ACL rule with invalid VRF name + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = "Vrf-yellow"; + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidUnitsInTableFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto *acl_table = GetAclTable(kAclIngressTableName); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // Invalid meter unit + acl_table->meter_unit = "INVALID"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_table->meter_unit = P4_METER_UNIT_BYTES; + + // Invalid counter unit + acl_table->counter_unit = "INVALID"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_table->counter_unit = P4_COUNTER_UNIT_BYTES; +} + +TEST_F(AclManagerTest, CreateAclRuleFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // Set up an next hop mapping + const std::string next_hop_id = "ju1u32m1.atl11:qe-1/7"; + const auto &next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, + /*next_hop_oid=*/1); + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + + // Fails to create ACL rule when sai_acl_api->create_acl_entry() fails + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + uint32_t ref_count; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_count)); + EXPECT_EQ(0, ref_count); + + // Set VRF action + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = gVrfName; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // Fails to create ACL rule when sai_acl_api->create_acl_entry() fails + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_acl_entry() fails plus + // meter and counter recovery fails + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state x2. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_policer() fails + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_acl_counter() fails + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_CALL(mock_sai_policer_, remove_policer(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_acl_counter() fails and + // meter recovery fails + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_CALL(mock_sai_policer_, remove_policer(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidSetSrcIpActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + app_db_entry.action_param_fvs["ip_address"] = "null"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidSetDstIpv6ActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.2"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + app_db_entry.action_param_fvs["ip_address"] = "null"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, DeleteAclRuleWhenTableDoesNotExistFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) +{ + auto app_db_def_entry = getDefaultAclTableDefAppDbEntry(); + app_db_def_entry.counter_unit = P4_COUNTER_UNIT_BOTH; + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_def_entry)); + auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); + + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + const auto &counter_stats_key = app_db_entry.db_key; + std::vector values; + std::string stats; + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_acl_, get_acl_counter_attribute(Eq(kAclCounterOid1), _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *counter_attr) { + counter_attr[0].value.u64 = 50; // packets + counter_attr[1].value.u64 = 500; // bytes + }), + Return(SAI_STATUS_SUCCESS))); + DoAclCounterStatsTask(); + // Only packets and bytes are populated in COUNTERS_DB + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); + EXPECT_EQ("50", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + EXPECT_EQ("500", stats); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_BYTES, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_BYTES, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_BYTES, stats)); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(Eq(kAclCounterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + + // Install rule with packet color GREEN + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) { + counters[0] = 10; // green_packets + counters[1] = 100; // green_bytes + }), + Return(SAI_STATUS_SUCCESS))); + DoAclCounterStatsTask(); + // Only green_packets and green_bytes are populated in COUNTERS_DB + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_PACKETS, stats)); + EXPECT_EQ("10", stats); + + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_BYTES, stats)); + EXPECT_EQ("100", stats); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_BYTES, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_BYTES, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + + // Install rule with packet color YELLOW and RED + app_db_entry.action = "punt_non_green_pk"; + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) { + counters[0] = 20; // yellow_packets + counters[1] = 200; // yellow_bytes + counters[2] = 30; // red_packets + counters[3] = 300; // red_bytes + }), + Return(SAI_STATUS_SUCCESS))); + DoAclCounterStatsTask(); + // Only yellow/red_packets and yellow/red_bytes are populated in COUNTERS_DB + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_PACKETS, stats)); + EXPECT_EQ("20", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_BYTES, stats)); + EXPECT_EQ("200", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_PACKETS, stats)); + EXPECT_EQ("30", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_BYTES, stats)); + EXPECT_EQ("300", stats); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_BYTES, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); +} + +TEST_F(AclManagerTest, DoAclCounterStatsTaskFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); + + // Insert the ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + const auto &counter_stats_key = app_db_entry.db_key; + std::vector values; + std::string stats; + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_acl_, get_acl_counter_attribute(Eq(kAclCounterOid1), _, _)) + .WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + DoAclCounterStatsTask(); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(Eq(kAclCounterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Install rule with packet color GREEN + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // Fails when get_policer_stats() is not implemented + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + DoAclCounterStatsTask(); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DISABLED_InitCreateGroupFails) +{ + // Failed to create ACL groups + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group(_, Eq(gSwitchId), Eq(3), _)) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + TableConnector stateDbSwitchTable(gStateDb, "SWITCH_CAPABILITY"); + TableConnector app_switch_table(gAppDb, APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(gConfigDb, CFG_ASIC_SENSORS_TABLE_NAME); + std::vector switch_tables = {conf_asic_sensors, app_switch_table}; + EXPECT_THROW(new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable), std::runtime_error); +} + +TEST_F(AclManagerTest, DISABLED_InitBindGroupToSwitchFails) +{ + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + // Failed to bind ACL group to switch attribute. + EXPECT_CALL(mock_sai_acl_, create_acl_table_group(_, Eq(gSwitchId), Eq(3), _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_switch_, set_switch_attribute(Eq(gSwitchId), _)).WillOnce(Return(SAI_STATUS_FAILURE)); + TableConnector stateDbSwitchTable(gStateDb, "SWITCH_CAPABILITY"); + TableConnector app_switch_table(gAppDb, APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(gConfigDb, CFG_ASIC_SENSORS_TABLE_NAME); + std::vector switch_tables = {conf_asic_sensors, app_switch_table}; + EXPECT_THROW(new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable), std::runtime_error); +} + +} // namespace test +} // namespace p4orch diff --git a/orchagent/p4orch/tests/fake_consumerstatetable.cpp b/orchagent/p4orch/tests/fake_consumerstatetable.cpp new file mode 100644 index 0000000000..045950abfd --- /dev/null +++ b/orchagent/p4orch/tests/fake_consumerstatetable.cpp @@ -0,0 +1,11 @@ +#include "consumerstatetable.h" + +namespace swss +{ + +ConsumerStateTable::ConsumerStateTable(DBConnector *db, const std::string &tableName, int popBatchSize, int pri) + : ConsumerTableBase(db, tableName, popBatchSize, pri), TableName_KeySet(tableName) +{ +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_crmorch.cpp b/orchagent/p4orch/tests/fake_crmorch.cpp new file mode 100644 index 0000000000..03f15c28ac --- /dev/null +++ b/orchagent/p4orch/tests/fake_crmorch.cpp @@ -0,0 +1,67 @@ +#include +#include + +#include "crmorch.h" + +CrmOrch::CrmOrch(swss::DBConnector *db, std::string tableName) : Orch(db, std::vector{}) +{ +} + +void CrmOrch::incCrmResUsedCounter(CrmResourceType resource) +{ +} + +void CrmOrch::decCrmResUsedCounter(CrmResourceType resource) +{ +} + +void CrmOrch::incCrmAclUsedCounter(CrmResourceType resource, sai_acl_stage_t stage, sai_acl_bind_point_type_t point) +{ +} + +void CrmOrch::decCrmAclUsedCounter(CrmResourceType resource, sai_acl_stage_t stage, sai_acl_bind_point_type_t point, + sai_object_id_t oid) +{ +} + +void CrmOrch::incCrmAclTableUsedCounter(CrmResourceType resource, sai_object_id_t tableId) +{ +} + +void CrmOrch::decCrmAclTableUsedCounter(CrmResourceType resource, sai_object_id_t tableId) +{ +} + +void CrmOrch::doTask(Consumer &consumer) +{ +} + +void CrmOrch::handleSetCommand(const std::string &key, const std::vector &data) +{ +} + +void CrmOrch::doTask(swss::SelectableTimer &timer) +{ +} + +void CrmOrch::getResAvailableCounters() +{ +} + +void CrmOrch::updateCrmCountersTable() +{ +} + +void CrmOrch::checkCrmThresholds() +{ +} + +std::string CrmOrch::getCrmAclKey(sai_acl_stage_t stage, sai_acl_bind_point_type_t bindPoint) +{ + return ""; +} + +std::string CrmOrch::getCrmAclTableKey(sai_object_id_t id) +{ + return ""; +} diff --git a/orchagent/p4orch/tests/fake_dbconnector.cpp b/orchagent/p4orch/tests/fake_dbconnector.cpp new file mode 100644 index 0000000000..1709d9d977 --- /dev/null +++ b/orchagent/p4orch/tests/fake_dbconnector.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "dbconnector.h" + +namespace swss +{ + +static std::map dbNameIdMap = { + {"APPL_DB", 0}, {"ASIC_DB", 1}, {"COUNTERS_DB", 2}, {"CONFIG_DB", 4}, {"FLEX_COUNTER_DB", 5}, {"STATE_DB", 6}, +}; + +RedisContext::RedisContext() +{ +} + +RedisContext::~RedisContext() +{ +} + +DBConnector::DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout) : m_dbId(dbId) +{ +} + +DBConnector::DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn) +{ + if (dbNameIdMap.find(dbName) != dbNameIdMap.end()) + { + m_dbId = dbNameIdMap[dbName]; + } + else + { + m_dbId = -1; + } +} + +DBConnector::DBConnector(int dbId, const std::string &unixPath, unsigned int timeout) : m_dbId(dbId) +{ +} + +int DBConnector::getDbId() const +{ + return m_dbId; +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_notificationconsumer.cpp b/orchagent/p4orch/tests/fake_notificationconsumer.cpp new file mode 100644 index 0000000000..b903b336ab --- /dev/null +++ b/orchagent/p4orch/tests/fake_notificationconsumer.cpp @@ -0,0 +1,13 @@ +#include "notificationconsumer.h" + +namespace swss +{ + +NotificationConsumer::NotificationConsumer(swss::DBConnector *db, const std::string &channel, int pri, + size_t popBatchSize) + : Selectable(pri), POP_BATCH_SIZE(popBatchSize), m_db(db), m_subscribe(NULL), m_channel(channel) +{ + SWSS_LOG_ENTER(); +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_portorch.cpp b/orchagent/p4orch/tests/fake_portorch.cpp new file mode 100644 index 0000000000..aaf766e1aa --- /dev/null +++ b/orchagent/p4orch/tests/fake_portorch.cpp @@ -0,0 +1,691 @@ +extern "C" +{ +#include "sai.h" +} + +#include +#include + +#include "portsorch.h" + +#define PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 1000 +#define PORT_BUFFER_DROP_STAT_POLLING_INTERVAL_MS 60000 +#define QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 10000 + +PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vector &tableNames, + DBConnector *chassisAppDb) + : Orch(db, tableNames), m_portStateTable(stateDb, STATE_PORT_TABLE_NAME), + port_stat_manager(PORT_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, + PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true), + port_buffer_drop_stat_manager(PORT_BUFFER_DROP_STAT_FLEX_COUNTER_GROUP, StatsMode::READ, + PORT_BUFFER_DROP_STAT_POLLING_INTERVAL_MS, true), + queue_stat_manager(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, + QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true) +{ +} + +bool PortsOrch::allPortsReady() +{ + return true; +} + +bool PortsOrch::isInitDone() +{ + return true; +} + +bool PortsOrch::isConfigDone() +{ + return true; +} + +bool PortsOrch::isPortAdminUp(const string &alias) +{ + return true; +} + +std::map &PortsOrch::getAllPorts() +{ + return m_portList; +} + +bool PortsOrch::bake() +{ + return true; +} + +void PortsOrch::cleanPortTable(const vector &keys) +{ +} + +bool PortsOrch::getBridgePort(sai_object_id_t id, Port &port) +{ + return true; +} + +bool PortsOrch::setBridgePortLearningFDB(Port &port, sai_bridge_port_fdb_learning_mode_t mode) +{ + return true; +} + +bool PortsOrch::getPort(string alias, Port &port) +{ + if (m_portList.find(alias) == m_portList.end()) + { + return false; + } + port = m_portList[alias]; + return true; +} + +bool PortsOrch::getPort(sai_object_id_t id, Port &port) +{ + for (const auto &p : m_portList) + { + if (p.second.m_port_id == id) + { + port = p.second; + return true; + } + } + return false; +} + +void PortsOrch::increasePortRefCount(const string &alias) +{ +} + +void PortsOrch::decreasePortRefCount(const string &alias) +{ +} + +bool PortsOrch::getPortByBridgePortId(sai_object_id_t bridge_port_id, Port &port) +{ + return true; +} + +void PortsOrch::setPort(string alias, Port port) +{ + m_portList[alias] = port; +} + +void PortsOrch::getCpuPort(Port &port) +{ +} + +bool PortsOrch::getInbandPort(Port &port) +{ + return true; +} + +bool PortsOrch::getVlanByVlanId(sai_vlan_id_t vlan_id, Port &vlan) +{ + return true; +} + +bool PortsOrch::setHostIntfsOperStatus(const Port &port, bool up) const +{ + return true; +} + +void PortsOrch::updateDbPortOperStatus(const Port &port, sai_port_oper_status_t status) const +{ +} + +bool PortsOrch::createVlanHostIntf(Port &vl, string hostif_name) +{ + return true; +} + +bool PortsOrch::removeVlanHostIntf(Port vl) +{ + return true; +} + +bool PortsOrch::createBindAclTableGroup(sai_object_id_t port_oid, sai_object_id_t acl_table_oid, + sai_object_id_t &group_oid, acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::unbindRemoveAclTableGroup(sai_object_id_t port_oid, sai_object_id_t acl_table_oid, + acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::bindAclTable(sai_object_id_t id, sai_object_id_t table_oid, sai_object_id_t &group_member_oid, + acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::unbindAclTable(sai_object_id_t port_oid, sai_object_id_t acl_table_oid, + sai_object_id_t acl_group_member_oid, acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::bindUnbindAclTableGroup(Port &port, bool ingress, bool bind) +{ + return true; +} + +bool PortsOrch::getPortPfc(sai_object_id_t portId, uint8_t *pfc_bitmask) +{ + return true; +} + +bool PortsOrch::setPortPfc(sai_object_id_t portId, uint8_t pfc_bitmask) +{ + return true; +} + +void PortsOrch::generateQueueMap() +{ +} + +void PortsOrch::generatePriorityGroupMap() +{ +} + +void PortsOrch::generatePortCounterMap() +{ +} + +void PortsOrch::generatePortBufferDropCounterMap() +{ +} + +void PortsOrch::refreshPortStatus() +{ +} + +bool PortsOrch::removeAclTableGroup(const Port &p) +{ + return true; +} + +bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, const uint32_t &mtu) +{ + return true; +} + +bool PortsOrch::removeSubPort(const string &alias) +{ + return true; +} + +bool PortsOrch::updateL3VniStatus(uint16_t vlan_id, bool status) +{ + return true; +} + +void PortsOrch::getLagMember(Port &lag, vector &portv) +{ +} + +void PortsOrch::updateChildPortsMtu(const Port &p, const uint32_t mtu) +{ +} + +bool PortsOrch::addTunnel(string tunnel, sai_object_id_t, bool learning) +{ + return true; +} + +bool PortsOrch::removeTunnel(Port tunnel) +{ + return true; +} + +bool PortsOrch::addBridgePort(Port &port) +{ + return true; +} + +bool PortsOrch::removeBridgePort(Port &port) +{ + return true; +} + +bool PortsOrch::addVlanMember(Port &vlan, Port &port, string &tagging_mode, string end_point_ip) +{ + return true; +} + +bool PortsOrch::removeVlanMember(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +bool PortsOrch::isVlanMember(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +bool PortsOrch::addVlanFloodGroups(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +bool PortsOrch::removeVlanEndPointIp(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +void PortsOrch::increaseBridgePortRefCount(Port &port) +{ +} + +void PortsOrch::decreaseBridgePortRefCount(Port &port) +{ +} + +bool PortsOrch::getBridgePortReferenceCount(Port &port) +{ + return true; +} + +bool PortsOrch::isInbandPort(const string &alias) +{ + return true; +} + +bool PortsOrch::setVoqInbandIntf(string &alias, string &type) +{ + return true; +} + +bool PortsOrch::getRecircPort(Port &p, string role) +{ + return true; +} + +const gearbox_phy_t *PortsOrch::getGearboxPhy(const Port &port) +{ + return nullptr; +} + +bool PortsOrch::getPortIPG(sai_object_id_t port_id, uint32_t &ipg) +{ + return true; +} + +bool PortsOrch::setPortIPG(sai_object_id_t port_id, uint32_t ipg) +{ + return true; +} + +bool PortsOrch::getPortOperStatus(const Port &port, sai_port_oper_status_t &status) const +{ + status = port.m_oper_status; + return true; +} + +std::string PortsOrch::getQueueWatermarkFlexCounterTableKey(std::string s) +{ + return ""; +} + +std::string PortsOrch::getPriorityGroupWatermarkFlexCounterTableKey(std::string s) +{ + return ""; +} + +std::string PortsOrch::getPriorityGroupDropPacketsFlexCounterTableKey(std::string s) +{ + return ""; +} + +std::string PortsOrch::getPortRateFlexCounterTableKey(std::string s) +{ + return ""; +} + +void PortsOrch::doTask() +{ +} + +void PortsOrch::doTask(Consumer &consumer) +{ +} + +void PortsOrch::doPortTask(Consumer &consumer) +{ +} + +void PortsOrch::doVlanTask(Consumer &consumer) +{ +} + +void PortsOrch::doVlanMemberTask(Consumer &consumer) +{ +} + +void PortsOrch::doLagTask(Consumer &consumer) +{ +} + +void PortsOrch::doLagMemberTask(Consumer &consumer) +{ +} + +void PortsOrch::doTask(NotificationConsumer &consumer) +{ +} + +void PortsOrch::removePortFromLanesMap(string alias) +{ +} + +void PortsOrch::removePortFromPortListMap(sai_object_id_t port_id) +{ +} + +void PortsOrch::removeDefaultVlanMembers() +{ +} + +void PortsOrch::removeDefaultBridgePorts() +{ +} + +bool PortsOrch::initializePort(Port &port) +{ + return true; +} + +void PortsOrch::initializePriorityGroups(Port &port) +{ +} + +void PortsOrch::initializePortMaximumHeadroom(Port &port) +{ +} + +void PortsOrch::initializeQueues(Port &port) +{ +} + +bool PortsOrch::addHostIntfs(Port &port, string alias, sai_object_id_t &host_intfs_id) +{ + return true; +} + +bool PortsOrch::setHostIntfsStripTag(Port &port, sai_hostif_vlan_tag_t strip) +{ + return true; +} + +bool PortsOrch::setBridgePortLearnMode(Port &port, string learn_mode) +{ + return true; +} + +bool PortsOrch::addVlan(string vlan) +{ + return true; +} + +bool PortsOrch::removeVlan(Port vlan) +{ + return true; +} + +bool PortsOrch::addLag(string lag, uint32_t spa_id, int32_t switch_id) +{ + return true; +} + +bool PortsOrch::removeLag(Port lag) +{ + return true; +} + +bool PortsOrch::setLagTpid(sai_object_id_t id, sai_uint16_t tpid) +{ + return true; +} + +bool PortsOrch::addLagMember(Port &lag, Port &port, bool enableForwarding) +{ + return true; +} + +bool PortsOrch::removeLagMember(Port &lag, Port &port) +{ + return true; +} + +bool PortsOrch::setCollectionOnLagMember(Port &lagMember, bool enableCollection) +{ + return true; +} + +bool PortsOrch::setDistributionOnLagMember(Port &lagMember, bool enableDistribution) +{ + return true; +} + +bool PortsOrch::addPort(const set &lane_set, uint32_t speed, int an, string fec) +{ + return true; +} + +sai_status_t PortsOrch::removePort(sai_object_id_t port_id) +{ + return SAI_STATUS_SUCCESS; +} + +bool PortsOrch::initPort(const string &alias, const string &role, const int index, const set &lane_set) +{ + return true; +} + +void PortsOrch::deInitPort(string alias, sai_object_id_t port_id) +{ +} + +bool PortsOrch::setPortAdminStatus(Port &port, bool up) +{ + return true; +} + +bool PortsOrch::getPortAdminStatus(sai_object_id_t id, bool &up) +{ + return true; +} + +bool PortsOrch::setPortMtu(sai_object_id_t id, sai_uint32_t mtu) +{ + return true; +} + +bool PortsOrch::setPortTpid(sai_object_id_t id, sai_uint16_t tpid) +{ + return true; +} + +bool PortsOrch::setPortPvid(Port &port, sai_uint32_t pvid) +{ + return true; +} + +bool PortsOrch::getPortPvid(Port &port, sai_uint32_t &pvid) +{ + return true; +} + +bool PortsOrch::setPortFec(Port &port, sai_port_fec_mode_t mode) +{ + return true; +} + +bool PortsOrch::setPortPfcAsym(Port &port, string pfc_asym) +{ + return true; +} + +bool PortsOrch::getDestPortId(sai_object_id_t src_port_id, dest_port_type_t port_type, sai_object_id_t &des_port_id) +{ + return true; +} + +bool PortsOrch::setBridgePortAdminStatus(sai_object_id_t id, bool up) +{ + return true; +} + +bool PortsOrch::isSpeedSupported(const std::string &alias, sai_object_id_t port_id, sai_uint32_t speed) +{ + return true; +} + +void PortsOrch::getPortSupportedSpeeds(const std::string &alias, sai_object_id_t port_id, + PortSupportedSpeeds &supported_speeds) +{ +} + +void PortsOrch::initPortSupportedSpeeds(const std::string &alias, sai_object_id_t port_id) +{ +} + +task_process_status PortsOrch::setPortSpeed(Port &port, sai_uint32_t speed) +{ + return task_success; +} + +bool PortsOrch::getPortSpeed(sai_object_id_t port_id, sai_uint32_t &speed) +{ + return true; +} + +bool PortsOrch::setGearboxPortsAttr(Port &port, sai_port_attr_t id, void *value) +{ + return true; +} + +bool PortsOrch::setGearboxPortAttr(Port &port, dest_port_type_t port_type, sai_port_attr_t id, void *value) +{ + return true; +} + +task_process_status PortsOrch::setPortAdvSpeeds(sai_object_id_t port_id, std::vector &speed_list) +{ + return task_success; +} + +bool PortsOrch::getQueueTypeAndIndex(sai_object_id_t queue_id, string &type, uint8_t &index) +{ + return true; +} + +void PortsOrch::generateQueueMapPerPort(const Port &port) +{ +} + +void PortsOrch::generatePriorityGroupMapPerPort(const Port &port) +{ +} + +task_process_status PortsOrch::setPortAutoNeg(sai_object_id_t id, int an) +{ + return task_success; +} + +bool PortsOrch::setPortFecMode(sai_object_id_t id, int fec) +{ + return true; +} + +task_process_status PortsOrch::setPortInterfaceType(sai_object_id_t id, sai_port_interface_type_t interface_type) +{ + return task_success; +} + +task_process_status PortsOrch::setPortAdvInterfaceTypes(sai_object_id_t id, std::vector &interface_types) +{ + return task_success; +} + +void PortsOrch::updatePortOperStatus(Port &port, sai_port_oper_status_t status) +{ +} + +bool PortsOrch::getPortOperSpeed(const Port &port, sai_uint32_t &speed) const +{ + return true; +} + +void PortsOrch::updateDbPortOperSpeed(Port &port, sai_uint32_t speed) +{ +} + +void PortsOrch::getPortSerdesVal(const std::string &s, std::vector &lane_values) +{ +} + +bool PortsOrch::getPortAdvSpeedsVal(const std::string &s, std::vector &speed_values) +{ + return true; +} + +bool PortsOrch::getPortInterfaceTypeVal(const std::string &s, sai_port_interface_type_t &interface_type) +{ + return true; +} + +bool PortsOrch::getPortAdvInterfaceTypesVal(const std::string &s, std::vector &type_values) +{ + return true; +} + +void PortsOrch::removePortSerdesAttribute(sai_object_id_t port_id) +{ +} + +bool PortsOrch::getSaiAclBindPointType(Port::Type type, sai_acl_bind_point_type_t &sai_acl_bind_type) +{ + return true; +} + +void PortsOrch::initGearbox() +{ +} + +bool PortsOrch::initGearboxPort(Port &port) +{ + return true; +} + +bool PortsOrch::getSystemPorts() +{ + return true; +} + +bool PortsOrch::addSystemPorts() +{ + return true; +} + +void PortsOrch::voqSyncAddLag(Port &lag) +{ +} + +void PortsOrch::voqSyncDelLag(Port &lag) +{ +} + +void PortsOrch::voqSyncAddLagMember(Port &lag, Port &port) +{ +} + +void PortsOrch::voqSyncDelLagMember(Port &lag, Port &port) +{ +} + +std::unordered_set PortsOrch::generateCounterStats(const string &type) +{ + return {}; +} \ No newline at end of file diff --git a/orchagent/p4orch/tests/fake_producertable.cpp b/orchagent/p4orch/tests/fake_producertable.cpp new file mode 100644 index 0000000000..703be025eb --- /dev/null +++ b/orchagent/p4orch/tests/fake_producertable.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include "producertable.h" + +namespace swss +{ + +ProducerTable::ProducerTable(DBConnector *db, const std::string &tableName) + : TableBase(tableName, ":"), TableName_KeyValueOpQueues(tableName) +{ +} + +ProducerTable::~ProducerTable() +{ +} + +void ProducerTable::set(const std::string &key, const std::vector &values, const std::string &op, + const std::string &prefix) +{ +} + +void ProducerTable::del(const std::string &key, const std::string &op, const std::string &prefix) +{ +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_subscriberstatetable.cpp b/orchagent/p4orch/tests/fake_subscriberstatetable.cpp new file mode 100644 index 0000000000..729fbcefae --- /dev/null +++ b/orchagent/p4orch/tests/fake_subscriberstatetable.cpp @@ -0,0 +1,11 @@ +#include "subscriberstatetable.h" + +namespace swss +{ + +SubscriberStateTable::SubscriberStateTable(DBConnector *db, const std::string &tableName, int popBatchSize, int pri) + : ConsumerTableBase(db, tableName, popBatchSize, pri), m_table(db, tableName) +{ +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_table.cpp b/orchagent/p4orch/tests/fake_table.cpp new file mode 100644 index 0000000000..e1f785f048 --- /dev/null +++ b/orchagent/p4orch/tests/fake_table.cpp @@ -0,0 +1,97 @@ +#include + +#include "table.h" + +namespace swss +{ + +using TableDataT = std::map>; +using TablesT = std::map; + +namespace fake_db +{ + +TablesT gTables; + +} // namespace fake_db + +using namespace fake_db; + +Table::Table(const DBConnector *db, const std::string &tableName) : TableBase(tableName, ":") +{ +} + +Table::~Table() +{ +} + +void Table::hset(const std::string &key, const std::string &field, const std::string &value, const std::string & /*op*/, + const std::string & /*prefix*/) +{ + gTables[getTableName()][key][field] = value; +} + +void Table::set(const std::string &key, const std::vector &values, const std::string & /*op*/, + const std::string & /*prefix*/) +{ + auto &fvs = gTables[getTableName()][key]; + for (const auto &fv : values) + { + fvs[fv.first] = fv.second; + } +} + +bool Table::hget(const std::string &key, const std::string &field, std::string &value) +{ + const auto &table_data = gTables[getTableName()]; + const auto &key_it = table_data.find(key); + if (key_it == table_data.end()) + { + return false; + } + const auto &field_it = key_it->second.find(field); + if (field_it == key_it->second.end()) + { + return false; + } + value = field_it->second; + return true; +} + +bool Table::get(const std::string &key, std::vector &ovalues) +{ + const auto &table_data = gTables[getTableName()]; + if (table_data.find(key) == table_data.end()) + { + return false; + } + + for (const auto &fv : table_data.at(key)) + { + ovalues.push_back({fv.first, fv.second}); + } + return true; +} + +void Table::del(const std::string &key, const std::string & /*op*/, const std::string & /*prefix*/) +{ + gTables[getTableName()].erase(key); +} + +void Table::hdel(const std::string &key, const std::string &field, const std::string & /*op*/, + const std::string & /*prefix*/) +{ + gTables[getTableName()][key].erase(field); +} + +void Table::getKeys(std::vector &keys) +{ + keys.clear(); + auto table = gTables[getTableName()]; + for (const auto &it : table) + { + keys.push_back(it.first); + } +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/mirror_session_manager_test.cpp b/orchagent/p4orch/tests/mirror_session_manager_test.cpp new file mode 100644 index 0000000000..c45a0d9bcd --- /dev/null +++ b/orchagent/p4orch/tests/mirror_session_manager_test.cpp @@ -0,0 +1,1011 @@ +#include "p4orch/mirror_session_manager.h" + +#include +#include + +#include +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_mirror.h" +#include "p4oidmapper.h" +#include "p4orch_util.h" +#include "portsorch.h" +#include "swss/ipaddress.h" +#include "swss/macaddress.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_mirror_api_t *sai_mirror_api; +extern sai_object_id_t gSwitchId; +extern PortsOrch *gPortsOrch; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +namespace p4orch +{ +namespace test +{ +namespace +{ + +constexpr char *kMirrorSessionId = "mirror_session1"; +constexpr sai_object_id_t kMirrorSessionOid = 0x445566; +// A physical port set up in test_main.cpp +constexpr char *kPort1 = "Ethernet1"; +constexpr sai_object_id_t kPort1Oid = 0x112233; +// A management port set up in test_main.cpp +constexpr char *kPort2 = "Ethernet8"; +// A physical port set up in test_main.cpp +constexpr char *kPort3 = "Ethernet3"; +constexpr sai_object_id_t kPort3Oid = 0xaabbccdd; +constexpr char *kSrcIp1 = "10.206.196.31"; +constexpr char *kSrcIp2 = "10.206.196.32"; +constexpr char *kDstIp1 = "172.20.0.203"; +constexpr char *kDstIp2 = "172.20.0.204"; +constexpr char *kSrcMac1 = "00:02:03:04:05:06"; +constexpr char *kSrcMac2 = "00:02:03:04:05:07"; +constexpr char *kDstMac1 = "00:1a:11:17:5f:80"; +constexpr char *kDstMac2 = "00:1a:11:17:5f:81"; +constexpr char *kTtl1 = "0x40"; +constexpr char *kTtl2 = "0x41"; +constexpr uint8_t kTtl1Num = 0x40; +constexpr uint8_t kTtl2Num = 0x41; +constexpr char *kTos1 = "0x00"; +constexpr char *kTos2 = "0x01"; +constexpr uint8_t kTos1Num = 0x00; +constexpr uint8_t kTos2Num = 0x01; + +// Generates attribute list for create_mirror_session(). +std::vector GenerateAttrListForCreate(sai_object_id_t port_oid, uint8_t ttl, uint8_t tos, + const swss::IpAddress &src_ip, const swss::IpAddress &dst_ip, + const swss::MacAddress &src_mac, const swss::MacAddress &dst_mac) +{ + std::vector attrs; + sai_attribute_t attr; + + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = port_oid; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TYPE; + attr.value.s32 = SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE; + attr.value.s32 = SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION; + attr.value.u8 = MIRROR_SESSION_DEFAULT_IP_HDR_VER; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = tos; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = ttl; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, src_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, dst_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, src_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, dst_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE; + attr.value.u16 = GRE_PROTOCOL_ERSPAN; + attrs.push_back(attr); + + return attrs; +} + +// Matcher for attribute list in SAI mirror call. +// Returns true if attribute lists have the same values in the same order. +bool MatchSaiCallAttrList(const sai_attribute_t *attr_list, const std::vector &expected_attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + + for (uint i = 0; i < expected_attr_list.size(); ++i) + { + switch (attr_list[i].id) + { + case SAI_MIRROR_SESSION_ATTR_MONITOR_PORT: + if (attr_list[i].value.oid != expected_attr_list[i].value.oid) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_TYPE: + case SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE: + if (attr_list[i].value.s32 != expected_attr_list[i].value.s32) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION: + case SAI_MIRROR_SESSION_ATTR_TOS: + case SAI_MIRROR_SESSION_ATTR_TTL: + if (attr_list[i].value.u8 != expected_attr_list[i].value.u8) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE: + if (attr_list[i].value.u16 != expected_attr_list[i].value.u16) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS: + case SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS: + if (attr_list[i].value.ipaddr.addr_family != expected_attr_list[i].value.ipaddr.addr_family || + (attr_list[i].value.ipaddr.addr_family == SAI_IP_ADDR_FAMILY_IPV4 && + attr_list[i].value.ipaddr.addr.ip4 != expected_attr_list[i].value.ipaddr.addr.ip4) || + (attr_list[i].value.ipaddr.addr_family == SAI_IP_ADDR_FAMILY_IPV6 && + memcmp(&attr_list[i].value.ipaddr.addr.ip6, &expected_attr_list[i].value.ipaddr.addr.ip6, + sizeof(sai_ip6_t)) != 0)) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS: + case SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS: + if (memcmp(&attr_list[i].value.mac, &expected_attr_list[i].value.mac, sizeof(sai_mac_t)) != 0) + { + return false; + } + break; + + default: + return false; + } + } + + return true; +} + +} // namespace + +class MirrorSessionManagerTest : public ::testing::Test +{ + protected: + MirrorSessionManagerTest() : mirror_session_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + // Set up mock stuff for SAI mirror API structure. + mock_sai_mirror = &mock_sai_mirror_; + sai_mirror_api->create_mirror_session = mock_create_mirror_session; + sai_mirror_api->remove_mirror_session = mock_remove_mirror_session; + sai_mirror_api->set_mirror_session_attribute = mock_set_mirror_session_attribute; + sai_mirror_api->get_mirror_session_attribute = mock_get_mirror_session_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + return mirror_session_manager_.enqueue(entry); + } + + void Drain() + { + return mirror_session_manager_.drain(); + } + + ReturnCodeOr DeserializeP4MirrorSessionAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return mirror_session_manager_.deserializeP4MirrorSessionAppDbEntry(key, attributes); + } + + p4orch::P4MirrorSessionEntry *GetMirrorSessionEntry(const std::string &mirror_session_key) + { + return mirror_session_manager_.getMirrorSessionEntry(mirror_session_key); + } + + ReturnCode ProcessAddRequest(const P4MirrorSessionAppDbEntry &app_db_entry) + { + return mirror_session_manager_.processAddRequest(app_db_entry); + } + + ReturnCode CreateMirrorSession(p4orch::P4MirrorSessionEntry mirror_session_entry) + { + return mirror_session_manager_.createMirrorSession(mirror_session_entry); + } + + ReturnCode ProcessUpdateRequest(const P4MirrorSessionAppDbEntry &app_db_entry, + p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.processUpdateRequest(app_db_entry, existing_mirror_session_entry); + } + + ReturnCode SetPort(const std::string &new_port, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setPort(new_port, existing_mirror_session_entry); + } + + ReturnCode SetSrcIp(const swss::IpAddress &new_src_ip, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setSrcIp(new_src_ip, existing_mirror_session_entry); + } + + ReturnCode SetDstIp(const swss::IpAddress &new_dst_ip, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setDstIp(new_dst_ip, existing_mirror_session_entry); + } + + ReturnCode SetSrcMac(const swss::MacAddress &new_src_mac, + p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setSrcMac(new_src_mac, existing_mirror_session_entry); + } + + ReturnCode SetDstMac(const swss::MacAddress &new_dst_mac, + p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setDstMac(new_dst_mac, existing_mirror_session_entry); + } + + ReturnCode SetTtl(uint8_t new_ttl, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setTtl(new_ttl, existing_mirror_session_entry); + } + + ReturnCode SetTos(uint8_t new_tos, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setTos(new_tos, existing_mirror_session_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &mirror_session_key) + { + return mirror_session_manager_.processDeleteRequest(mirror_session_key); + } + + void AddDefaultMirrorSection() + { + P4MirrorSessionAppDbEntry app_db_entry; + app_db_entry.mirror_session_id = kMirrorSessionId; + app_db_entry.has_port = true; + app_db_entry.port = kPort1; + app_db_entry.has_src_ip = true; + app_db_entry.src_ip = swss::IpAddress(kSrcIp1); + app_db_entry.has_dst_ip = true; + app_db_entry.dst_ip = swss::IpAddress(kDstIp1); + app_db_entry.has_src_mac = true; + app_db_entry.src_mac = swss::MacAddress(kSrcMac1); + app_db_entry.has_dst_mac = true; + app_db_entry.dst_mac = swss::MacAddress(kDstMac1); + app_db_entry.has_ttl = true; + app_db_entry.ttl = kTtl1Num; + app_db_entry.has_tos = true; + app_db_entry.tos = kTos1Num; + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session(::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate( + kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(app_db_entry)); + } + + StrictMock mock_sai_mirror_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + p4orch::MirrorSessionManager mirror_session_manager_; +}; + +// Do add, update and delete serially. +TEST_F(MirrorSessionManagerTest, SuccessfulEnqueueAndDrain) +{ + // 1. Add a new entry. + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session( + ::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate(kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + p4orch::P4MirrorSessionEntry expected_mirror_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + sai_object_id_t oid_in_mapper = 0; + EXPECT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), &oid_in_mapper)); + EXPECT_EQ(kMirrorSessionOid, oid_in_mapper); + + // 2. Update the added entry. + fvs = {{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort3}, + {prependParamField(p4orch::kSrcIp), kSrcIp2}, {prependParamField(p4orch::kDstIp), kDstIp2}, + {prependParamField(p4orch::kSrcMac), kSrcMac2}, {prependParamField(p4orch::kDstMac), kDstMac2}, + {prependParamField(p4orch::kTtl), kTtl2}, {prependParamField(p4orch::kTos), kTos2}}; + + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock call. + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = kPort3Oid; + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, swss::IpAddress(kSrcIp2)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, swss::IpAddress(kDstIp2)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, swss::MacAddress(kSrcMac2).getMac(), sizeof(sai_mac_t)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, swss::MacAddress(kDstMac2).getMac(), sizeof(sai_mac_t)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = kTtl2Num; + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = kTos2Num; + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + expected_mirror_entry.port = kPort3; + expected_mirror_entry.src_ip = swss::IpAddress(kSrcIp2); + expected_mirror_entry.dst_ip = swss::IpAddress(kDstIp2); + expected_mirror_entry.src_mac = swss::MacAddress(kSrcMac2); + expected_mirror_entry.dst_mac = swss::MacAddress(kDstMac2); + expected_mirror_entry.ttl = kTtl2Num; + expected_mirror_entry.tos = kTos2Num; + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + // 3. Delete the entry. + fvs = {}; + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), DEL_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, remove_mirror_session(Eq(kMirrorSessionOid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); + + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForInvalidAppDbEntryMatchFiled) +{ + nlohmann::json j; + j[prependMatchField("invalid_match_field")] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForUnknownOp) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), "unknown_op", fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForInvalidAppDbEntryFieldValue) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), "0123456789"}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForUnknownAppDbEntryFieldValue) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, + {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, + {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, + {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, + {prependParamField(p4orch::kTos), kTos1}, + {"unknown_field", "unknown_value"}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForIncompleteAppDbEntry) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs_missing_tos{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs_missing_tos); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForUnknownPort) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), "unknown_port"}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailWhenCreateSaiCallFails) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session( + ::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate(kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailWhenDeleteSaiCallFails) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session( + ::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate(kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + fvs = {}; + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), DEL_COMMAND, fvs}; + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_mirror_, remove_mirror_session(Eq(kMirrorSessionOid))).WillOnce(Return(SAI_STATUS_FAILURE)); + Drain(); + + // Check entry still exists. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DeserializeInvalidValueShouldFail) +{ + constexpr char *kInalidKey = R"({"invalid_key"})"; + std::vector fvs{{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kInalidKey, fvs).ok()); + + constexpr char *kValidKey = R"({"match/mirror_session_id":"mirror_session1"})"; + + std::vector invalid_src_ip_value = {{prependParamField(p4orch::kSrcIp), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kInalidKey, invalid_src_ip_value).ok()); + + std::vector invalid_dst_ip_value = {{prependParamField(p4orch::kDstIp), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_dst_ip_value).ok()); + + std::vector invalid_src_mac_value = {{prependParamField(p4orch::kSrcMac), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_src_mac_value).ok()); + + std::vector invalid_dst_mac_value = {{prependParamField(p4orch::kDstMac), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_dst_mac_value).ok()); + + std::vector invalid_ttl_value = {{prependParamField(p4orch::kTtl), "gpins"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_ttl_value).ok()); + + std::vector invalid_tos_value = {{prependParamField(p4orch::kTos), "xyz"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_tos_value).ok()); + + std::vector unsupported_port = {{prependParamField(p4orch::kPort), kPort2}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, unsupported_port).ok()); + + std::vector invalid_action_value = {{p4orch::kAction, "abc"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_action_value).ok()); +} + +TEST_F(MirrorSessionManagerTest, CreateExistingMirrorSessionInMapperShouldFail) +{ + p4orch::P4MirrorSessionEntry mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + + // Add this mirror session's oid to centralized mapper. + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry.mirror_session_key, + mirror_session_entry.mirror_session_oid)); + + // (TODO): Expect critical state. + EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); +} + +TEST_F(MirrorSessionManagerTest, CreateMirrorSessionWithInvalidPortShouldFail) +{ + // Non-existing port. + p4orch::P4MirrorSessionEntry mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, + "Non-existing Port", swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1), kTtl1Num, kTos1Num); + + EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); + + // Unsupported management port. + mirror_session_entry.port = kPort2; + EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); +} + +TEST_F(MirrorSessionManagerTest, UpdatingNonexistingMirrorSessionShouldFail) +{ + P4MirrorSessionAppDbEntry app_db_entry; + // Fail because existing_mirror_session_entry is nullptr. + // (TODO): Expect critical state. + EXPECT_FALSE(ProcessUpdateRequest(app_db_entry, + /*existing_mirror_session_entry=*/nullptr) + .ok()); + + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + + // Fail because the mirror session is not added into centralized mapper. + // (TODO): Expect critical state. + EXPECT_FALSE(ProcessUpdateRequest(app_db_entry, &existing_mirror_session_entry).ok()); +} + +TEST_F(MirrorSessionManagerTest, UpdatingPortFailureCases) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + // Case 1: non-existing port. + EXPECT_FALSE(SetPort("invalid_port", &existing_mirror_session_entry).ok()); + + // Case 2: kPort2 is an unsupported management port. + EXPECT_FALSE(SetPort(kPort2, &existing_mirror_session_entry).ok()); + + // Case 3: SAI call failure. + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetPort(kPort3, &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingSrcIpSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetSrcIp(swss::IpAddress(kSrcIp2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingDstIpSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetDstIp(swss::IpAddress(kDstIp2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingSrcMacSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetSrcMac(swss::MacAddress(kSrcMac2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingDstMacSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetDstMac(swss::MacAddress(kDstMac2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingTtlSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetTtl(kTtl2Num, &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingTosSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetTos(kTos2Num, &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +// The update operation should be atomic -- it either succeeds or fails without +// changing anything. This test case verifies that failed update operation +// doesn't change existing entry. +TEST_F(MirrorSessionManagerTest, UpdateFailureShouldNotChangeExistingEntry) +{ + // 1. Add a new entry. + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, create_mirror_session(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + p4orch::P4MirrorSessionEntry expected_mirror_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + sai_object_id_t oid_in_mapper = 0; + EXPECT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), &oid_in_mapper)); + EXPECT_EQ(kMirrorSessionOid, oid_in_mapper); + + // 2. Update the added entry. + fvs = {{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort3}, + {prependParamField(p4orch::kSrcIp), kSrcIp2}, {prependParamField(p4orch::kDstIp), kDstIp2}, + {prependParamField(p4orch::kSrcMac), kSrcMac2}, {prependParamField(p4orch::kDstMac), kDstMac2}, + {prependParamField(p4orch::kTtl), kTtl2}, {prependParamField(p4orch::kTos), kTos2}}; + + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock calls. Update entry will trigger 7 attribute updates and each + // attribute update requires a seperate SAI call. Let's pass the first 6 SAI + // calls and fail the last one. When update fails in the middle, 6 successful + // attribute updates will be reverted one by one. So the set SAI call wil be + // called 13 times and actions are 6 successes, 1 failure, 6successes. + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)) + .Times(13) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); +} + +TEST_F(MirrorSessionManagerTest, UpdateRecoveryFailureShouldRaiseCriticalState) +{ + // 1. Add a new entry. + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, create_mirror_session(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + p4orch::P4MirrorSessionEntry expected_mirror_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + sai_object_id_t oid_in_mapper = 0; + EXPECT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), &oid_in_mapper)); + EXPECT_EQ(kMirrorSessionOid, oid_in_mapper); + + // 2. Update the added entry. + fvs = {{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort3}, + {prependParamField(p4orch::kSrcIp), kSrcIp2}, {prependParamField(p4orch::kDstIp), kDstIp2}, + {prependParamField(p4orch::kSrcMac), kSrcMac2}, {prependParamField(p4orch::kDstMac), kDstMac2}, + {prependParamField(p4orch::kTtl), kTtl2}, {prependParamField(p4orch::kTos), kTos2}}; + + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock calls. Update entry will trigger 7 attribute updates and each + // attribute update requires a seperate SAI call. Let's pass the first 6 SAI + // calls and fail the last one. When update fails in the middle, 6 successful + // attribute updates will be reverted one by one. We will fail the recovery by + // failing the last revert. + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)) + .Times(13) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DeleteNonExistingMirrorSessionShouldFail) +{ + ASSERT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +TEST_F(MirrorSessionManagerTest, DeleteMirrorSessionWithNonZeroRefShouldFail) +{ + AddDefaultMirrorSection(); + p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_EQ(StatusCode::SWSS_RC_IN_USE, + ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +TEST_F(MirrorSessionManagerTest, DeleteMirrorSessionNotInMapperShouldFail) +{ + AddDefaultMirrorSection(); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + // (TODO): Expect critical state. + ASSERT_EQ(StatusCode::SWSS_RC_INTERNAL, + ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +} // namespace test +} // namespace p4orch diff --git a/orchagent/p4orch/tests/mock_response_publisher.h b/orchagent/p4orch/tests/mock_response_publisher.h new file mode 100644 index 0000000000..d163333bd0 --- /dev/null +++ b/orchagent/p4orch/tests/mock_response_publisher.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "response_publisher_interface.h" + +class MockResponsePublisher : public ResponsePublisherInterface +{ + public: + MOCK_METHOD6(publish, void(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + const std::vector &state_attrs, bool replace)); + MOCK_METHOD5(publish, + void(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, bool replace)); + MOCK_METHOD5(writeToDB, + void(const std::string &table, const std::string &key, + const std::vector &values, const std::string &op, bool replace)); +}; diff --git a/orchagent/p4orch/tests/mock_sai_acl.cpp b/orchagent/p4orch/tests/mock_sai_acl.cpp new file mode 100644 index 0000000000..531a71b3a5 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_acl.cpp @@ -0,0 +1,68 @@ +#include "mock_sai_acl.h" + +MockSaiAcl *mock_sai_acl; + +sai_status_t create_acl_table(sai_object_id_t *acl_table_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_table(acl_table_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_table(sai_object_id_t acl_table_id) +{ + return mock_sai_acl->remove_acl_table(acl_table_id); +} + +sai_status_t create_acl_table_group(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_table_group(acl_table_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_table_group(sai_object_id_t acl_table_group_id) +{ + return mock_sai_acl->remove_acl_table_group(acl_table_group_id); +} + +sai_status_t create_acl_table_group_member(sai_object_id_t *acl_table_group_member_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_table_group_member(acl_table_group_member_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_table_group_member(sai_object_id_t acl_table_group_member_id) +{ + return mock_sai_acl->remove_acl_table_group_member(acl_table_group_member_id); +} + +sai_status_t create_acl_entry(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_entry(acl_entry_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_entry(sai_object_id_t acl_entry_id) +{ + return mock_sai_acl->remove_acl_entry(acl_entry_id); +} + +sai_status_t create_acl_counter(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_counter(acl_counter_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_counter(sai_object_id_t acl_counter_id) +{ + return mock_sai_acl->remove_acl_counter(acl_counter_id); +} + +sai_status_t get_acl_counter_attribute(sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *attr_list) +{ + return mock_sai_acl->get_acl_counter_attribute(acl_counter_id, attr_count, attr_list); +} + +sai_status_t set_acl_entry_attribute(sai_object_id_t acl_entry_id, const sai_attribute_t *attr) +{ + return mock_sai_acl->set_acl_entry_attribute(acl_entry_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_acl.h b/orchagent/p4orch/tests/mock_sai_acl.h new file mode 100644 index 0000000000..252eb1768a --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_acl.h @@ -0,0 +1,87 @@ +#include + +extern "C" +{ +#include "sai.h" +#include "saiacl.h" +} + +class SaiAclInterface +{ + public: + virtual sai_status_t create_acl_table(sai_object_id_t *acl_table_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_table(sai_object_id_t acl_table_id) = 0; + virtual sai_status_t create_acl_table_group(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_table_group(sai_object_id_t acl_table_group_id) = 0; + virtual sai_status_t create_acl_table_group_member(sai_object_id_t *acl_table_group_member_id, + sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_table_group_member(sai_object_id_t acl_table_group_member_id) = 0; + + virtual sai_status_t create_acl_entry(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_entry(sai_object_id_t acl_entry_id) = 0; + virtual sai_status_t create_acl_counter(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_counter(sai_object_id_t acl_counter_id) = 0; + virtual sai_status_t get_acl_counter_attribute(sai_object_id_t acl_counter_id, uint32_t attr_count, + sai_attribute_t *attr_list) = 0; + virtual sai_status_t set_acl_entry_attribute(sai_object_id_t acl_entry_id, const sai_attribute_t *attr) = 0; +}; + +class MockSaiAcl : public SaiAclInterface +{ + public: + MOCK_METHOD4(create_acl_table, sai_status_t(sai_object_id_t *acl_table_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_table, sai_status_t(sai_object_id_t acl_table_id)); + MOCK_METHOD4(create_acl_table_group, sai_status_t(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_table_group, sai_status_t(sai_object_id_t acl_table_group_id)); + MOCK_METHOD4(create_acl_table_group_member, + sai_status_t(sai_object_id_t *acl_table_group_member_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_table_group_member, sai_status_t(sai_object_id_t acl_table_group_member_id)); + MOCK_METHOD4(create_acl_entry, sai_status_t(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_entry, sai_status_t(sai_object_id_t acl_entry_id)); + MOCK_METHOD4(create_acl_counter, sai_status_t(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_counter, sai_status_t(sai_object_id_t acl_counter_id)); + MOCK_METHOD3(get_acl_counter_attribute, + sai_status_t(sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *attr_list)); + MOCK_METHOD2(set_acl_entry_attribute, sai_status_t(sai_object_id_t acl_entry_id, const sai_attribute_t *attr)); +}; + +extern MockSaiAcl *mock_sai_acl; + +sai_status_t create_acl_table(_Out_ sai_object_id_t *acl_table_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t remove_acl_table(sai_object_id_t acl_table_id); + +sai_status_t create_acl_table_group(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_acl_table_group(sai_object_id_t acl_table_group_id); + +sai_status_t create_acl_table_group_member(sai_object_id_t *acl_table_group_member_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list); + +sai_status_t remove_acl_table_group_member(sai_object_id_t acl_table_group_member_id); + +sai_status_t create_acl_entry(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_acl_entry(sai_object_id_t acl_entry_id); + +sai_status_t create_acl_counter(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_acl_counter(sai_object_id_t acl_counter_id); + +sai_status_t get_acl_counter_attribute(sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *attr_list); + +sai_status_t set_acl_entry_attribute(sai_object_id_t acl_entry_id, const sai_attribute_t *attr); diff --git a/orchagent/p4orch/tests/mock_sai_hostif.cpp b/orchagent/p4orch/tests/mock_sai_hostif.cpp new file mode 100644 index 0000000000..7dcc0f70c2 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_hostif.cpp @@ -0,0 +1,67 @@ +#include "mock_sai_hostif.h" + +MockSaiHostif *mock_sai_hostif; + +sai_status_t mock_create_hostif_trap_group(_Out_ sai_object_id_t *hostif_trap_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_trap_group(hostif_trap_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_set_hostif_trap_group_attribute(_In_ sai_object_id_t hostif_trap_group_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_hostif->set_hostif_trap_group_attribute(hostif_trap_group_id, attr); +} + +sai_status_t mock_remove_hostif_trap_group(_In_ const sai_object_id_t hostif_trap_group_id) +{ + return mock_sai_hostif->remove_hostif_trap_group(hostif_trap_group_id); +} + +sai_status_t mock_create_hostif_table_entry(_Out_ sai_object_id_t *hostif_table_entry_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_table_entry(hostif_table_entry_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_hostif_table_entry(_In_ const sai_object_id_t hostif_table_entry_id) +{ + return mock_sai_hostif->remove_hostif_table_entry(hostif_table_entry_id); +} + +sai_status_t mock_create_hostif_user_defined_trap(_Out_ sai_object_id_t *hostif_user_defined_trap_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_user_defined_trap(hostif_user_defined_trap_id, switch_id, attr_count, + attr_list); +} + +sai_status_t mock_remove_hostif_user_defined_trap(_In_ const sai_object_id_t hostif_user_defined_trap_id) +{ + return mock_sai_hostif->remove_hostif_user_defined_trap(hostif_user_defined_trap_id); +} + +sai_status_t mock_create_hostif_trap(_Out_ sai_object_id_t *hostif_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_trap(hostif_trap_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_hostif_trap(_In_ const sai_object_id_t hostif_trap_id) +{ + return mock_sai_hostif->remove_hostif_trap(hostif_trap_id); +} + +sai_status_t mock_create_hostif(_Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif(hostif_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_hostif(_In_ const sai_object_id_t hostif_id) +{ + return mock_sai_hostif->remove_hostif(hostif_id); +} diff --git a/orchagent/p4orch/tests/mock_sai_hostif.h b/orchagent/p4orch/tests/mock_sai_hostif.h new file mode 100644 index 0000000000..0e758aeebd --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_hostif.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to host interface object SAI APIs. +class MockSaiHostif +{ + public: + MOCK_METHOD4(create_hostif_trap_group, + sai_status_t(_Out_ sai_object_id_t *hostif_trap_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD2(set_hostif_trap_group_attribute, + sai_status_t(_In_ sai_object_id_t hostif_trap_group_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD1(remove_hostif_trap_group, sai_status_t(_In_ sai_object_id_t hostif_trap_group_id)); + + MOCK_METHOD4(create_hostif_table_entry, + sai_status_t(_Out_ sai_object_id_t *hostif_table_entry_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif_table_entry, sai_status_t(_In_ sai_object_id_t hostif_table_entry_id)); + + MOCK_METHOD4(create_hostif_user_defined_trap, + sai_status_t(_Out_ sai_object_id_t *hostif_user_defined_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif_user_defined_trap, sai_status_t(_In_ sai_object_id_t hostif_user_defined_trap_id)); + + MOCK_METHOD4(create_hostif_trap, sai_status_t(_Out_ sai_object_id_t *hostif_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif_trap, sai_status_t(_In_ sai_object_id_t hostif_trap_id)); + + MOCK_METHOD4(create_hostif, sai_status_t(_Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif, sai_status_t(_In_ sai_object_id_t hostif_id)); +}; + +extern MockSaiHostif *mock_sai_hostif; + +sai_status_t mock_create_hostif_trap_group(_Out_ sai_object_id_t *hostif_trap_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_set_hostif_trap_group_attribute(_In_ sai_object_id_t hostif_trap_group_id, + _In_ const sai_attribute_t *attr); + +sai_status_t mock_remove_hostif_trap_group(_In_ const sai_object_id_t hostif_trap_group_id); + +sai_status_t mock_create_hostif_table_entry(_Out_ sai_object_id_t *hostif_table_entry_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif_table_entry(_In_ const sai_object_id_t hostif_table_entry_id); + +sai_status_t mock_create_hostif_user_defined_trap(_Out_ sai_object_id_t *hostif_user_defined_trap_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif_user_defined_trap(_In_ const sai_object_id_t hostif_user_defined_trap_id); + +sai_status_t mock_create_hostif_trap(_Out_ sai_object_id_t *hostif_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif_trap(_In_ const sai_object_id_t hostif_trap_id); + +sai_status_t mock_create_hostif(_Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif(_In_ const sai_object_id_t hostif_id); diff --git a/orchagent/p4orch/tests/mock_sai_mirror.h b/orchagent/p4orch/tests/mock_sai_mirror.h new file mode 100644 index 0000000000..39991d583e --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_mirror.h @@ -0,0 +1,53 @@ +// Define classes and functions to mock SAI mirror functions. +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock class including mock functions mapping to SAI mirror's functions. +class MockSaiMirror +{ + public: + MOCK_METHOD4(create_mirror_session, + sai_status_t(_Out_ sai_object_id_t *mirror_session_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_mirror_session, sai_status_t(_In_ sai_object_id_t mirror_session_id)); + + MOCK_METHOD2(set_mirror_session_attribute, + sai_status_t(_In_ sai_object_id_t mirror_session_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_mirror_session_attribute, + sai_status_t(_In_ sai_object_id_t mirror_session_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +// Note that before mock functions below are used, mock_sai_mirror must be +// initialized to point to an instance of MockSaiMirror. +MockSaiMirror *mock_sai_mirror; + +sai_status_t mock_create_mirror_session(_Out_ sai_object_id_t *mirror_session_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_mirror->create_mirror_session(mirror_session_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_mirror_session(_In_ sai_object_id_t mirror_session_id) +{ + return mock_sai_mirror->remove_mirror_session(mirror_session_id); +} + +sai_status_t mock_set_mirror_session_attribute(_In_ sai_object_id_t mirror_session_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_mirror->set_mirror_session_attribute(mirror_session_id, attr); +} + +sai_status_t mock_get_mirror_session_attribute(_In_ sai_object_id_t mirror_session_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_mirror->get_mirror_session_attribute(mirror_session_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_neighbor.h b/orchagent/p4orch/tests/mock_sai_neighbor.h new file mode 100644 index 0000000000..cd8f2aa0a9 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_neighbor.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to neighbor object SAI APIs. +class MockSaiNeighbor +{ + public: + MOCK_METHOD3(create_neighbor_entry, sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_neighbor_entry, sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry)); + + MOCK_METHOD2(set_neighbor_entry_attribute, + sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_neighbor_entry_attribute, + sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +MockSaiNeighbor *mock_sai_neighbor; + +sai_status_t mock_create_neighbor_entry(_In_ const sai_neighbor_entry_t *neighbor_entry, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_neighbor->create_neighbor_entry(neighbor_entry, attr_count, attr_list); +} + +sai_status_t mock_remove_neighbor_entry(_In_ const sai_neighbor_entry_t *neighbor_entry) +{ + return mock_sai_neighbor->remove_neighbor_entry(neighbor_entry); +} + +sai_status_t mock_set_neighbor_entry_attribute(_In_ const sai_neighbor_entry_t *neighbor_entry, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_neighbor->set_neighbor_entry_attribute(neighbor_entry, attr); +} + +sai_status_t mock_get_neighbor_entry_attribute(_In_ const sai_neighbor_entry_t *neighbor_entry, + _In_ uint32_t attr_count, _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_neighbor->get_neighbor_entry_attribute(neighbor_entry, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_next_hop.h b/orchagent/p4orch/tests/mock_sai_next_hop.h new file mode 100644 index 0000000000..83e6e7d506 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_next_hop.h @@ -0,0 +1,51 @@ +// Define classes and functions to mock SAI next hop functions. +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock class including mock functions mapping to SAI next hop's functions. +class MockSaiNextHop +{ + public: + MOCK_METHOD4(create_next_hop, sai_status_t(_Out_ sai_object_id_t *next_hop_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_next_hop, sai_status_t(_In_ sai_object_id_t next_hop_id)); + + MOCK_METHOD2(set_next_hop_attribute, + sai_status_t(_In_ sai_object_id_t next_hop_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_next_hop_attribute, sai_status_t(_In_ sai_object_id_t next_hop_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +// Note that before mock functions below are used, mock_sai_next_hop must be +// initialized to point to an instance of MockSaiNextHop. +MockSaiNextHop *mock_sai_next_hop; + +sai_status_t mock_create_next_hop(_Out_ sai_object_id_t *next_hop_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_next_hop->create_next_hop(next_hop_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_next_hop(_In_ sai_object_id_t next_hop_id) +{ + return mock_sai_next_hop->remove_next_hop(next_hop_id); +} + +sai_status_t mock_set_next_hop_attribute(_In_ sai_object_id_t next_hop_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_next_hop->set_next_hop_attribute(next_hop_id, attr); +} + +sai_status_t mock_get_next_hop_attribute(_In_ sai_object_id_t next_hop_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_next_hop->get_next_hop_attribute(next_hop_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_next_hop_group.h b/orchagent/p4orch/tests/mock_sai_next_hop_group.h new file mode 100644 index 0000000000..5398ec5a70 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_next_hop_group.h @@ -0,0 +1,64 @@ +// Define classes and functions to mock SAI next hop group functions. +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock class including mock functions mapping to SAI next hop group's +// functions. +class MockSaiNextHopGroup +{ + public: + MOCK_METHOD4(create_next_hop_group, + sai_status_t(_Out_ sai_object_id_t *next_hop_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_next_hop_group, sai_status_t(_In_ sai_object_id_t next_hop_group_id)); + + MOCK_METHOD4(create_next_hop_group_member, + sai_status_t(_Out_ sai_object_id_t *next_hop_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_next_hop_group_member, sai_status_t(_In_ sai_object_id_t next_hop_group_member_id)); + + MOCK_METHOD2(set_next_hop_group_member_attribute, + sai_status_t(_In_ sai_object_id_t next_hop_group_member_id, _In_ const sai_attribute_t *attr)); +}; + +// Note that before mock functions below are used, mock_sai_next_hop_group must +// be initialized to point to an instance of MockSaiNextHopGroup. +MockSaiNextHopGroup *mock_sai_next_hop_group; + +sai_status_t create_next_hop_group(_Out_ sai_object_id_t *next_hop_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_next_hop_group->create_next_hop_group(next_hop_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_next_hop_group(_In_ sai_object_id_t next_hop_group_id) +{ + return mock_sai_next_hop_group->remove_next_hop_group(next_hop_group_id); +} + +sai_status_t create_next_hop_group_member(_Out_ sai_object_id_t *next_hop_group_member_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_next_hop_group->create_next_hop_group_member(next_hop_group_member_id, switch_id, attr_count, + attr_list); +} + +sai_status_t remove_next_hop_group_member(_In_ sai_object_id_t next_hop_group_member_id) +{ + return mock_sai_next_hop_group->remove_next_hop_group_member(next_hop_group_member_id); +} + +sai_status_t set_next_hop_group_member_attribute(_In_ sai_object_id_t next_hop_group_member_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_next_hop_group->set_next_hop_group_member_attribute(next_hop_group_member_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_policer.h b/orchagent/p4orch/tests/mock_sai_policer.h new file mode 100644 index 0000000000..351cca14c0 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_policer.h @@ -0,0 +1,53 @@ +#pragma once + +extern "C" +{ +#include "sai.h" +#include "saipolicer.h" +} + +class SaiPolicerInterface +{ + public: + virtual sai_status_t create_policer(sai_object_id_t *policer_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_policer(sai_object_id_t policer_id) = 0; + virtual sai_status_t get_policer_stats(sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) = 0; + virtual sai_status_t set_policer_attribute(sai_object_id_t policer_id, const sai_attribute_t *attr) = 0; +}; + +class MockSaiPolicer : public SaiPolicerInterface +{ + public: + MOCK_METHOD4(create_policer, sai_status_t(sai_object_id_t *policer_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_policer, sai_status_t(sai_object_id_t policer_id)); + MOCK_METHOD4(get_policer_stats, sai_status_t(sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters)); + MOCK_METHOD2(set_policer_attribute, sai_status_t(sai_object_id_t policer_id, const sai_attribute_t *attr)); +}; + +MockSaiPolicer *mock_sai_policer; + +sai_status_t create_policer(sai_object_id_t *policer_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_policer->create_policer(policer_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_policer(sai_object_id_t policer_id) +{ + return mock_sai_policer->remove_policer(policer_id); +} + +sai_status_t get_policer_stats(sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) +{ + return mock_sai_policer->get_policer_stats(policer_id, number_of_counters, counter_ids, counters); +} + +sai_status_t set_policer_attribute(sai_object_id_t policer_id, const sai_attribute_t *attr) +{ + return mock_sai_policer->set_policer_attribute(policer_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_route.h b/orchagent/p4orch/tests/mock_sai_route.h new file mode 100644 index 0000000000..b40cf6605e --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_route.h @@ -0,0 +1,108 @@ +#pragma once + +extern "C" +{ +#include "sai.h" +#include "sairoute.h" +} + +class SaiRouteInterface +{ + public: + virtual sai_status_t create_route_entry(const sai_route_entry_t *route_entry, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_route_entry(const sai_route_entry_t *route_entry) = 0; + virtual sai_status_t set_route_entry_attribute(const sai_route_entry_t *route_entry, + const sai_attribute_t *attr) = 0; + virtual sai_status_t get_route_entry_attribute(const sai_route_entry_t *route_entry, uint32_t attr_count, + sai_attribute_t *attr_list) = 0; + virtual sai_status_t create_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, const sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) = 0; + virtual sai_status_t remove_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) = 0; + virtual sai_status_t set_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses) = 0; + virtual sai_status_t get_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) = 0; +}; + +class MockSaiRoute : public SaiRouteInterface +{ + public: + MOCK_METHOD3(create_route_entry, sai_status_t(const sai_route_entry_t *route_entry, uint32_t attr_count, + const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_route_entry, sai_status_t(const sai_route_entry_t *route_entry)); + MOCK_METHOD2(set_route_entry_attribute, + sai_status_t(const sai_route_entry_t *route_entry, const sai_attribute_t *attr)); + MOCK_METHOD3(get_route_entry_attribute, + sai_status_t(const sai_route_entry_t *route_entry, uint32_t attr_count, sai_attribute_t *attr_list)); + MOCK_METHOD6(create_route_entries, sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, const sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses)); + MOCK_METHOD4(remove_route_entries, sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses)); + MOCK_METHOD5(set_route_entries_attribute, + sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, + const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses)); + MOCK_METHOD6(get_route_entries_attribute, + sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, const uint32_t *attr_count, + sai_attribute_t **attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses)); +}; + +MockSaiRoute *mock_sai_route; + +sai_status_t create_route_entry(const sai_route_entry_t *route_entry, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_route->create_route_entry(route_entry, attr_count, attr_list); +} + +sai_status_t remove_route_entry(const sai_route_entry_t *route_entry) +{ + return mock_sai_route->remove_route_entry(route_entry); +} + +sai_status_t set_route_entry_attribute(const sai_route_entry_t *route_entry, const sai_attribute_t *attr) +{ + return mock_sai_route->set_route_entry_attribute(route_entry, attr); +} + +sai_status_t get_route_entry_attribute(const sai_route_entry_t *route_entry, uint32_t attr_count, + sai_attribute_t *attr_list) +{ + return mock_sai_route->get_route_entry_attribute(route_entry, attr_count, attr_list); +} + +sai_status_t create_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, const sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) +{ + return mock_sai_route->create_route_entries(object_count, route_entry, attr_count, attr_list, mode, + object_statuses); +} + +sai_status_t remove_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) +{ + return mock_sai_route->remove_route_entries(object_count, route_entry, mode, object_statuses); +} + +sai_status_t set_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses) +{ + return mock_sai_route->set_route_entries_attribute(object_count, route_entry, attr_list, mode, object_statuses); +} + +sai_status_t get_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) +{ + return mock_sai_route->get_route_entries_attribute(object_count, route_entry, attr_count, attr_list, mode, + object_statuses); +} diff --git a/orchagent/p4orch/tests/mock_sai_router_interface.h b/orchagent/p4orch/tests/mock_sai_router_interface.h new file mode 100644 index 0000000000..9c0caa3004 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_router_interface.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to router interface SAI APIs. +class MockSaiRouterInterface +{ + public: + MOCK_METHOD4(create_router_interface, + sai_status_t(_Out_ sai_object_id_t *router_interface_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_router_interface, sai_status_t(_In_ sai_object_id_t router_interface_id)); + + MOCK_METHOD2(set_router_interface_attribute, + sai_status_t(_In_ sai_object_id_t router_interface_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_router_interface_attribute, + sai_status_t(_In_ sai_object_id_t router_interface_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +MockSaiRouterInterface *mock_sai_router_intf; + +sai_status_t mock_create_router_interface(_Out_ sai_object_id_t *router_interface_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_router_intf->create_router_interface(router_interface_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_router_interface(_In_ sai_object_id_t router_interface_id) +{ + return mock_sai_router_intf->remove_router_interface(router_interface_id); +} + +sai_status_t mock_set_router_interface_attribute(_In_ sai_object_id_t router_interface_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_router_intf->set_router_interface_attribute(router_interface_id, attr); +} + +sai_status_t mock_get_router_interface_attribute(_In_ sai_object_id_t router_interface_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_router_intf->get_router_interface_attribute(router_interface_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_serialize.cpp b/orchagent/p4orch/tests/mock_sai_serialize.cpp new file mode 100644 index 0000000000..ada42acf02 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_serialize.cpp @@ -0,0 +1,13 @@ +#include "mock_sai_serialize.h" + +MockSaiSerialize *mock_sai_serialize; + +inline std::string sai_serialize_object_id(sai_object_id_t oid) +{ + return mock_sai_serialize->sai_serialize_object_id(oid); +} + +inline std::string sai_serialize_object_type(sai_object_type_t object_type) +{ + return mock_sai_serialize->sai_serialize_object_type(object_type); +} diff --git a/orchagent/p4orch/tests/mock_sai_serialize.h b/orchagent/p4orch/tests/mock_sai_serialize.h new file mode 100644 index 0000000000..4e4ae50573 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_serialize.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "sai_serialize.h" + +class SaiSerializeInterface +{ + public: + virtual std::string sai_serialize_object_id(sai_object_id_t oid) = 0; + virtual std::string sai_serialize_object_type(sai_object_type_t object_type) = 0; +}; + +class MockSaiSerialize : public SaiSerializeInterface +{ + public: + MOCK_METHOD1(sai_serialize_object_id, std::string(sai_object_id_t oid)); + MOCK_METHOD1(sai_serialize_object_type, std::string(sai_object_type_t object_type)); +}; + +extern MockSaiSerialize *mock_sai_serialize; + +std::string sai_serialize_object_id(sai_object_id_t oid); + +std::string sai_serialize_object_type(sai_object_type_t object_type); diff --git a/orchagent/p4orch/tests/mock_sai_switch.cpp b/orchagent/p4orch/tests/mock_sai_switch.cpp new file mode 100644 index 0000000000..180de2d6f9 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_switch.cpp @@ -0,0 +1,14 @@ +#include "mock_sai_switch.h" + +MockSaiSwitch *mock_sai_switch; + +sai_status_t mock_get_switch_attribute(_In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_switch->get_switch_attribute(switch_id, attr_count, attr_list); +} + +sai_status_t mock_set_switch_attribute(_In_ sai_object_id_t switch_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_switch->set_switch_attribute(switch_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_switch.h b/orchagent/p4orch/tests/mock_sai_switch.h new file mode 100644 index 0000000000..2883178ea3 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_switch.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to switch object SAI APIs. +class MockSaiSwitch +{ + public: + MOCK_METHOD3(get_switch_attribute, sai_status_t(_In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); + MOCK_METHOD2(set_switch_attribute, sai_status_t(_In_ sai_object_id_t switch_id, _In_ const sai_attribute_t *attr)); +}; + +extern MockSaiSwitch *mock_sai_switch; + +sai_status_t mock_get_switch_attribute(_In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list); + +sai_status_t mock_set_switch_attribute(_In_ sai_object_id_t switch_id, _In_ const sai_attribute_t *attr); diff --git a/orchagent/p4orch/tests/mock_sai_udf.cpp b/orchagent/p4orch/tests/mock_sai_udf.cpp new file mode 100644 index 0000000000..4133988c8b --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_udf.cpp @@ -0,0 +1,36 @@ +#include "mock_sai_udf.h" + +MockSaiUdf *mock_sai_udf; + +sai_status_t create_udf(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_udf->create_udf(udf_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_udf(sai_object_id_t udf_id) +{ + return mock_sai_udf->remove_udf(udf_id); +} + +sai_status_t create_udf_group(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_udf->create_udf_group(udf_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_udf_group(sai_object_id_t udf_group_id) +{ + return mock_sai_udf->remove_udf_group(udf_group_id); +} + +sai_status_t create_udf_match(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_udf->create_udf_match(udf_match_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_udf_match(sai_object_id_t udf_match_id) +{ + return mock_sai_udf->remove_udf_match(udf_match_id); +} diff --git a/orchagent/p4orch/tests/mock_sai_udf.h b/orchagent/p4orch/tests/mock_sai_udf.h new file mode 100644 index 0000000000..251f8c2b1a --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_udf.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +#include "saiudf.h" +} + +class SaiUdfInterface +{ + public: + virtual sai_status_t create_udf(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_udf(sai_object_id_t udf_id) = 0; + virtual sai_status_t create_udf_group(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_udf_group(sai_object_id_t udf_group_id) = 0; + virtual sai_status_t create_udf_match(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_udf_match(sai_object_id_t udf_match_id) = 0; +}; + +class MockSaiUdf : public SaiUdfInterface +{ + public: + MOCK_METHOD4(create_udf, sai_status_t(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_udf, sai_status_t(sai_object_id_t udf_id)); + MOCK_METHOD4(create_udf_group, sai_status_t(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_udf_group, sai_status_t(sai_object_id_t udf_group_id)); + MOCK_METHOD4(create_udf_match, sai_status_t(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_udf_match, sai_status_t(sai_object_id_t udf_match_id)); +}; + +extern MockSaiUdf *mock_sai_udf; + +sai_status_t create_udf(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_udf(sai_object_id_t udf_id); + +sai_status_t create_udf_group(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_udf_group(sai_object_id_t udf_group_id); + +sai_status_t create_udf_match(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_udf_match(sai_object_id_t udf_match_id); diff --git a/orchagent/p4orch/tests/mock_sai_virtual_router.h b/orchagent/p4orch/tests/mock_sai_virtual_router.h new file mode 100644 index 0000000000..943b9b4828 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_virtual_router.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to router interface SAI APIs. +class MockSaiVirtualRouter +{ + public: + MOCK_METHOD4(create_virtual_router, + sai_status_t(_Out_ sai_object_id_t *virtual_router_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_virtual_router, sai_status_t(_In_ sai_object_id_t virtual_router_id)); + + MOCK_METHOD2(set_virtual_router_attribute, + sai_status_t(_In_ sai_object_id_t virtual_router_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_virtual_router_attribute, + sai_status_t(_In_ sai_object_id_t virtual_router_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +MockSaiVirtualRouter *mock_sai_virtual_router; + +sai_status_t create_virtual_router(_Out_ sai_object_id_t *virtual_router_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_virtual_router->create_virtual_router(virtual_router_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_virtual_router(_In_ sai_object_id_t virtual_router_id) +{ + return mock_sai_virtual_router->remove_virtual_router(virtual_router_id); +} + +sai_status_t set_virtual_router_attribute(_In_ sai_object_id_t virtual_router_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_virtual_router->set_virtual_router_attribute(virtual_router_id, attr); +} + +sai_status_t get_virtual_router_attribute(_In_ sai_object_id_t virtual_router_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_virtual_router->get_virtual_router_attribute(virtual_router_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/neighbor_manager_test.cpp b/orchagent/p4orch/tests/neighbor_manager_test.cpp new file mode 100644 index 0000000000..e3986ef701 --- /dev/null +++ b/orchagent/p4orch/tests/neighbor_manager_test.cpp @@ -0,0 +1,822 @@ +#include "neighbor_manager.h" + +#include +#include + +#include +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_neighbor.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "return_code.h" +#include "swssnet.h" + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::Eq; +using ::testing::Return; +using ::testing::StrictMock; +using ::testing::Truly; + +extern sai_object_id_t gSwitchId; +extern sai_neighbor_api_t *sai_neighbor_api; + +namespace +{ + +constexpr char *kRouterInterfaceId1 = "intf-3/4"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 0x295100; + +constexpr char *kRouterInterfaceId2 = "Ethernet20"; +constexpr sai_object_id_t kRouterInterfaceOid2 = 0x51411; + +const swss::IpAddress kNeighborId1("10.0.0.22"); +const swss::MacAddress kMacAddress1("00:01:02:03:04:05"); + +const swss::IpAddress kNeighborId2("fe80::21a:11ff:fe17:5f80"); +const swss::MacAddress kMacAddress2("00:ff:ee:dd:cc:bb"); + +bool MatchNeighborEntry(const sai_neighbor_entry_t *neigh_entry, const sai_neighbor_entry_t &expected_neigh_entry) +{ + if (neigh_entry == nullptr) + return false; + + if ((neigh_entry->switch_id != expected_neigh_entry.switch_id) || + (neigh_entry->rif_id != expected_neigh_entry.rif_id) || + (neigh_entry->ip_address.addr_family != expected_neigh_entry.ip_address.addr_family)) + return false; + + if ((neigh_entry->ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) && + (neigh_entry->ip_address.addr.ip4 != expected_neigh_entry.ip_address.addr.ip4)) + { + return false; + } + else if ((neigh_entry->ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV6) && + (memcmp(neigh_entry->ip_address.addr.ip6, expected_neigh_entry.ip_address.addr.ip6, 16))) + { + return false; + } + + return true; +} + +bool MatchNeighborCreateAttributeList(const sai_attribute_t *attr_list, const swss::MacAddress &dst_mac_address) +{ + if (attr_list == nullptr) + return false; + + std::unordered_set attrs; + + for (int i = 0; i < 2; ++i) + { + if (attrs.count(attr_list[i].id) != 0) + { + // Repeated attribute. + return false; + } + switch (attr_list[i].id) + { + case SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS: + if (memcmp(attr_list[i].value.mac, dst_mac_address.getMac(), sizeof(sai_mac_t)) != 0) + { + return false; + } + break; + case SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE: + if (!attr_list[i].value.booldata) + { + return false; + } + break; + default: + return false; + } + attrs.insert(attr_list[i].id); + } + + return true; +} + +bool MatchNeighborSetAttributeList(const sai_attribute_t *attr_list, const swss::MacAddress &dst_mac_address) +{ + if (attr_list == nullptr) + return false; + + return (attr_list[0].id == SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS && + memcmp(attr_list[0].value.mac, dst_mac_address.getMac(), sizeof(sai_mac_t)) == 0); +} + +} // namespace + +class NeighborManagerTest : public ::testing::Test +{ + protected: + NeighborManagerTest() : neighbor_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + mock_sai_neighbor = &mock_sai_neighbor_; + sai_neighbor_api->create_neighbor_entry = mock_create_neighbor_entry; + sai_neighbor_api->remove_neighbor_entry = mock_remove_neighbor_entry; + sai_neighbor_api->set_neighbor_entry_attribute = mock_set_neighbor_entry_attribute; + sai_neighbor_api->get_neighbor_entry_attribute = mock_get_neighbor_entry_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + neighbor_manager_.enqueue(entry); + } + + void Drain() + { + neighbor_manager_.drain(); + } + + ReturnCodeOr DeserializeNeighborEntry(const std::string &key, + const std::vector &attributes) + { + return neighbor_manager_.deserializeNeighborEntry(key, attributes); + } + + ReturnCode ValidateNeighborAppDbEntry(const P4NeighborAppDbEntry &app_db_entry) + { + return neighbor_manager_.validateNeighborAppDbEntry(app_db_entry); + } + + ReturnCode CreateNeighbor(P4NeighborEntry &neighbor_entry) + { + return neighbor_manager_.createNeighbor(neighbor_entry); + } + + ReturnCode RemoveNeighbor(const std::string &neighbor_key) + { + return neighbor_manager_.removeNeighbor(neighbor_key); + } + + ReturnCode SetDstMacAddress(P4NeighborEntry *neighbor_entry, const swss::MacAddress &mac_address) + { + return neighbor_manager_.setDstMacAddress(neighbor_entry, mac_address); + } + + ReturnCode ProcessAddRequest(const P4NeighborAppDbEntry &app_db_entry, const std::string &neighbor_key) + { + return neighbor_manager_.processAddRequest(app_db_entry, neighbor_key); + } + + ReturnCode ProcessUpdateRequest(const P4NeighborAppDbEntry &app_db_entry, P4NeighborEntry *neighbor_entry) + { + return neighbor_manager_.processUpdateRequest(app_db_entry, neighbor_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &neighbor_key) + { + return neighbor_manager_.processDeleteRequest(neighbor_key); + } + + P4NeighborEntry *GetNeighborEntry(const std::string &neighbor_key) + { + return neighbor_manager_.getNeighborEntry(neighbor_key); + } + + void ValidateNeighborEntry(const P4NeighborEntry &expected_entry, const uint32_t router_intf_ref_count) + { + auto neighbor_entry = GetNeighborEntry(expected_entry.neighbor_key); + + EXPECT_NE(nullptr, neighbor_entry); + EXPECT_EQ(expected_entry.router_intf_id, neighbor_entry->router_intf_id); + EXPECT_EQ(expected_entry.neighbor_id, neighbor_entry->neighbor_id); + EXPECT_EQ(expected_entry.dst_mac_address, neighbor_entry->dst_mac_address); + EXPECT_EQ(expected_entry.router_intf_key, neighbor_entry->router_intf_key); + EXPECT_EQ(expected_entry.neighbor_key, neighbor_entry->neighbor_key); + + EXPECT_TRUE(MatchNeighborEntry(&neighbor_entry->neigh_entry, expected_entry.neigh_entry)); + + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, expected_entry.neighbor_key)); + + uint32_t ref_count; + ASSERT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, expected_entry.router_intf_key, &ref_count)); + EXPECT_EQ(router_intf_ref_count, ref_count); + } + + void ValidateNeighborEntryNotPresent(const P4NeighborEntry &neighbor_entry, bool check_ref_count, + const uint32_t router_intf_ref_count = 0) + { + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + EXPECT_EQ(current_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); + + if (check_ref_count) + { + uint32_t ref_count; + ASSERT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key, + &ref_count)); + EXPECT_EQ(ref_count, router_intf_ref_count); + } + } + + void AddNeighborEntry(P4NeighborEntry &neighbor_entry, const sai_object_id_t router_intf_oid) + { + sai_neighbor_entry_t neigh_entry; + neigh_entry.switch_id = gSwitchId; + copy(neigh_entry.ip_address, neighbor_entry.neighbor_id); + neigh_entry.rif_id = router_intf_oid; + + EXPECT_CALL(mock_sai_neighbor_, + create_neighbor_entry(Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neigh_entry)), + Eq(2), + Truly(std::bind(MatchNeighborCreateAttributeList, std::placeholders::_1, + neighbor_entry.dst_mac_address)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + ASSERT_TRUE( + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key, router_intf_oid)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateNeighbor(neighbor_entry)); + } + + std::string CreateNeighborAppDbKey(const std::string router_interface_id, const swss::IpAddress neighbor_id) + { + nlohmann::json j; + j[prependMatchField(p4orch::kRouterInterfaceId)] = router_interface_id; + j[prependMatchField(p4orch::kNeighborId)] = neighbor_id.to_string(); + return j.dump(); + } + + StrictMock mock_sai_neighbor_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + NeighborManager neighbor_manager_; +}; + +TEST_F(NeighborManagerTest, CreateNeighborValidAttributes) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, CreateNeighborEntryExistsInManager) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + // Same neighbor key with different destination mac address. + P4NeighborEntry new_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress2); + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, CreateNeighbor(new_entry)); + + // Validate that entry in Manager has not changed. + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, CreateNeighborEntryExistsInP4OidMapper) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateNeighbor(neighbor_entry)); + + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + EXPECT_EQ(current_entry, nullptr); + + // Validate that dummyOID still exists in Centralized Mapper. + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); +} + +TEST_F(NeighborManagerTest, CreateNeighborNonExistentRouterIntf) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, CreateNeighbor(neighbor_entry)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/false); +} + +TEST_F(NeighborManagerTest, CreateNeighborSaiApiFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + + ASSERT_TRUE( + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key, kRouterInterfaceOid1)); + EXPECT_CALL(mock_sai_neighbor_, create_neighbor_entry(_, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateNeighbor(neighbor_entry)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, RemoveNeighborExistingNeighborEntry) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + sai_neighbor_entry_t neigh_entry; + neigh_entry.switch_id = gSwitchId; + copy(neigh_entry.ip_address, neighbor_entry.neighbor_id); + neigh_entry.rif_id = kRouterInterfaceOid2; + + EXPECT_CALL(mock_sai_neighbor_, + remove_neighbor_entry(Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neigh_entry)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveNeighbor(neighbor_entry.neighbor_key)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, RemoveNeighborNonExistingNeighborEntry) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, RemoveNeighbor(neighbor_entry.neighbor_key)); +} + +TEST_F(NeighborManagerTest, RemoveNeighborNotExistInMapper) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, RemoveNeighbor(neighbor_entry.neighbor_key)); +} + +TEST_F(NeighborManagerTest, RemoveNeighborNonZeroRefCount) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, RemoveNeighbor(neighbor_entry.neighbor_key)); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, RemoveNeighborSaiApiFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + EXPECT_CALL(mock_sai_neighbor_, remove_neighbor_entry(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveNeighbor(neighbor_entry.neighbor_key)); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, SetDstMacAddressModifyMacAddress) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + sai_neighbor_entry_t neigh_entry; + neigh_entry.switch_id = gSwitchId; + copy(neigh_entry.ip_address, neighbor_entry.neighbor_id); + neigh_entry.rif_id = kRouterInterfaceOid2; + + EXPECT_CALL(mock_sai_neighbor_, + set_neighbor_entry_attribute( + Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neigh_entry)), + Truly(std::bind(MatchNeighborSetAttributeList, std::placeholders::_1, kMacAddress1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetDstMacAddress(&neighbor_entry, kMacAddress1)); + EXPECT_EQ(neighbor_entry.dst_mac_address, kMacAddress1); +} + +TEST_F(NeighborManagerTest, SetDstMacAddressIdempotent) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + + // SAI API not being called makes the operation idempotent. + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetDstMacAddress(&neighbor_entry, kMacAddress2)); + EXPECT_EQ(neighbor_entry.dst_mac_address, kMacAddress2); +} + +TEST_F(NeighborManagerTest, SetDstMacAddressSaiApiFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + + EXPECT_CALL(mock_sai_neighbor_, set_neighbor_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, SetDstMacAddress(&neighbor_entry, kMacAddress1)); + EXPECT_EQ(neighbor_entry.dst_mac_address, kMacAddress2); +} + +TEST_F(NeighborManagerTest, ProcessAddRequestValidAppDbParams) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, app_db_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + + EXPECT_CALL( + mock_sai_neighbor_, + create_neighbor_entry( + Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neighbor_entry.neigh_entry)), Eq(2), + Truly(std::bind(MatchNeighborCreateAttributeList, std::placeholders::_1, app_db_entry.dst_mac_address)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + neighbor_entry.neigh_entry.rif_id)); + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(app_db_entry, neighbor_key)); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, ProcessAddRequesDstMacAddressNotSet) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = swss::MacAddress(), + .is_set_dst_mac = false}; + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(app_db_entry, neighbor_key)); + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/false); +} + +TEST_F(NeighborManagerTest, ProcessAddRequestInvalidRouterInterface) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(app_db_entry, neighbor_key)); + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/false); +} + +TEST_F(NeighborManagerTest, ProcessUpdateRequestSetDstMacAddress) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + + EXPECT_CALL(mock_sai_neighbor_, + set_neighbor_entry_attribute( + Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neighbor_entry.neigh_entry)), + Truly(std::bind(MatchNeighborSetAttributeList, std::placeholders::_1, kMacAddress2)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress2, + .is_set_dst_mac = true}; + + // Update neighbor entry present in the Manager. + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that neighbor entry present in the Manager has the updated + // MacAddress. + neighbor_entry.dst_mac_address = kMacAddress2; + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, ProcessUpdateRequestSetDstMacAddressFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + EXPECT_CALL(mock_sai_neighbor_, set_neighbor_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress2, + .is_set_dst_mac = true}; + + // Update neighbor entry present in the Manager. + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that neighbor entry present in the Manager has not changed. + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, ProcessDeleteRequestExistingNeighborEntry) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + + EXPECT_CALL(mock_sai_neighbor_, remove_neighbor_entry(Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, + neighbor_entry.neigh_entry)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRequest(neighbor_entry.neighbor_key)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, ProcessDeleteRequestNonExistingNeighborEntry) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateNeighborKey(kRouterInterfaceId1, kNeighborId1))); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryValidAttributes) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_dst_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), kMacAddress2.to_string()), + }; + + auto app_db_entry_or = + DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_intf_id, kRouterInterfaceId2); + EXPECT_EQ(app_db_entry.neighbor_id, kNeighborId2); + EXPECT_EQ(app_db_entry.dst_mac_address, kMacAddress2); + EXPECT_TRUE(app_db_entry.is_set_dst_mac); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidKeyFormat) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_dst_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), kMacAddress2.to_string()), + }; + + // Ensure that the following key is valid. It shall be modified to construct + // invalid key in rest of the test case. + std::string valid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id":"10.0.0.1"})"; + EXPECT_TRUE(DeserializeNeighborEntry(valid_key, attributes).ok()); + + // Invalid json format. + std::string invalid_key = R"({"match/router_interface_id:intf-3/4,match/neighbor_id:10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid json format. + invalid_key = R"([{"match/router_interface_id":"intf-3/4","match/neighbor_id":"10.0.0.1"}])"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid json format. + invalid_key = R"(["match/router_interface_id","intf-3/4","match/neighbor_id","10.0.0.1"])"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid json format. + invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id:10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid router interface id field name. + invalid_key = R"({"match/router_interface":"intf-3/4","match/neighbor_id":"10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid neighbor id field name. + invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor":"10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryMissingAction) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), kMacAddress2.to_string()), + }; + + auto app_db_entry_or = + DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_intf_id, kRouterInterfaceId2); + EXPECT_EQ(app_db_entry.neighbor_id, kNeighborId2); + EXPECT_EQ(app_db_entry.dst_mac_address, kMacAddress2); + EXPECT_TRUE(app_db_entry.is_set_dst_mac); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryNoAttributes) +{ + const std::vector attributes; + + auto app_db_entry_or = + DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_intf_id, kRouterInterfaceId2); + EXPECT_EQ(app_db_entry.neighbor_id, kNeighborId2); + EXPECT_EQ(app_db_entry.dst_mac_address, swss::MacAddress()); + EXPECT_FALSE(app_db_entry.is_set_dst_mac); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidField) +{ + const std::vector attributes = {swss::FieldValueTuple("invalid_field", "invalid_value")}; + + EXPECT_FALSE(DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes).ok()); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidIpAddrValue) +{ + const std::vector attributes; + + // Invalid IPv4 address. + std::string invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id":"10.0.0.x"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid IPv6 address. + invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id":"fe80::fe17:5f8g"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidMacAddrValue) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), "11:22:33:44:55")}; + + EXPECT_FALSE(DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryValidEntry) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + kRouterInterfaceOid1)); + + EXPECT_TRUE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryNonExistentRouterInterface) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + EXPECT_FALSE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryZeroMacAddress) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = swss::MacAddress(), + .is_set_dst_mac = true}; + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + kRouterInterfaceOid1)); + + EXPECT_FALSE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryMacAddressNotPresent) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = swss::MacAddress(), + .is_set_dst_mac = false}; + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + kRouterInterfaceOid1)); + + EXPECT_TRUE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, DrainValidAttributes) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + const std::string appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + + // Enqueue entry for create operation. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kMacAddress1.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_neighbor_, create_neighbor_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); + + // Enqueue entry for update operation. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kMacAddress2.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_neighbor_, set_neighbor_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + neighbor_entry.dst_mac_address = kMacAddress2; + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); + + // Enqueue entry for delete operation. + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, DEL_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_neighbor_, remove_neighbor_entry(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, DrainInvalidAppDbEntryKey) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + // create invalid neighbor key with router interface id as kRouterInterfaceId1 + // and neighbor id as kNeighborId1 + const std::string invalid_neighbor_key = R"({"match/router_interface_id:intf-3/4,match/neighbor_id:10.0.0.22"})"; + const std::string appl_db_key = + std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + invalid_neighbor_key; + + // Enqueue entry for create operation. + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + Drain(); + + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, DrainInvalidAppDbEntryAttributes) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + // Non-existent router interface id in neighbor key. + std::string appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId1); + + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + // Invalid destination mac address attribute. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kDstMac, swss::MacAddress().to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + Drain(); + + // Validate that first create operation did not create a neighbor entry. + P4NeighborEntry neighbor_entry1(kRouterInterfaceId2, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry1, /*check_ref_count=*/false); + + // Validate that second create operation did not create a neighbor entry. + P4NeighborEntry neighbor_entry2(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry2, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, DrainInvalidOperation) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + const std::string appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, "INVALID", attributes)); + Drain(); + + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} diff --git a/orchagent/p4orch/tests/next_hop_manager_test.cpp b/orchagent/p4orch/tests/next_hop_manager_test.cpp new file mode 100644 index 0000000000..a78310cc8d --- /dev/null +++ b/orchagent/p4orch/tests/next_hop_manager_test.cpp @@ -0,0 +1,709 @@ +#include "next_hop_manager.h" + +#include +#include + +#include +#include +#include + +#include "ipaddress.h" +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_next_hop.h" +#include "p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "p4orch_util.h" +#include "return_code.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +extern sai_object_id_t gSwitchId; +extern sai_next_hop_api_t *sai_next_hop_api; +extern MockSaiNextHop *mock_sai_next_hop; + +namespace +{ + +constexpr char *kNextHopId = "8"; +constexpr char *kNextHopP4AppDbKey = R"({"match/nexthop_id":"8"})"; +constexpr sai_object_id_t kNextHopOid = 1; +constexpr char *kRouterInterfaceId1 = "16"; +constexpr char *kRouterInterfaceId2 = "17"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 1; +constexpr sai_object_id_t kRouterInterfaceOid2 = 2; +constexpr char *kNeighborId1 = "10.0.0.1"; +constexpr char *kNeighborId2 = "fe80::21a:11ff:fe17:5f80"; + +// APP DB entries for Add and Update request. +const P4NextHopAppDbEntry kP4NextHopAppDbEntry1{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/kRouterInterfaceId1, + /*neighbor_id=*/swss::IpAddress(kNeighborId1), + /*is_set_router_interface_id=*/true, /*is_set_neighbor_id=*/true}; + +const P4NextHopAppDbEntry kP4NextHopAppDbEntry2{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/kRouterInterfaceId2, + /*neighbor_id=*/swss::IpAddress(kNeighborId2), + /*is_set_router_interface_id=*/true, /*is_set_neighbor_id=*/true}; + +// APP DB entries for Delete request. +const P4NextHopAppDbEntry kP4NextHopAppDbEntry3{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/"", + /*neighbor_id=*/swss::IpAddress(), + /*is_set_router_interface_id=*/false, /*is_set_neighbor_id=*/false}; + +std::unordered_map CreateAttributeListForNextHopObject( + const P4NextHopAppDbEntry &app_entry, const sai_object_id_t &rif_oid) +{ + std::unordered_map next_hop_attrs; + sai_attribute_t next_hop_attr; + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_IP; + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; + swss::copy(next_hop_attr.value.ipaddr, app_entry.neighbor_id); + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; + next_hop_attr.value.oid = rif_oid; + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + + return next_hop_attrs; +} + +// Verifies whether the attribute list is the same as expected for SAI next +// hop's create_next_hop(). +// Returns true if they match; otherwise, false. +bool MatchCreateNextHopArgAttrList(const sai_attribute_t *attr_list, + const std::unordered_map &expected_attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + + // Sanity check for expected_attr_list. + const auto end = expected_attr_list.end(); + if (expected_attr_list.size() != 3 || expected_attr_list.find(SAI_NEXT_HOP_ATTR_TYPE) == end || + expected_attr_list.find(SAI_NEXT_HOP_ATTR_IP) == end || + expected_attr_list.find(SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID) == end) + { + return false; + } + + for (int i = 0; i < 3; ++i) + { + switch (attr_list[i].id) + { + case SAI_NEXT_HOP_ATTR_TYPE: + if (attr_list[i].value.s32 != expected_attr_list.at(SAI_NEXT_HOP_ATTR_TYPE).s32) + return false; + break; + case SAI_NEXT_HOP_ATTR_IP: { + auto construct_ip_addr = [](const sai_ip_address_t &sai_ip_address) -> swss::IpAddress { + swss::ip_addr_t ipaddr; + if (sai_ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + ipaddr.family = AF_INET; + ipaddr.ip_addr.ipv4_addr = sai_ip_address.addr.ip4; + } + else + { + ipaddr.family = AF_INET6; + memcpy(&ipaddr.ip_addr.ipv6_addr, &sai_ip_address.addr.ip6, sizeof(ipaddr.ip_addr.ipv6_addr)); + } + + return swss::IpAddress(ipaddr); + }; + + auto ipaddr = construct_ip_addr(attr_list[i].value.ipaddr); + auto expected_ipaddr = construct_ip_addr(expected_attr_list.at(SAI_NEXT_HOP_ATTR_IP).ipaddr); + if (ipaddr != expected_ipaddr) + { + return false; + } + break; + } + case SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID).oid) + { + return false; + } + break; + default: + // Invalid attribute ID in next hop's attribute list. + return false; + } + } + + return true; +} + +} // namespace + +class NextHopManagerTest : public ::testing::Test +{ + protected: + NextHopManagerTest() : next_hop_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + // Set up mock stuff for SAI next hop API structure. + mock_sai_next_hop = &mock_sai_next_hop_; + sai_next_hop_api->create_next_hop = mock_create_next_hop; + sai_next_hop_api->remove_next_hop = mock_remove_next_hop; + sai_next_hop_api->set_next_hop_attribute = mock_set_next_hop_attribute; + sai_next_hop_api->get_next_hop_attribute = mock_get_next_hop_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + next_hop_manager_.enqueue(entry); + } + + void Drain() + { + next_hop_manager_.drain(); + } + + ReturnCode ProcessAddRequest(const P4NextHopAppDbEntry &app_db_entry) + { + return next_hop_manager_.processAddRequest(app_db_entry); + } + + ReturnCode ProcessUpdateRequest(const P4NextHopAppDbEntry &app_db_entry, P4NextHopEntry *next_hop_entry) + { + return next_hop_manager_.processUpdateRequest(app_db_entry, next_hop_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &next_hop_key) + { + return next_hop_manager_.processDeleteRequest(next_hop_key); + } + + P4NextHopEntry *GetNextHopEntry(const std::string &next_hop_key) + { + return next_hop_manager_.getNextHopEntry(next_hop_key); + } + + ReturnCodeOr DeserializeP4NextHopAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return next_hop_manager_.deserializeP4NextHopAppDbEntry(key, attributes); + } + + // Resolves the dependency of a next hop entry by adding depended router + // interface and neighbor into centralized mapper. + // Returns true on succuess. + bool ResolveNextHopEntryDependency(const P4NextHopAppDbEntry &app_db_entry, const sai_object_id_t &rif_oid); + + // Adds the next hop entry -- kP4NextHopAppDbEntry1, via next hop manager's + // ProcessAddRequest (). This function also takes care of all the dependencies + // of the next hop entry. + // Returns a valid pointer to next hop entry on success. + P4NextHopEntry *AddNextHopEntry1(); + + // Validates that a P4 App next hop entry is correctly added in next hop + // manager and centralized mapper. Returns true on success. + bool ValidateNextHopEntryAdd(const P4NextHopAppDbEntry &app_db_entry, const sai_object_id_t &expected_next_hop_oid); + + // Return true if the specified the object has the expected number of + // reference. + bool ValidateRefCnt(sai_object_type_t object_type, const std::string &key, uint32_t expected_ref_count) + { + uint32_t ref_count; + if (!p4_oid_mapper_.getRefCount(object_type, key, &ref_count)) + return false; + return ref_count == expected_ref_count; + } + + StrictMock mock_sai_next_hop_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + NextHopManager next_hop_manager_; +}; + +bool NextHopManagerTest::ResolveNextHopEntryDependency(const P4NextHopAppDbEntry &app_db_entry, + const sai_object_id_t &rif_oid) +{ + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + if (!p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, rif_oid)) + { + return false; + } + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_interface_id, app_db_entry.neighbor_id); + if (!p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) + { + return false; + } + return true; +} + +P4NextHopEntry *NextHopManagerTest::AddNextHopEntry1() +{ + if (!ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)) + { + return nullptr; + } + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, + create_next_hop( + ::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateNextHopArgAttrList, std::placeholders::_1, + CreateAttributeListForNextHopObject(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + return GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); +} + +bool NextHopManagerTest::ValidateNextHopEntryAdd(const P4NextHopAppDbEntry &app_db_entry, + const sai_object_id_t &expected_next_hop_oid) +{ + const auto *p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(app_db_entry.next_hop_id)); + if (p4_next_hop_entry == nullptr || p4_next_hop_entry->next_hop_id != app_db_entry.next_hop_id || + p4_next_hop_entry->router_interface_id != app_db_entry.router_interface_id || + p4_next_hop_entry->neighbor_id != app_db_entry.neighbor_id || + p4_next_hop_entry->next_hop_oid != expected_next_hop_oid) + return false; + + sai_object_id_t next_hop_oid; + if (!p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key, &next_hop_oid) || + next_hop_oid != expected_next_hop_oid) + { + return false; + } + + return true; +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldSucceedAddingNewNextHop) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + uint32_t original_rif_ref_count; + ASSERT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, &original_rif_ref_count)); + uint32_t original_neighbor_ref_count; + ASSERT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, &original_neighbor_ref_count)); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, + create_next_hop( + ::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateNextHopArgAttrList, std::placeholders::_1, + CreateAttributeListForNextHopObject(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, original_rif_ref_count + 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, original_neighbor_ref_count + 1)); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenNextHopExistInCentralMapper) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + ASSERT_TRUE(p4_oid_mapper_.setOID( + SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id), kNextHopOid)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRequest(kP4NextHopAppDbEntry1)); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingRifIsAbsentInCentralMapper) +{ + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + ASSERT_TRUE(p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingNeigherIsAbsentInCentralMapper) +{ + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, kRouterInterfaceOid1)); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenSaiCallFails) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, + create_next_hop( + ::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateNextHopArgAttrList, std::placeholders::_1, + CreateAttributeListForNextHopObject(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + // The add request failed for the next hop entry. + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldDoNoOpForDuplicateAddRequest) +{ + ASSERT_NE(AddNextHopEntry1(), nullptr); + + // Add the same next hop entry again. + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + // Adding the same next hop entry multiple times should have the same outcome + // as adding it once. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessUpdateRequestShouldFailAsItIsUnsupported) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRequest(kP4NextHopAppDbEntry2, p4_next_hop_entry)); + + // Expect that the update call will fail, so next hop entry's fields stay the + // same. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + + // Validate ref count stay the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldSucceedForExistingNextHop) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, remove_next_hop(Eq(p4_next_hop_entry->next_hop_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry has been deleted in both P4 next hop manager + // and centralized mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + EXPECT_EQ(p4_next_hop_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); + + // Validate ref count decrement. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 0)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 0)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailForNonExistingNextHop) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfNextHopEntryIsAbsentInCentralMapper) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key)); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry is not deleted in P4 next hop manager. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Validate ref count remains the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfNextHopEntryIsStillReferenced) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry is not deleted in either P4 next hop manager or + // central mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + ASSERT_NE(p4_next_hop_entry, nullptr); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key, 1)); + + // Validate ref count remains the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfSaiCallFails) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, remove_next_hop(Eq(p4_next_hop_entry->next_hop_oid))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry is not deleted in either P4 next hop manager or + // central mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + ASSERT_NE(p4_next_hop_entry, nullptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); + + // Validate ref count remains the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, GetNextHopEntryShouldReturnValidPointerForAddedNextHop) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, + create_next_hop( + ::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateNextHopArgAttrList, std::placeholders::_1, + CreateAttributeListForNextHopObject(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_NE(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, GetNextHopEntryShouldReturnNullPointerForNonexistingNextHop) +{ + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNextHopSetEntry) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kNeighborId), kNeighborId1)}; + + auto app_db_entry_or = DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.next_hop_id, kNextHopId); + EXPECT_TRUE(app_db_entry.is_set_router_interface_id); + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_TRUE(app_db_entry.is_set_neighbor_id); + EXPECT_EQ(app_db_entry.neighbor_id, swss::IpAddress(kNeighborId1)); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNextHopDeleteEntry) +{ + auto app_db_entry_or = DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, std::vector()); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.next_hop_id, kNextHopId); + EXPECT_FALSE(app_db_entry.is_set_router_interface_id); + EXPECT_FALSE(app_db_entry.is_set_neighbor_id); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerWhenFailToDeserializeNextHopId) +{ + // Incorrect format of P4 App next hop entry key + std::string key = R"({"nexthop":"8"})"; + std::vector attributes; + + EXPECT_FALSE(DeserializeP4NextHopAppDbEntry(key, attributes).ok()); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerForInvalidIpAddr) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kNeighborId), "0.0.0.0.0.0")}; // Invalid IP address. + + EXPECT_FALSE(DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, attributes).ok()); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerDueToUnexpectedField) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(p4orch::kRouterInterfaceId, kRouterInterfaceId1), + swss::FieldValueTuple("unexpected_field", "unexpected_value")}; + + EXPECT_FALSE(DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, attributes).ok()); +} + +TEST_F(NextHopManagerTest, DrainValidAppEntryShouldSucceed) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + EXPECT_CALL(mock_sai_next_hop_, create_next_hop(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + Drain(); + + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); +} + +TEST_F(NextHopManagerTest, DrainAppEntryWithInvalidOpShouldBeNoOp) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + "INVALID_OP", fvs); + + Enqueue(app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + + Drain(); + + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); +} + +TEST_F(NextHopManagerTest, DrainAppEntryWithInvalidFieldShouldBeNoOp) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}, + {"unexpected_field", "unexpected_value"}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + "INVALID_OP", fvs); + + Enqueue(app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + + Drain(); + + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); +} + +TEST_F(NextHopManagerTest, DrainUpdateRequestShouldBeUnsupported) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + Drain(); + + // Expect that the update call will fail, so next hop entry's fields stay the + // same. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + + // Validate ref count stay the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, DrainDeleteRequestShouldSucceedForExistingNextHop) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + std::vector fvs; + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + DEL_COMMAND, fvs); + EXPECT_CALL(mock_sai_next_hop_, remove_next_hop(Eq(p4_next_hop_entry->next_hop_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + Enqueue(app_db_entry); + Drain(); + + // Validate the next hop entry has been deleted in both P4 next hop manager + // and centralized mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + EXPECT_EQ(p4_next_hop_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); + + // Validate ref count decrement. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 0)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 0)); +} diff --git a/orchagent/p4orch/tests/p4oidmapper_test.cpp b/orchagent/p4orch/tests/p4oidmapper_test.cpp new file mode 100644 index 0000000000..131ee7aedb --- /dev/null +++ b/orchagent/p4orch/tests/p4oidmapper_test.cpp @@ -0,0 +1,122 @@ +#include "p4oidmapper.h" + +#include + +#include + +extern "C" +{ +#include "saitypes.h" +} + +namespace +{ + +constexpr char *kNextHopObject1 = "NextHop1"; +constexpr char *kNextHopObject2 = "NextHop2"; +constexpr char *kRouteObject1 = "Route1"; +constexpr char *kRouteObject2 = "Route2"; +constexpr sai_object_id_t kOid1 = 1; +constexpr sai_object_id_t kOid2 = 2; + +TEST(P4OidMapperTest, MapperTest) +{ + P4OidMapper mapper; + EXPECT_TRUE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1)); + EXPECT_TRUE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid2, + /*ref_count=*/100)); + EXPECT_TRUE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_TRUE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, + /*ref_count=*/200)); + + EXPECT_EQ(2, mapper.getNumEntries(SAI_OBJECT_TYPE_NEXT_HOP)); + EXPECT_EQ(2, mapper.getNumEntries(SAI_OBJECT_TYPE_ROUTE_ENTRY)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + sai_object_id_t oid; + EXPECT_TRUE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &oid)); + EXPECT_EQ(kOid1, oid); + EXPECT_TRUE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &oid)); + EXPECT_EQ(kOid2, oid); + + uint32_t ref_count; + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &ref_count)); + EXPECT_EQ(0, ref_count); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &ref_count)); + EXPECT_EQ(100, ref_count); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1, &ref_count)); + EXPECT_EQ(0, ref_count); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, &ref_count)); + EXPECT_EQ(200, ref_count); + EXPECT_TRUE(mapper.increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &ref_count)); + EXPECT_EQ(1, ref_count); + EXPECT_TRUE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, &ref_count)); + EXPECT_EQ(199, ref_count); + + EXPECT_TRUE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_EQ(1, mapper.getNumEntries(SAI_OBJECT_TYPE_NEXT_HOP)); + EXPECT_EQ(2, mapper.getNumEntries(SAI_OBJECT_TYPE_ROUTE_ENTRY)); + EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + mapper.eraseAllOIDs(SAI_OBJECT_TYPE_ROUTE_ENTRY); + EXPECT_EQ(1, mapper.getNumEntries(SAI_OBJECT_TYPE_NEXT_HOP)); + EXPECT_EQ(0, mapper.getNumEntries(SAI_OBJECT_TYPE_ROUTE_ENTRY)); + EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); +} + +TEST(P4OidMapperTest, ErrorTest) +{ + P4OidMapper mapper; + EXPECT_TRUE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1)); + EXPECT_TRUE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, std::numeric_limits::max())); + + // Set existing OID should fail. + EXPECT_FALSE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid2)); + EXPECT_FALSE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + // Get non-existing OID should fail. + sai_object_id_t oid; + EXPECT_FALSE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &oid)); + + // Get OID with nullptr should fail. + EXPECT_FALSE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, nullptr)); + + // Get non-existing ref count should fail. + uint32_t ref_count; + EXPECT_FALSE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1, &ref_count)); + + // Get ref count with nullptr should fail. + EXPECT_FALSE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, nullptr)); + + // Erase non-existing OID should fail. + EXPECT_FALSE(mapper.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + + // Erase OID with non-zero ref count should fail. + EXPECT_FALSE(mapper.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + // Increase max ref count should fail. + EXPECT_FALSE(mapper.increaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + // Increase non-existing ref count should fail. + EXPECT_FALSE(mapper.increaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + + // Decrease zero ref count should fail. + EXPECT_FALSE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + + // Decrease non-existing ref count should fail. + EXPECT_FALSE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); +} + +} // namespace diff --git a/orchagent/p4orch/tests/p4orch_util_test.cpp b/orchagent/p4orch/tests/p4orch_util_test.cpp new file mode 100644 index 0000000000..8c171b1e6e --- /dev/null +++ b/orchagent/p4orch/tests/p4orch_util_test.cpp @@ -0,0 +1,80 @@ +#include "p4orch_util.h" + +#include + +#include + +#include "ipprefix.h" +#include "swssnet.h" + +namespace +{ + +TEST(P4OrchUtilTest, KeyGeneratorTest) +{ + std::string intf_key = KeyGenerator::generateRouterInterfaceKey("intf-qe-3/7"); + EXPECT_EQ("router_interface_id=intf-qe-3/7", intf_key); + std::string neighbor_key = KeyGenerator::generateNeighborKey("intf-qe-3/7", swss::IpAddress("10.0.0.22")); + EXPECT_EQ("neighbor_id=10.0.0.22:router_interface_id=intf-qe-3/7", neighbor_key); + std::string nexthop_key = KeyGenerator::generateNextHopKey("ju1u32m1.atl11:qe-3/7"); + EXPECT_EQ("nexthop_id=ju1u32m1.atl11:qe-3/7", nexthop_key); + std::string wcmp_group_key = KeyGenerator::generateWcmpGroupKey("group-1"); + EXPECT_EQ("wcmp_group_id=group-1", wcmp_group_key); + std::string ipv4_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("10.11.12.0/24")); + EXPECT_EQ("ipv4_dst=10.11.12.0/24:vrf_id=b4-traffic", ipv4_route_key); + ipv4_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("0.0.0.0/0")); + EXPECT_EQ("ipv4_dst=0.0.0.0/0:vrf_id=b4-traffic", ipv4_route_key); + std::string ipv6_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("2001:db8:1::/32")); + EXPECT_EQ("ipv6_dst=2001:db8:1::/32:vrf_id=b4-traffic", ipv6_route_key); + ipv6_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("::/0")); + EXPECT_EQ("ipv6_dst=::/0:vrf_id=b4-traffic", ipv6_route_key); + + // Test with special characters. + neighbor_key = KeyGenerator::generateNeighborKey("::===::", swss::IpAddress("::1")); + EXPECT_EQ("neighbor_id=::1:router_interface_id=::===::", neighbor_key); + + std::map match_fvs; + match_fvs["ether_type"] = "0x0800"; + match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53 & fdf8:f53b:82e4::53"; + auto acl_rule_key = KeyGenerator::generateAclRuleKey(match_fvs, "15"); + EXPECT_EQ("match/ether_type=0x0800:match/" + "ipv6_dst=fdf8:f53b:82e4::53 & fdf8:f53b:82e4::53:priority=15", + acl_rule_key); +} + +TEST(P4OrchUtilTest, ParseP4RTKeyTest) +{ + std::string table; + std::string key; + parseP4RTKey("table:key", &table, &key); + EXPECT_EQ("table", table); + EXPECT_EQ("key", key); + parseP4RTKey("|||::::", &table, &key); + EXPECT_EQ("|||", table); + EXPECT_EQ(":::", key); + parseP4RTKey("invalid", &table, &key); + EXPECT_TRUE(table.empty()); + EXPECT_TRUE(key.empty()); +} + +TEST(P4OrchUtilTest, PrependMatchFieldShouldSucceed) +{ + EXPECT_EQ(prependMatchField("str"), "match/str"); +} + +TEST(P4OrchUtilTest, PrependParamFieldShouldSucceed) +{ + EXPECT_EQ(prependParamField("str"), "param/str"); +} + +TEST(P4OrchUtilTest, QuotedVarTest) +{ + std::string foo("Hello World"); + std::string bar("a string has 'quote'"); + EXPECT_EQ(QuotedVar(foo), "'Hello World'"); + EXPECT_EQ(QuotedVar(foo.c_str()), "'Hello World'"); + EXPECT_EQ(QuotedVar(bar), "'a string has \\\'quote\\\''"); + EXPECT_EQ(QuotedVar(bar.c_str()), "'a string has \\\'quote\\\''"); +} + +} // namespace diff --git a/orchagent/p4orch/tests/return_code_test.cpp b/orchagent/p4orch/tests/return_code_test.cpp new file mode 100644 index 0000000000..7a866827d7 --- /dev/null +++ b/orchagent/p4orch/tests/return_code_test.cpp @@ -0,0 +1,176 @@ +#include "return_code.h" + +#include + +#include +#include + +extern "C" +{ +#include "sai.h" +} + +namespace +{ + +TEST(ReturnCodeTest, SuccessCode) +{ + auto return_code = ReturnCode(); + EXPECT_TRUE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code.code()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.codeStr()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.message()); + EXPECT_EQ("SWSS_RC_SUCCESS:SWSS_RC_SUCCESS", return_code.toString()); + EXPECT_FALSE(return_code.isSai()); +} + +TEST(ReturnCodeTest, SuccessSaiCode) +{ + auto return_code = ReturnCode(SAI_STATUS_SUCCESS); + EXPECT_TRUE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code.code()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.codeStr()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.message()); + EXPECT_EQ("SWSS_RC_SUCCESS:SWSS_RC_SUCCESS", return_code.toString()); + EXPECT_TRUE(return_code.isSai()); +} + +TEST(ReturnCodeTest, ErrorStatusCode) +{ + auto return_code = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid arguments."; + EXPECT_FALSE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code.code()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code.codeStr()); + EXPECT_EQ("Invalid arguments.", return_code.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:Invalid arguments.", return_code.toString()); + EXPECT_FALSE(return_code.isSai()); +} + +TEST(ReturnCodeTest, ErrorSaiCode) +{ + sai_status_t sai_statue = SAI_STATUS_NOT_IMPLEMENTED; + auto return_code = ReturnCode(sai_statue) << "SAI error: " << sai_statue; + EXPECT_FALSE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, return_code.code()); + EXPECT_EQ("SWSS_RC_UNIMPLEMENTED", return_code.codeStr()); + EXPECT_EQ("SAI error: -15", return_code.message()); + EXPECT_EQ("SWSS_RC_UNIMPLEMENTED:SAI error: -15", return_code.toString()); + EXPECT_TRUE(return_code.isSai()); +} + +TEST(ReturnCodeTest, ReturnCodeCopy) +{ + ReturnCode return_code_1 = ReturnCode() << "SUCCESS"; + ReturnCode return_code_2 = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid arguments."; + return_code_1 = return_code_2; + EXPECT_FALSE(return_code_1.ok()); + EXPECT_EQ(return_code_1, StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(return_code_1.code(), StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code_1.codeStr()); + EXPECT_EQ("Invalid arguments.", return_code_1.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:Invalid arguments.", return_code_1.toString()); + EXPECT_EQ(return_code_1, return_code_2); + EXPECT_FALSE(return_code_1 != return_code_2); +} + +TEST(ReturnCodeTest, PrependStringInMsg) +{ + auto return_code = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Detailed reasons."; + return_code.prepend("General statement - "); + EXPECT_FALSE(return_code.ok()); + EXPECT_FALSE(StatusCode::SWSS_RC_INVALID_PARAM != return_code); + EXPECT_FALSE(return_code != StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code.code()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code.codeStr()); + EXPECT_EQ("General statement - Detailed reasons.", return_code.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:General statement - Detailed reasons.", return_code.toString()); +} + +TEST(ReturnCodeTest, CopyAndAppendStringInMsg) +{ + ReturnCode return_code; + return_code = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Detailed reasons."; + EXPECT_EQ("Detailed reasons.", return_code.message()); + return_code << " More details."; + EXPECT_FALSE(return_code.ok()); + EXPECT_FALSE(StatusCode::SWSS_RC_INVALID_PARAM != return_code); + EXPECT_FALSE(return_code != StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code.code()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code.codeStr()); + EXPECT_EQ("Detailed reasons. More details.", return_code.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:Detailed reasons. More details.", return_code.toString()); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasInt) +{ + ReturnCodeOr return_code_or = 42; + EXPECT_TRUE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code_or.status()); + EXPECT_EQ(42, return_code_or.value()); + EXPECT_EQ(42, *return_code_or); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasCopyableObject) +{ + class TestObj + { + public: + TestObj(int value) : value_(value) + { + } + int GetValue() + { + return value_; + } + + private: + int value_ = 0; + }; + + ReturnCodeOr return_code_or = TestObj(42); + EXPECT_TRUE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code_or.status()); + EXPECT_EQ(42, return_code_or.value().GetValue()); + EXPECT_EQ(42, (*return_code_or).GetValue()); + EXPECT_EQ(42, return_code_or->GetValue()); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasMoveableObject) +{ + class TestObj + { + public: + TestObj(int value) : value_(value) + { + } + int GetValue() + { + return value_; + } + + private: + int value_ = 0; + }; + + ReturnCodeOr> return_code_or = std::make_unique(42); + EXPECT_TRUE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code_or.status()); + EXPECT_EQ(42, return_code_or.value()->GetValue()); + EXPECT_EQ(42, (*return_code_or)->GetValue()); + EXPECT_EQ(42, return_code_or->get()->GetValue()); + std::unique_ptr test_obj = std::move(*return_code_or); + EXPECT_EQ(42, test_obj->GetValue()); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasReturnCode) +{ + ReturnCodeOr return_code_or = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_FALSE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code_or.status()); +} + +} // namespace diff --git a/orchagent/p4orch/tests/route_manager_test.cpp b/orchagent/p4orch/tests/route_manager_test.cpp new file mode 100644 index 0000000000..de1238761b --- /dev/null +++ b/orchagent/p4orch/tests/route_manager_test.cpp @@ -0,0 +1,1558 @@ +#include "route_manager.h" + +#include +#include + +#include +#include +#include + +#include "ipprefix.h" +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_route.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "return_code.h" +#include "swssnet.h" +#include "vrforch.h" + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; +extern sai_object_id_t gVrfOid; +extern char *gVrfName; +extern sai_route_api_t *sai_route_api; +extern VRFOrch *gVrfOrch; + +namespace +{ + +constexpr char *kIpv4Prefix = "10.11.12.0/24"; +constexpr char *kIpv6Prefix = "2001:db8:1::/32"; +constexpr char *kNexthopId1 = "ju1u32m1.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid1 = 1; +constexpr char *kNexthopId2 = "ju1u32m2.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid2 = 2; +constexpr char *kWcmpGroup1 = "wcmp-group-1"; +constexpr sai_object_id_t kWcmpGroupOid1 = 3; +constexpr char *kWcmpGroup2 = "wcmp-group-2"; +constexpr sai_object_id_t kWcmpGroupOid2 = 4; + +// Returns true if the two prefixes are equal. False otherwise. +// Arguments must be non-nullptr. +bool PrefixCmp(const sai_ip_prefix_t *x, const sai_ip_prefix_t *y) +{ + if (x->addr_family != y->addr_family) + { + return false; + } + if (x->addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + return memcmp(&x->addr.ip4, &y->addr.ip4, sizeof(sai_ip4_t)) == 0 && + memcmp(&x->mask.ip4, &y->mask.ip4, sizeof(sai_ip4_t)) == 0; + } + return memcmp(&x->addr.ip6, &y->addr.ip6, sizeof(sai_ip6_t)) == 0 && + memcmp(&x->mask.ip6, &y->mask.ip6, sizeof(sai_ip6_t)) == 0; +} + +// Matches the sai_route_entry_t argument. +bool MatchSaiRouteEntry(const sai_ip_prefix_t &expected_prefix, const sai_route_entry_t *route_entry, + const sai_object_id_t expected_vrf_oid) +{ + if (route_entry == nullptr) + { + return false; + } + if (route_entry->vr_id != expected_vrf_oid) + { + return false; + } + if (route_entry->switch_id != gSwitchId) + { + return false; + } + if (!PrefixCmp(&route_entry->destination, &expected_prefix)) + { + return false; + } + return true; +} + +// Matches the action type sai_attribute_t argument. +bool MatchSaiAttributeAction(sai_packet_action_t expected_action, const sai_attribute_t *attr) +{ + if (attr == nullptr) + { + return false; + } + if (attr->id != SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION) + { + return false; + } + if (attr->value.s32 != expected_action) + { + return false; + } + return true; +} + +// Matches the nexthop ID type sai_attribute_t argument. +bool MatchSaiAttributeNexthopId(sai_object_id_t expected_oid, const sai_attribute_t *attr) +{ + if (attr == nullptr) + { + return false; + } + if (attr->id != SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID) + { + return false; + } + if (attr->value.oid != expected_oid) + { + return false; + } + return true; +} + +} // namespace + +class RouteManagerTest : public ::testing::Test +{ + protected: + RouteManagerTest() : route_manager_(&p4_oid_mapper_, gVrfOrch, &publisher_) + { + } + + void SetUp() override + { + mock_sai_route = &mock_sai_route_; + sai_route_api->create_route_entry = create_route_entry; + sai_route_api->remove_route_entry = remove_route_entry; + sai_route_api->set_route_entry_attribute = set_route_entry_attribute; + sai_route_api->get_route_entry_attribute = get_route_entry_attribute; + sai_route_api->create_route_entries = create_route_entries; + sai_route_api->remove_route_entries = remove_route_entries; + sai_route_api->set_route_entries_attribute = set_route_entries_attribute; + sai_route_api->get_route_entries_attribute = get_route_entries_attribute; + } + + bool MergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret) + { + return route_manager_.mergeRouteEntry(dest, src, ret); + } + + ReturnCodeOr DeserializeRouteEntry(const std::string &key, + const std::vector &attributes, + const std::string &table_name) + { + return route_manager_.deserializeRouteEntry(key, attributes, table_name); + } + + P4RouteEntry *GetRouteEntry(const std::string &route_entry_key) + { + return route_manager_.getRouteEntry(route_entry_key); + } + + ReturnCode ValidateRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.validateRouteEntry(route_entry); + } + + ReturnCode ValidateSetRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.validateSetRouteEntry(route_entry); + } + + ReturnCode ValidateDelRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.validateDelRouteEntry(route_entry); + } + + ReturnCode CreateRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.createRouteEntry(route_entry); + } + + ReturnCode UpdateRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.updateRouteEntry(route_entry); + } + + ReturnCode DeleteRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.deleteRouteEntry(route_entry); + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + route_manager_.enqueue(entry); + } + + void Drain() + { + route_manager_.drain(); + } + + // Sets up a nexthop route entry for test. + void SetupNexthopIdRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix, + const std::string &nexthop_id, sai_object_id_t nexthop_oid) + { + P4RouteEntry route_entry = {}; + route_entry.vrf_id = vrf_id; + route_entry.route_prefix = route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = nexthop_id; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + nexthop_oid); + + EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + } + + // Sets up a wcmp route entry for test. + void SetupWcmpGroupRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix, + const std::string &wcmp_group_id, sai_object_id_t wcmp_group_oid) + { + P4RouteEntry route_entry = {}; + route_entry.vrf_id = vrf_id; + route_entry.route_prefix = route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = wcmp_group_id; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), wcmp_group_oid); + + EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + } + + // Verifies the two given route entries are identical. + void VerifyRouteEntriesEq(const P4RouteEntry &x, const P4RouteEntry &y) + { + EXPECT_EQ(x.route_entry_key, y.route_entry_key); + EXPECT_EQ(x.vrf_id, y.vrf_id); + EXPECT_EQ(x.route_prefix, y.route_prefix); + EXPECT_EQ(x.action, y.action); + EXPECT_EQ(x.nexthop_id, y.nexthop_id); + EXPECT_EQ(x.wcmp_group, y.wcmp_group); + EXPECT_EQ(x.sai_route_entry.vr_id, y.sai_route_entry.vr_id); + EXPECT_EQ(x.sai_route_entry.switch_id, y.sai_route_entry.switch_id); + EXPECT_TRUE(PrefixCmp(&x.sai_route_entry.destination, &y.sai_route_entry.destination)); + } + + // Verifies the given route entry exists and matches. + void VerifyRouteEntry(const P4RouteEntry &route_entry, const sai_ip_prefix_t &sai_route_prefix, + const sai_object_id_t vrf_oid) + { + auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); + P4RouteEntry expect_entry = route_entry; + expect_entry.sai_route_entry.vr_id = vrf_oid; + expect_entry.sai_route_entry.switch_id = gSwitchId; + expect_entry.sai_route_entry.destination = sai_route_prefix; + VerifyRouteEntriesEq(expect_entry, *route_entry_ptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + } + + StrictMock mock_sai_route_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + RouteManager route_manager_; +}; + +TEST_F(RouteManagerTest, MergeRouteEntryWithNexthopIdActionDestTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry dest = {}; + dest.vrf_id = gVrfName; + dest.route_prefix = swss_ipv4_route_prefix; + dest.action = p4orch::kSetNexthopId; + dest.nexthop_id = kNexthopId1; + dest.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + dest.sai_route_entry.vr_id = gVrfOid; + dest.sai_route_entry.switch_id = gSwitchId; + copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); + + // Source is identical to destination. + P4RouteEntry src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kSetNexthopId; + src.nexthop_id = kNexthopId1; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + P4RouteEntry ret = {}; + EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); + VerifyRouteEntriesEq(dest, ret); + + // Source has different nexthop ID. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.nexthop_id = kNexthopId2; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + P4RouteEntry expect_entry = dest; + expect_entry.nexthop_id = kNexthopId2; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has wcmp group action and dest has nexhop ID action. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kSetWcmpGroupId; + src.wcmp_group = kWcmpGroup1; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = ""; + expect_entry.action = p4orch::kSetWcmpGroupId; + expect_entry.wcmp_group = kWcmpGroup1; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has drop action and dest has nexhop ID action. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kDrop; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = ""; + expect_entry.action = p4orch::kDrop; + VerifyRouteEntriesEq(expect_entry, ret); +} + +TEST_F(RouteManagerTest, MergeRouteEntryWithWcmpGroupActionDestTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry dest = {}; + dest.vrf_id = gVrfName; + dest.route_prefix = swss_ipv4_route_prefix; + dest.action = p4orch::kSetWcmpGroupId; + dest.wcmp_group = kWcmpGroup1; + dest.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + dest.sai_route_entry.vr_id = gVrfOid; + dest.sai_route_entry.switch_id = gSwitchId; + copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); + + // Source is identical to destination. + P4RouteEntry src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kSetWcmpGroupId; + src.wcmp_group = kWcmpGroup1; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + P4RouteEntry ret = {}; + EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); + VerifyRouteEntriesEq(dest, ret); + + // Source has different wcmp group. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.wcmp_group = kWcmpGroup2; + src.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + P4RouteEntry expect_entry = dest; + expect_entry.wcmp_group = kWcmpGroup2; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has nexthop ID action and dest has wcmp group action. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kSetNexthopId; + src.nexthop_id = kNexthopId1; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.wcmp_group = ""; + expect_entry.action = p4orch::kSetNexthopId; + expect_entry.nexthop_id = kNexthopId1; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has drop action and dest has wcmp group action. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kDrop; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.wcmp_group = ""; + expect_entry.action = p4orch::kDrop; + VerifyRouteEntriesEq(expect_entry, ret); +} + +TEST_F(RouteManagerTest, MergeRouteEntryWithDropActionDestTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry dest = {}; + dest.vrf_id = gVrfName; + dest.route_prefix = swss_ipv4_route_prefix; + dest.action = p4orch::kDrop; + dest.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + dest.sai_route_entry.vr_id = gVrfOid; + dest.sai_route_entry.switch_id = gSwitchId; + copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); + + // Source is identical to destination. + P4RouteEntry src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kDrop; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + P4RouteEntry ret = {}; + EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); + VerifyRouteEntriesEq(dest, ret); + + // Source has nexthop ID action and dest has drop action. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kSetNexthopId; + src.nexthop_id = kNexthopId1; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + P4RouteEntry expect_entry = dest; + expect_entry.action = p4orch::kSetNexthopId; + expect_entry.nexthop_id = kNexthopId1; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has wcmp group action and dest has drop action. + src = {}; + src.vrf_id = gVrfName; + src.route_prefix = swss_ipv4_route_prefix; + src.action = p4orch::kSetWcmpGroupId; + src.wcmp_group = kWcmpGroup1; + src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.action = p4orch::kSetWcmpGroupId; + expect_entry.wcmp_group = kWcmpGroup1; + VerifyRouteEntriesEq(expect_entry, ret); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithNexthopIdActionTest) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv4_dst":"10.11.12.0/24"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + P4RouteEntry expect_entry = {}; + expect_entry.vrf_id = "b4-traffic"; + expect_entry.route_prefix = swss::IpPrefix("10.11.12.0/24"); + expect_entry.action = p4orch::kSetNexthopId; + expect_entry.nexthop_id = kNexthopId1; + expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithWcmpGroupActionTest) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv4_dst":"10.11.12.0/24"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetWcmpGroupId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kWcmpGroupId), kWcmpGroup1}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + P4RouteEntry expect_entry = {}; + expect_entry.vrf_id = "b4-traffic"; + expect_entry.route_prefix = swss::IpPrefix("10.11.12.0/24"); + expect_entry.action = p4orch::kSetWcmpGroupId; + expect_entry.wcmp_group = kWcmpGroup1; + expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithDropActionTest) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv6_dst":"2001:db8:1::/32"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + P4RouteEntry expect_entry = {}; + expect_entry.vrf_id = "b4-traffic"; + expect_entry.route_prefix = swss::IpPrefix("2001:db8:1::/32"); + expect_entry.action = p4orch::kDrop; + expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithInvalidKeyShouldFail) +{ + std::string key = "{{{{{{{{{{{{"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_FALSE(route_entry_or.ok()); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithInvalidFieldShouldFail) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv6_dst":"2001:db8:1::/32"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{"invalid", "invalid"}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_FALSE(route_entry_or.ok()); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithInvalidRouteShouldFail) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv6_dst":"invalid"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_FALSE(route_entry_or.ok()); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithoutIpv4WildcardLpmMatchShouldSucceed) +{ + std::string key = R"({"match/vrf_id":"b4-traffic"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + P4RouteEntry expect_entry = {}; + expect_entry.vrf_id = "b4-traffic"; + expect_entry.route_prefix = swss::IpPrefix("0.0.0.0/0"); + expect_entry.action = p4orch::kDrop; + expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithoutIpv6WildcardLpmMatchShouldSucceed) +{ + std::string key = R"({"match/vrf_id":"b4-traffic"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + P4RouteEntry expect_entry = {}; + expect_entry.vrf_id = "b4-traffic"; + expect_entry.route_prefix = swss::IpPrefix("::/0"); + expect_entry.action = p4orch::kDrop; + expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + + // ValidateRouteEntry should fail when the nexthop does not exist in + // centralized map. + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryWcmpGroupActionWithInvalidWcmpGroupShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryWcmpGroupActionWithValidWcmpGroupShouldSucceed) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryDoesNotExistInManagerShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryExistsInMapperDoesNotExistInManagerShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryExistsInManagerDoesNotExistInMapperShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryNexthopIdActionWithoutNexthopIdShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryNexthopIdActionWithWcmpGroupShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryWcmpGroupActionWithoutWcmpGroupShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryWcmpGroupActionWithNexthopIdShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.nexthop_id = kNexthopId1; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryDropActionWithNexthopIdShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryDropActionWithWcmpGroupShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryInvalidActionShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = "invalid"; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntrySucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateSetRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryDoesNotExistInManagerShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateDelRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryDoesNotExistInMapperShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ValidateDelRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryHasActionShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateDelRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryHasNexthopIdShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateDelRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryHasWcmpGroupShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateDelRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntrySucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateDelRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, CreateRouteEntryWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup1; + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, CreateNexthopIdIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + + EXPECT_CALL( + mock_sai_route_, + create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, CreateNexthopIdIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv6_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + + EXPECT_CALL( + mock_sai_route_, + create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, std::placeholders::_1, gVrfOid)), + Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, CreateDropIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + EXPECT_CALL(mock_sai_route_, + create_route_entry( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), Eq(1), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_DROP, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); +} + +TEST_F(RouteManagerTest, CreateDropIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv6_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + EXPECT_CALL(mock_sai_route_, + create_route_entry( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, std::placeholders::_1, gVrfOid)), Eq(1), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_DROP, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); +} + +TEST_F(RouteManagerTest, CreateWcmpIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + + EXPECT_CALL( + mock_sai_route_, + create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, CreateWcmpIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv6_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + + EXPECT_CALL( + mock_sai_route_, + create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, std::placeholders::_1, gVrfOid)), + Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryWcmpWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid2); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryWcmpNotExistInMapperShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, UpdateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, UpdateRouteFromSetWcmpToSetNextHopSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid2, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromSetNexthopIdToSetWcmpSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid2); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid2, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + route_entry.action = p4orch::kSetWcmpGroupId; + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryNexthopIdWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryNexthopIdNotExistInMapperShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, UpdateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryDropWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, UpdateRouteWithDifferentNexthopIdsSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid2, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + route_entry.action = p4orch::kSetNexthopId; + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdToDropSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_DROP, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeNexthopId, SAI_NULL_OBJECT_ID, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromDropToNexthopIdSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid2, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteWithDifferentWcmpGroupsSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid2); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid2, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, + set_route_entry_attribute( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), + Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + route_entry.action = p4orch::kSetWcmpGroupId; + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateNexthopIdRouteWithNoChangeSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryRecoverFailureShouldRaiseCriticalState) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kDrop; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, DeleteRouteEntryWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + EXPECT_CALL(mock_sai_route_, remove_route_entry(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, DeleteRouteEntry(route_entry)); +} + +TEST_F(RouteManagerTest, DeleteIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + EXPECT_CALL(mock_sai_route_, remove_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, + std::placeholders::_1, gVrfOid)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, DeleteRouteEntry(route_entry)); + auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, DeleteIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv6_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + EXPECT_CALL(mock_sai_route_, remove_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, + std::placeholders::_1, gVrfOid)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + P4RouteEntry route_entry = {}; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv6_route_prefix; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, DeleteRouteEntry(route_entry)); + auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, RouteCreateAndUpdateInDrainSucceeds) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = gVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId2}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).Times(2).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + + Drain(); + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + P4RouteEntry route_entry; + route_entry.vrf_id = gVrfName; + route_entry.route_prefix = swss_ipv4_route_prefix; + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, RouteCreateAndDeleteInDrainSucceeds) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = gVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), DEL_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_route_, remove_route_entry(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + std::string key = KeyGenerator::generateRouteKey(gVrfName, swss::IpPrefix(kIpv4Prefix)); + auto *route_entry_ptr = GetRouteEntry(key); + EXPECT_EQ(nullptr, route_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, key)); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, RouteCreateInDrainSucceedsWhenVrfIsEmpty) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + const std::string kDefaultVrfName = ""; // Default Vrf + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = kDefaultVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + + EXPECT_CALL( + mock_sai_route_, + create_route_entry( + Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVirtualRouterId)), Eq(1), + Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + Drain(); + std::string key = KeyGenerator::generateRouteKey(kDefaultVrfName, swss::IpPrefix(kIpv4Prefix)); + auto *route_entry_ptr = GetRouteEntry(key); + EXPECT_NE(nullptr, route_entry_ptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, key)); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + Enqueue( + swss::KeyOpFieldsValuesTuple(kKeyPrefix + "{{{{{{{{{{{{", SET_COMMAND, std::vector{})); + Drain(); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryInDrainFailsWhenVrfDoesNotExist) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = "Invalid-Vrf"; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + // Vrf does not exist. + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + Drain(); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryInDrainFailsWhenNexthopDoesNotExist) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = gVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + // Nexthop ID does not exist. + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + Drain(); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = gVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + // No nexthop ID with kSetNexthopId action. + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + Drain(); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = gVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + // Fields are non-empty for DEl. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), DEL_COMMAND, attributes)); + Drain(); +} + +TEST_F(RouteManagerTest, InvalidCommandInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + nlohmann::json j; + j[prependMatchField(p4orch::kVrfId)] = gVrfName; + j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), "INVALID_COMMAND", attributes)); + Drain(); +} diff --git a/orchagent/p4orch/tests/router_interface_manager_test.cpp b/orchagent/p4orch/tests/router_interface_manager_test.cpp new file mode 100644 index 0000000000..661fe33efa --- /dev/null +++ b/orchagent/p4orch/tests/router_interface_manager_test.cpp @@ -0,0 +1,843 @@ +#include "router_interface_manager.h" + +#include +#include + +#include +#include + +#include "mock_response_publisher.h" +#include "mock_sai_router_interface.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "return_code.h" +#include "swssnet.h" + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +extern PortsOrch *gPortsOrch; + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; +extern sai_router_interface_api_t *sai_router_intfs_api; + +namespace +{ + +constexpr char *kPortName1 = "Ethernet1"; +constexpr sai_object_id_t kPortOid1 = 0x112233; +constexpr uint32_t kMtu1 = 1500; + +constexpr char *kPortName2 = "Ethernet2"; +constexpr sai_object_id_t kPortOid2 = 0x1fed3; +constexpr uint32_t kMtu2 = 4500; + +constexpr char *kRouterInterfaceId1 = "intf-3/4"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 0x295100; +const swss::MacAddress kMacAddress1("00:01:02:03:04:05"); + +constexpr char *kRouterInterfaceId2 = "Ethernet20"; +constexpr sai_object_id_t kRouterInterfaceOid2 = 0x51411; +const swss::MacAddress kMacAddress2("00:ff:ee:dd:cc:bb"); + +const swss::MacAddress kZeroMacAddress("00:00:00:00:00:00"); + +constexpr char *kRouterIntfAppDbKey = R"({"match/router_interface_id":"intf-3/4"})"; + +std::unordered_map CreateRouterInterfaceAttributeList( + const sai_object_id_t &virtual_router_oid, const swss::MacAddress mac_address, const sai_object_id_t &port_oid, + const uint32_t mtu) +{ + std::unordered_map attr_list; + sai_attribute_value_t attr_value; + + attr_value.oid = virtual_router_oid; + attr_list[SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID] = attr_value; + + if (mac_address != kZeroMacAddress) + { + memcpy(attr_value.mac, mac_address.getMac(), sizeof(sai_mac_t)); + attr_list[SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS] = attr_value; + } + + attr_value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attr_list[SAI_ROUTER_INTERFACE_ATTR_TYPE] = attr_value; + + attr_value.oid = port_oid; + attr_list[SAI_ROUTER_INTERFACE_ATTR_PORT_ID] = attr_value; + + attr_value.u32 = mtu; + attr_list[SAI_ROUTER_INTERFACE_ATTR_MTU] = attr_value; + + return attr_list; +} + +bool MatchCreateRouterInterfaceAttributeList( + const sai_attribute_t *attr_list, + const std::unordered_map &expected_attr_list) +{ + if (attr_list == nullptr) + return false; + + int matched_attr_num = 0; + const int attr_list_length = (int)expected_attr_list.size(); + for (int i = 0; i < attr_list_length; i++) + { + switch (attr_list[i].id) + { + case SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID).oid) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS: + if (memcmp(attr_list[i].value.mac, expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS).mac, + sizeof(sai_mac_t))) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_TYPE: + if (attr_list[i].value.s32 != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_TYPE).s32) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_PORT_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_PORT_ID).oid) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_MTU: + if (attr_list[i].value.u32 != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_MTU).u32) + { + return false; + } + matched_attr_num++; + break; + + default: + // Unexpected attribute present in attribute list + return false; + } + } + + return (matched_attr_num == attr_list_length); +} + +} // namespace + +class RouterInterfaceManagerTest : public ::testing::Test +{ + protected: + RouterInterfaceManagerTest() : router_intf_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + mock_sai_router_intf = &mock_sai_router_intf_; + sai_router_intfs_api->create_router_interface = mock_create_router_interface; + sai_router_intfs_api->remove_router_interface = mock_remove_router_interface; + sai_router_intfs_api->set_router_interface_attribute = mock_set_router_interface_attribute; + sai_router_intfs_api->get_router_interface_attribute = mock_get_router_interface_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + router_intf_manager_.enqueue(entry); + } + + void Drain() + { + router_intf_manager_.drain(); + } + + ReturnCodeOr DeserializeRouterIntfEntry( + const std::string &key, const std::vector &attributes) + { + return router_intf_manager_.deserializeRouterIntfEntry(key, attributes); + } + + ReturnCode CreateRouterInterface(const std::string &router_intf_key, P4RouterInterfaceEntry &router_intf_entry) + { + return router_intf_manager_.createRouterInterface(router_intf_key, router_intf_entry); + } + + ReturnCode RemoveRouterInterface(const std::string &router_intf_key) + { + return router_intf_manager_.removeRouterInterface(router_intf_key); + } + + ReturnCode SetSourceMacAddress(P4RouterInterfaceEntry *router_intf_entry, const swss::MacAddress &mac_address) + { + return router_intf_manager_.setSourceMacAddress(router_intf_entry, mac_address); + } + + ReturnCode ProcessAddRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, const std::string &router_intf_key) + { + return router_intf_manager_.processAddRequest(app_db_entry, router_intf_key); + } + + ReturnCode ProcessUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + P4RouterInterfaceEntry *router_intf_entry) + { + return router_intf_manager_.processUpdateRequest(app_db_entry, router_intf_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &router_intf_key) + { + return router_intf_manager_.processDeleteRequest(router_intf_key); + } + + P4RouterInterfaceEntry *GetRouterInterfaceEntry(const std::string &router_intf_key) + { + return router_intf_manager_.getRouterInterfaceEntry(router_intf_key); + } + + void ValidateRouterInterfaceEntry(const P4RouterInterfaceEntry &expected_entry) + { + const std::string router_intf_key = + KeyGenerator::generateRouterInterfaceKey(expected_entry.router_interface_id); + auto router_intf_entry = GetRouterInterfaceEntry(router_intf_key); + + EXPECT_NE(nullptr, router_intf_entry); + EXPECT_EQ(expected_entry.router_interface_id, router_intf_entry->router_interface_id); + EXPECT_EQ(expected_entry.port_name, router_intf_entry->port_name); + EXPECT_EQ(expected_entry.src_mac_address, router_intf_entry->src_mac_address); + EXPECT_EQ(expected_entry.router_interface_oid, router_intf_entry->router_interface_oid); + + sai_object_id_t p4_mapper_oid; + ASSERT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &p4_mapper_oid)); + EXPECT_EQ(expected_entry.router_interface_oid, p4_mapper_oid); + } + + void ValidateRouterInterfaceEntryNotPresent(const std::string router_interface_id) + { + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_interface_id); + auto current_entry = GetRouterInterfaceEntry(router_intf_key); + EXPECT_EQ(current_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)); + } + + void AddRouterInterfaceEntry(P4RouterInterfaceEntry &router_intf_entry, const sai_object_id_t port_oid, + const uint32_t mtu) + { + EXPECT_CALL(mock_sai_router_intf_, + create_router_interface( + ::testing::NotNull(), Eq(gSwitchId), Eq(5), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, + CreateRouterInterfaceAttributeList( + gVirtualRouterId, router_intf_entry.src_mac_address, port_oid, mtu))))) + .WillOnce(DoAll(SetArgPointee<0>(router_intf_entry.router_interface_oid), Return(SAI_STATUS_SUCCESS))); + + const std::string router_intf_key = + KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouterInterface(router_intf_key, router_intf_entry)); + } + + StrictMock mock_sai_router_intf_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + RouterInterfaceManager router_intf_manager_; +}; + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceValidAttributes) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceEntryExistsInManager) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + // Same router interface key with different attributes + P4RouterInterfaceEntry new_entry(router_intf_entry.router_interface_id, kPortName2, kMacAddress2); + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, CreateRouterInterface(router_intf_key, new_entry)); + + // Validate that entry in Manager and Centralized Mapper has not changed + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceEntryExistsInP4OidMapper) +{ + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, kRouterInterfaceOid2); + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateRouterInterface(router_intf_key, router_intf_entry)); + + auto current_entry = GetRouterInterfaceEntry(router_intf_key); + EXPECT_EQ(current_entry, nullptr); + + // Validate that OID doesn't change in Centralized Mapper + sai_object_id_t mapper_oid; + ASSERT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &mapper_oid)); + EXPECT_EQ(mapper_oid, kRouterInterfaceOid2); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceInvalidPort) +{ + const std::string invalid_port_name = "xyz"; + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, invalid_port_name, kMacAddress2); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + CreateRouterInterface(KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId2), router_intf_entry)); + + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId2); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceNoMacAddress) +{ + P4RouterInterfaceEntry router_intf_entry; + router_intf_entry.router_interface_id = kRouterInterfaceId1; + router_intf_entry.port_name = kPortName1; + + EXPECT_CALL(mock_sai_router_intf_, + create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(4), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, + CreateRouterInterfaceAttributeList( + gVirtualRouterId, kZeroMacAddress, kPortOid1, kMtu1))))) + .WillOnce(DoAll(SetArgPointee<0>(kRouterInterfaceOid1), Return(SAI_STATUS_SUCCESS))); + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouterInterface(router_intf_key, router_intf_entry)); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceSaiApiFails) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, + CreateRouterInterface(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id), + router_intf_entry)); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceExistingInterface) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + router_intf_entry.router_interface_oid = kRouterInterfaceOid2; + AddRouterInterfaceEntry(router_intf_entry, kPortOid2, kMtu2); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(router_intf_entry.router_interface_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, + RemoveRouterInterface(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceNonExistingInterface) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + RemoveRouterInterface(KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId2))); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceNonZeroRefCount) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + router_intf_entry.router_interface_oid = kRouterInterfaceOid2; + AddRouterInterfaceEntry(router_intf_entry, kPortOid2, kMtu2); + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id); + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, RemoveRouterInterface(router_intf_key)); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceSaiApiFails) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + router_intf_entry.router_interface_oid = kRouterInterfaceOid2; + AddRouterInterfaceEntry(router_intf_entry, kPortOid2, kMtu2); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(router_intf_entry.router_interface_oid))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, + RemoveRouterInterface(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, SetSourceMacAddressModifyMacAddress) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + + sai_attribute_value_t attr_value; + memcpy(attr_value.mac, kMacAddress2.getMac(), sizeof(sai_mac_t)); + std::unordered_map attr_list = { + {SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS, attr_value}}; + EXPECT_CALL(mock_sai_router_intf_, + set_router_interface_attribute( + Eq(router_intf_entry.router_interface_oid), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, attr_list)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetSourceMacAddress(&router_intf_entry, kMacAddress2)); + EXPECT_EQ(router_intf_entry.src_mac_address, kMacAddress2); +} + +TEST_F(RouterInterfaceManagerTest, SetSourceMacAddressIdempotent) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + + // SAI API not being called makes the operation idempotent. + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetSourceMacAddress(&router_intf_entry, kMacAddress1)); + EXPECT_EQ(router_intf_entry.src_mac_address, kMacAddress1); +} + +TEST_F(RouterInterfaceManagerTest, SetSourceMacAddressSaiApiFails) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + + sai_attribute_value_t attr_value; + memcpy(attr_value.mac, kMacAddress2.getMac(), sizeof(sai_mac_t)); + std::unordered_map attr_list = { + {SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS, attr_value}}; + EXPECT_CALL(mock_sai_router_intf_, + set_router_interface_attribute( + Eq(router_intf_entry.router_interface_oid), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, attr_list)))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, SetSourceMacAddress(&router_intf_entry, kMacAddress2)); + EXPECT_EQ(router_intf_entry.src_mac_address, kMacAddress1); +} + +TEST_F(RouterInterfaceManagerTest, ProcessAddRequestValidAppDbParams) +{ + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = kRouterInterfaceId1, + .port_name = kPortName1, + .src_mac_address = kMacAddress1, + .is_set_port_name = true, + .is_set_src_mac = true}; + + EXPECT_CALL(mock_sai_router_intf_, + create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(5), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, + CreateRouterInterfaceAttributeList( + gVirtualRouterId, kMacAddress1, kPortOid1, kMtu1))))) + .WillOnce(DoAll(SetArgPointee<0>(kRouterInterfaceOid1), Return(SAI_STATUS_SUCCESS))); + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(app_db_entry, router_intf_key)); + + P4RouterInterfaceEntry router_intf_entry(app_db_entry.router_interface_id, app_db_entry.port_name, + app_db_entry.src_mac_address); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessAddRequestPortNameMissing) +{ + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = kRouterInterfaceId1, + .port_name = "", + .src_mac_address = kMacAddress1, + .is_set_port_name = false, + .is_set_src_mac = true}; + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(app_db_entry, router_intf_key)); +} + +TEST_F(RouterInterfaceManagerTest, ProcessAddRequestInvalidPortName) +{ + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = kRouterInterfaceId1, + .port_name = "", + .src_mac_address = kMacAddress1, + .is_set_port_name = true, + .is_set_src_mac = true}; + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(app_db_entry, router_intf_key)); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestSetSourceMacAddress) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + sai_attribute_value_t attr_value; + memcpy(attr_value.mac, kMacAddress2.getMac(), sizeof(sai_mac_t)); + std::unordered_map attr_list = { + {SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS, attr_value}}; + EXPECT_CALL(mock_sai_router_intf_, + set_router_interface_attribute( + Eq(router_intf_entry.router_interface_oid), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, attr_list)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = "", + .src_mac_address = kMacAddress2, + .is_set_port_name = false, + .is_set_src_mac = true}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager has the updated + // MacAddress. + router_intf_entry.src_mac_address = kMacAddress2; + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestSetPortNameIdempotent) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = kPortName1, + .src_mac_address = swss::MacAddress(), + .is_set_port_name = true, + .is_set_src_mac = false}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager has not + // changed. + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestSetPortName) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = kPortName2, + .src_mac_address = swss::MacAddress(), + .is_set_port_name = true, + .is_set_src_mac = false}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager has not + // changed. + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestMacAddrAndPort) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = kPortName2, + .src_mac_address = kMacAddress2, + .is_set_port_name = true, + .is_set_src_mac = true}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + // Update port name not supported, hence ProcessUpdateRequest should fail. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager does not + // changed. + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestExistingInterface) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(router_intf_entry.router_interface_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, + ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestNonExistingInterface) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1))); +} + +TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestInterfaceNotExistInMapper) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, + ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryValidAttributes) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_port_and_src_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1), + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()), + }; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, kPortName1); + EXPECT_EQ(app_db_entry.src_mac_address, kMacAddress1); + EXPECT_TRUE(app_db_entry.is_set_port_name); + EXPECT_TRUE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryInvalidKeyFormat) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_port_and_src_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1), + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()), + }; + + // Invalid json format. + std::string invalid_key = R"({"match/router_interface_id:intf-3/4"})"; + auto app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); + + // Invalid json format. + invalid_key = R"([{"match/router_interface_id":"intf-3/4"}])"; + app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); + + // Invalid json format. + invalid_key = R"(["match/router_interface_id","intf-3/4"])"; + app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); + + // Invalid field name. + invalid_key = R"({"router_interface_id":"intf-3/4"})"; + app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryMissingAction) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1), + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()), + }; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, kPortName1); + EXPECT_EQ(app_db_entry.src_mac_address, kMacAddress1); + EXPECT_TRUE(app_db_entry.is_set_port_name); + EXPECT_TRUE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryOnlyPortNameAttribute) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1)}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, kPortName1); + EXPECT_EQ(app_db_entry.src_mac_address, kZeroMacAddress); + EXPECT_TRUE(app_db_entry.is_set_port_name); + EXPECT_FALSE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryOnlyMacAddrAttribute) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string())}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, ""); + EXPECT_EQ(app_db_entry.src_mac_address, kMacAddress1); + EXPECT_FALSE(app_db_entry.is_set_port_name); + EXPECT_TRUE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryNoAttributes) +{ + const std::vector attributes; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, ""); + EXPECT_EQ(app_db_entry.src_mac_address, kZeroMacAddress); + EXPECT_FALSE(app_db_entry.is_set_port_name); + EXPECT_FALSE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryInvalidField) +{ + const std::vector attributes = {swss::FieldValueTuple("invalid_field", "invalid_value")}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryInvalidMacAddrValue) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), "00:11:22:33:44")}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); +} + +TEST_F(RouterInterfaceManagerTest, DrainValidAttributes) +{ + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + std::string(kRouterIntfAppDbKey); + + // Enqueue entry for create operation. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kRouterInterfaceOid1), Return(SAI_STATUS_SUCCESS))); + Drain(); + + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + ValidateRouterInterfaceEntry(router_intf_entry); + + // Enqueue entry for update operation. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress2.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_router_intf_, set_router_interface_attribute(_, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + router_intf_entry.src_mac_address = kMacAddress2; + ValidateRouterInterfaceEntry(router_intf_entry); + + // Enqueue entry for delete operation. + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, DEL_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, DrainInvalidAppDbEntryKey) +{ + // Create invalid json key with router interface id as kRouterInterfaceId1. + const std::string invalid_router_intf_key = R"({"match/router_interface_id:intf-3/4"})"; + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + invalid_router_intf_key; + + // Enqueue entry for create operation. + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + Drain(); + + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); +} + +TEST_F(RouterInterfaceManagerTest, DrainInvalidAppDbEntryAttributes) +{ + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + std::string(kRouterIntfAppDbKey); + + // Invalid port attribute. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), "xyz"}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + // Zero mac address attribute. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kZeroMacAddress.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + Drain(); + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); +} + +TEST_F(RouterInterfaceManagerTest, DrainInvalidOperation) +{ + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + std::string(kRouterIntfAppDbKey); + + // Enqueue entry for invalid operation. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, "INVALID", attributes)); + Drain(); + + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); +} diff --git a/orchagent/p4orch/tests/test_main.cpp b/orchagent/p4orch/tests/test_main.cpp new file mode 100644 index 0000000000..23cf37d8e1 --- /dev/null +++ b/orchagent/p4orch/tests/test_main.cpp @@ -0,0 +1,201 @@ +extern "C" +{ +#include "sai.h" +} + +#include + +#include + +#include "copporch.h" +#include "crmorch.h" +#include "dbconnector.h" +#include "directory.h" +#include "mock_sai_virtual_router.h" +#include "p4orch.h" +#include "portsorch.h" +#include "sai_serialize.h" +#include "switchorch.h" +#include "vrforch.h" +#include "gtest/gtest.h" + +using ::testing::StrictMock; + +/* Global variables */ +sai_object_id_t gVirtualRouterId = SAI_NULL_OBJECT_ID; +sai_object_id_t gSwitchId = SAI_NULL_OBJECT_ID; +sai_object_id_t gVrfOid = 111; +sai_object_id_t gTrapGroupStartOid = 20; +sai_object_id_t gHostifStartOid = 30; +sai_object_id_t gUserDefinedTrapStartOid = 40; +char *gVrfName = "b4-traffic"; +char *gMirrorSession1 = "mirror-session-1"; +sai_object_id_t kMirrorSessionOid1 = 9001; +char *gMirrorSession2 = "mirror-session-2"; +sai_object_id_t kMirrorSessionOid2 = 9002; +sai_object_id_t gUnderlayIfId; + +#define DEFAULT_BATCH_SIZE 128 +int gBatchSize = DEFAULT_BATCH_SIZE; +bool gSairedisRecord = true; +bool gSwssRecord = true; +bool gLogRotate = false; +bool gSaiRedisLogRotate = false; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +bool gSyncMode = false; +bool gIsNatSupported = false; + +PortsOrch *gPortsOrch; +CrmOrch *gCrmOrch; +P4Orch *gP4Orch; +VRFOrch *gVrfOrch; +SwitchOrch *gSwitchOrch; +Directory gDirectory; +ofstream gRecordOfs; +string gRecordFile; +swss::DBConnector *gAppDb; +swss::DBConnector *gStateDb; +swss::DBConnector *gConfigDb; +swss::DBConnector *gCountersDb; +MacAddress gVxlanMacAddress; + +sai_router_interface_api_t *sai_router_intfs_api; +sai_neighbor_api_t *sai_neighbor_api; +sai_next_hop_api_t *sai_next_hop_api; +sai_next_hop_group_api_t *sai_next_hop_group_api; +sai_route_api_t *sai_route_api; +sai_acl_api_t *sai_acl_api; +sai_policer_api_t *sai_policer_api; +sai_virtual_router_api_t *sai_virtual_router_api; +sai_hostif_api_t *sai_hostif_api; +sai_switch_api_t *sai_switch_api; +sai_mirror_api_t *sai_mirror_api; +sai_udf_api_t *sai_udf_api; +sai_tunnel_api_t *sai_tunnel_api; + +namespace +{ + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +void CreatePort(const std::string port_name, const uint32_t speed, const uint32_t mtu, const sai_object_id_t port_oid, + Port::Type port_type = Port::PHY, const sai_port_oper_status_t oper_status = SAI_PORT_OPER_STATUS_DOWN, + const sai_object_id_t vrouter_id = gVirtualRouterId, const bool admin_state_up = true) +{ + Port port(port_name, port_type); + port.m_speed = speed; + port.m_mtu = mtu; + if (port_type == Port::LAG) + { + port.m_lag_id = port_oid; + } + else + { + port.m_port_id = port_oid; + } + port.m_vr_id = vrouter_id; + port.m_admin_state_up = admin_state_up; + port.m_oper_status = oper_status; + + gPortsOrch->setPort(port_name, port); +} + +void SetupPorts() +{ + CreatePort(/*port_name=*/"Ethernet1", /*speed=*/100000, + /*mtu=*/1500, /*port_oid=*/0x112233); + CreatePort(/*port_name=*/"Ethernet2", /*speed=*/400000, + /*mtu=*/4500, /*port_oid=*/0x1fed3); + CreatePort(/*port_name=*/"Ethernet3", /*speed=*/50000, + /*mtu=*/9100, /*port_oid=*/0xaabbccdd); + CreatePort(/*port_name=*/"Ethernet4", /*speed=*/100000, + /*mtu=*/1500, /*port_oid=*/0x9988); + CreatePort(/*port_name=*/"Ethernet5", /*speed=*/400000, + /*mtu=*/4500, /*port_oid=*/0x56789abcdef); + CreatePort(/*port_name=*/"Ethernet6", /*speed=*/50000, + /*mtu=*/9100, /*port_oid=*/0x56789abcdff, Port::PHY, SAI_PORT_OPER_STATUS_UP); + CreatePort(/*port_name=*/"Ethernet7", /*speed=*/100000, + /*mtu=*/9100, /*port_oid=*/0x1234, /*port_type*/ Port::LAG); + CreatePort(/*port_name=*/"Ethernet8", /*speed=*/100000, + /*mtu=*/9100, /*port_oid=*/0x5678, /*port_type*/ Port::MGMT); + CreatePort(/*port_name=*/"Ethernet9", /*speed=*/50000, + /*mtu=*/9100, /*port_oid=*/0x56789abcfff, Port::PHY, SAI_PORT_OPER_STATUS_UNKNOWN); +} + +void AddVrf() +{ + Table app_vrf_table(gAppDb, APP_VRF_TABLE_NAME); + std::vector attributes; + app_vrf_table.set(gVrfName, attributes); + + StrictMock mock_sai_virtual_router_; + mock_sai_virtual_router = &mock_sai_virtual_router_; + sai_virtual_router_api->create_virtual_router = create_virtual_router; + sai_virtual_router_api->remove_virtual_router = remove_virtual_router; + sai_virtual_router_api->set_virtual_router_attribute = set_virtual_router_attribute; + EXPECT_CALL(mock_sai_virtual_router_, create_virtual_router(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gVrfOid), Return(SAI_STATUS_SUCCESS))); + gVrfOrch->addExistingData(&app_vrf_table); + static_cast(gVrfOrch)->doTask(); +} + +} // namespace + +int main(int argc, char *argv[]) +{ + testing::InitGoogleTest(&argc, argv); + + sai_router_interface_api_t router_intfs_api; + sai_neighbor_api_t neighbor_api; + sai_next_hop_api_t next_hop_api; + sai_next_hop_group_api_t next_hop_group_api; + sai_route_api_t route_api; + sai_acl_api_t acl_api; + sai_policer_api_t policer_api; + sai_virtual_router_api_t virtual_router_api; + sai_hostif_api_t hostif_api; + sai_switch_api_t switch_api; + sai_mirror_api_t mirror_api; + sai_udf_api_t udf_api; + sai_router_intfs_api = &router_intfs_api; + sai_neighbor_api = &neighbor_api; + sai_next_hop_api = &next_hop_api; + sai_next_hop_group_api = &next_hop_group_api; + sai_route_api = &route_api; + sai_acl_api = &acl_api; + sai_policer_api = &policer_api; + sai_virtual_router_api = &virtual_router_api; + sai_hostif_api = &hostif_api; + sai_switch_api = &switch_api; + sai_mirror_api = &mirror_api; + sai_udf_api = &udf_api; + + swss::DBConnector appl_db("APPL_DB", 0); + swss::DBConnector state_db("STATE_DB", 0); + swss::DBConnector config_db("CONFIG_DB", 0); + swss::DBConnector counters_db("COUNTERS_DB", 0); + gAppDb = &appl_db; + gStateDb = &state_db; + gConfigDb = &config_db; + gCountersDb = &counters_db; + std::vector ports_tables; + PortsOrch ports_orch(gAppDb, gStateDb, ports_tables, gAppDb); + gPortsOrch = &ports_orch; + CrmOrch crm_orch(gConfigDb, CFG_CRM_TABLE_NAME); + + gCrmOrch = &crm_orch; + VRFOrch vrf_orch(gAppDb, APP_VRF_TABLE_NAME, gStateDb, STATE_VRF_OBJECT_TABLE_NAME); + gVrfOrch = &vrf_orch; + gDirectory.set(static_cast(&vrf_orch)); + + // Setup ports for all tests. + SetupPorts(); + AddVrf(); + + return RUN_ALL_TESTS(); +} diff --git a/orchagent/p4orch/tests/wcmp_manager_test.cpp b/orchagent/p4orch/tests/wcmp_manager_test.cpp new file mode 100644 index 0000000000..73cf34be25 --- /dev/null +++ b/orchagent/p4orch/tests/wcmp_manager_test.cpp @@ -0,0 +1,1852 @@ +#include "wcmp_manager.h" + +#include +#include + +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_acl.h" +#include "mock_sai_hostif.h" +#include "mock_sai_next_hop_group.h" +#include "mock_sai_serialize.h" +#include "mock_sai_switch.h" +#include "mock_sai_udf.h" +#include "p4oidmapper.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "p4orch_util.h" +#include "return_code.h" +#include "sai_serialize.h" +extern "C" +{ +#include "sai.h" +} + +extern P4Orch *gP4Orch; +extern VRFOrch *gVrfOrch; +extern swss::DBConnector *gAppDb; +extern sai_object_id_t gSwitchId; +extern sai_next_hop_group_api_t *sai_next_hop_group_api; +extern sai_hostif_api_t *sai_hostif_api; +extern sai_switch_api_t *sai_switch_api; +extern sai_udf_api_t *sai_udf_api; +extern sai_object_id_t gSwitchId; +extern sai_acl_api_t *sai_acl_api; + +namespace p4orch +{ +namespace test +{ + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +namespace +{ + +constexpr char *kWcmpGroupId1 = "group-1"; +constexpr sai_object_id_t kWcmpGroupOid1 = 10; +constexpr char *kNexthopId1 = "ju1u32m1.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid1 = 1; +constexpr sai_object_id_t kWcmpGroupMemberOid1 = 11; +constexpr char *kNexthopId2 = "ju1u32m2.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid2 = 2; +constexpr sai_object_id_t kWcmpGroupMemberOid2 = 12; +constexpr char *kNexthopId3 = "ju1u32m3.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid3 = 3; +constexpr sai_object_id_t kWcmpGroupMemberOid3 = 13; +constexpr sai_object_id_t kWcmpGroupMemberOid4 = 14; +constexpr sai_object_id_t kWcmpGroupMemberOid5 = 15; +const std::string kWcmpGroupKey1 = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); +const std::string kNexthopKey1 = KeyGenerator::generateNextHopKey(kNexthopId1); +const std::string kNexthopKey2 = KeyGenerator::generateNextHopKey(kNexthopId2); +const std::string kNexthopKey3 = KeyGenerator::generateNextHopKey(kNexthopId3); +constexpr sai_object_id_t kUdfMatchOid1 = 5001; + +// Matches the next hop group type sai_attribute_t argument. +bool MatchSaiNextHopGroupAttribute(const sai_attribute_t *attr) +{ + if (attr == nullptr || attr->id != SAI_NEXT_HOP_GROUP_ATTR_TYPE || attr->value.s32 != SAI_NEXT_HOP_GROUP_TYPE_ECMP) + { + return false; + } + return true; +} + +// Matches the action type sai_attribute_t argument. +bool MatchSaiNextHopGroupMemberAttribute(const sai_object_id_t expected_next_hop_oid, const int expected_weight, + const sai_object_id_t expected_wcmp_group_oid, + const sai_attribute_t *attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + for (int i = 0; i < 3; ++i) + { + switch (attr_list[i].id) + { + case SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID: + if (attr_list[i].value.oid != expected_wcmp_group_oid) + { + return false; + } + break; + case SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID: + if (attr_list[i].value.oid != expected_next_hop_oid) + { + return false; + } + break; + case SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT: + if (attr_list[i].value.u32 != (uint32_t)expected_weight) + { + return false; + } + break; + default: + break; + } + } + return true; +} + +void VerifyWcmpGroupMemberEntry(const std::string &expected_next_hop_id, const int expected_weight, + std::shared_ptr wcmp_gm_entry) +{ + EXPECT_EQ(expected_next_hop_id, wcmp_gm_entry->next_hop_id); + EXPECT_EQ(expected_weight, (int)wcmp_gm_entry->weight); +} + +void VerifyWcmpGroupEntry(const P4WcmpGroupEntry &expect_entry, const P4WcmpGroupEntry &wcmp_entry) +{ + EXPECT_EQ(expect_entry.wcmp_group_id, wcmp_entry.wcmp_group_id); + ASSERT_EQ(expect_entry.wcmp_group_members.size(), wcmp_entry.wcmp_group_members.size()); + for (size_t i = 0; i < expect_entry.wcmp_group_members.size(); i++) + { + ASSERT_LE(i, wcmp_entry.wcmp_group_members.size()); + auto gm = expect_entry.wcmp_group_members[i]; + VerifyWcmpGroupMemberEntry(gm->next_hop_id, gm->weight, wcmp_entry.wcmp_group_members[i]); + } +} +} // namespace + +class WcmpManagerTest : public ::testing::Test +{ + protected: + WcmpManagerTest() + { + setUpMockApi(); + setUpP4Orch(); + wcmp_group_manager_ = gP4Orch->getWcmpManager(); + p4_oid_mapper_ = wcmp_group_manager_->m_p4OidMapper; + } + + ~WcmpManagerTest() + { + EXPECT_CALL(mock_sai_switch_, set_switch_attribute(Eq(gSwitchId), _)) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_match(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + delete gP4Orch; + delete copp_orch_; + } + + void setUpMockApi() + { + // Set up mock stuff for SAI next hop group API structure. + mock_sai_next_hop_group = &mock_sai_next_hop_group_; + mock_sai_switch = &mock_sai_switch_; + mock_sai_hostif = &mock_sai_hostif_; + mock_sai_serialize = &mock_sai_serialize_; + mock_sai_acl = &mock_sai_acl_; + mock_sai_udf = &mock_sai_udf_; + + sai_next_hop_group_api->create_next_hop_group = create_next_hop_group; + sai_next_hop_group_api->remove_next_hop_group = remove_next_hop_group; + sai_next_hop_group_api->create_next_hop_group_member = create_next_hop_group_member; + sai_next_hop_group_api->remove_next_hop_group_member = remove_next_hop_group_member; + sai_next_hop_group_api->set_next_hop_group_member_attribute = set_next_hop_group_member_attribute; + + sai_hostif_api->create_hostif_table_entry = mock_create_hostif_table_entry; + sai_hostif_api->create_hostif_trap = mock_create_hostif_trap; + sai_switch_api->get_switch_attribute = mock_get_switch_attribute; + sai_switch_api->set_switch_attribute = mock_set_switch_attribute; + sai_acl_api->create_acl_table_group = create_acl_table_group; + sai_acl_api->remove_acl_table_group = remove_acl_table_group; + sai_udf_api->create_udf_match = create_udf_match; + sai_udf_api->remove_udf_match = remove_udf_match; + } + + void setUpP4Orch() + { + // init copp orch + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, get_switch_attribute(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + copp_orch_ = new CoppOrch(gAppDb, APP_COPP_TABLE_NAME); + + // init P4 orch + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + + std::vector p4_tables; + gP4Orch = new P4Orch(gAppDb, p4_tables, gVrfOrch, copp_orch_); + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + wcmp_group_manager_->enqueue(entry); + } + + void Drain() + { + wcmp_group_manager_->drain(); + } + + ReturnCode ProcessAddRequest(P4WcmpGroupEntry *app_db_entry) + { + return wcmp_group_manager_->processAddRequest(app_db_entry); + } + + void HandlePortStatusChangeNotification(const std::string &op, const std::string &data) + { + gP4Orch->handlePortStatusChangeNotification(op, data); + } + + void PruneNextHops(const std::string &port) + { + wcmp_group_manager_->pruneNextHops(port); + } + + void RestorePrunedNextHops(const std::string &port) + { + wcmp_group_manager_->restorePrunedNextHops(port); + } + + bool VerifyWcmpGroupMemberInPrunedSet(std::shared_ptr gm, bool expected_member_present, + long unsigned int expected_set_size) + { + if (wcmp_group_manager_->pruned_wcmp_members_set.size() != expected_set_size) + return false; + + return expected_member_present ? (wcmp_group_manager_->pruned_wcmp_members_set.find(gm) != + wcmp_group_manager_->pruned_wcmp_members_set.end()) + : (wcmp_group_manager_->pruned_wcmp_members_set.find(gm) == + wcmp_group_manager_->pruned_wcmp_members_set.end()); + } + + bool VerifyWcmpGroupMemberInPortMap(std::shared_ptr gm, bool expected_member_present, + long unsigned int expected_set_size) + { + auto it = wcmp_group_manager_->port_name_to_wcmp_group_member_map.find(gm->watch_port); + if (it != wcmp_group_manager_->port_name_to_wcmp_group_member_map.end()) + { + auto &s = wcmp_group_manager_->port_name_to_wcmp_group_member_map[gm->watch_port]; + if (s.size() != expected_set_size) + return false; + return expected_member_present ? (s.count(gm) > 0) : (s.count(gm) == 0); + } + else + { + return !expected_member_present; + } + return false; + } + + ReturnCode ProcessUpdateRequest(P4WcmpGroupEntry *app_db_entry) + { + return wcmp_group_manager_->processUpdateRequest(app_db_entry); + } + + ReturnCode RemoveWcmpGroup(const std::string &wcmp_group_id) + { + return wcmp_group_manager_->removeWcmpGroup(wcmp_group_id); + } + + P4WcmpGroupEntry *GetWcmpGroupEntry(const std::string &wcmp_group_id) + { + return wcmp_group_manager_->getWcmpGroupEntry(wcmp_group_id); + } + + ReturnCodeOr DeserializeP4WcmpGroupAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return wcmp_group_manager_->deserializeP4WcmpGroupAppDbEntry(key, attributes); + } + + // Adds the WCMP group entry via WcmpManager::ProcessAddRequest(). This + // function also takes care of all the dependencies of the WCMP group entry. + // Returns a valid pointer to WCMP group entry on success. + P4WcmpGroupEntry AddWcmpGroupEntry1(); + P4WcmpGroupEntry AddWcmpGroupEntryWithWatchport(const std::string &port, const bool oper_up = false); + P4WcmpGroupEntry getDefaultWcmpGroupEntryForTest(); + std::shared_ptr createWcmpGroupMemberEntry(const std::string &next_hop_id, + const int weight); + std::shared_ptr createWcmpGroupMemberEntryWithWatchport(const std::string &next_hop_id, + const int weight, + const std::string &watch_port, + const std::string &wcmp_group_id, + const sai_object_id_t next_hop_oid); + + StrictMock mock_sai_next_hop_group_; + StrictMock mock_sai_switch_; + StrictMock mock_sai_hostif_; + StrictMock mock_sai_serialize_; + StrictMock mock_sai_acl_; + StrictMock mock_sai_udf_; + P4OidMapper *p4_oid_mapper_; + WcmpManager *wcmp_group_manager_; + CoppOrch *copp_orch_; +}; + +P4WcmpGroupEntry WcmpManagerTest::getDefaultWcmpGroupEntryForTest() +{ + P4WcmpGroupEntry app_db_entry; + app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr gm1 = std::make_shared(); + gm1->next_hop_id = kNexthopId1; + gm1->weight = 2; + app_db_entry.wcmp_group_members.push_back(gm1); + std::shared_ptr gm2 = std::make_shared(); + gm2->next_hop_id = kNexthopId2; + gm2->weight = 1; + app_db_entry.wcmp_group_members.push_back(gm2); + return app_db_entry; +} + +P4WcmpGroupEntry WcmpManagerTest::AddWcmpGroupEntryWithWatchport(const std::string &port, const bool oper_up) +{ + P4WcmpGroupEntry app_db_entry; + app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr gm1 = std::make_shared(); + gm1->next_hop_id = kNexthopId1; + gm1->weight = 2; + gm1->watch_port = port; + gm1->wcmp_group_id = kWcmpGroupId1; + app_db_entry.wcmp_group_members.push_back(gm1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group(_, Eq(gSwitchId), Eq(1), + Truly(std::bind(MatchSaiNextHopGroupAttribute, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + // For members with non empty watchport field, member creation in SAI happens + // for operationally up ports only.. + if (oper_up) + { + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + } + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(&app_db_entry)); + EXPECT_NE(nullptr, GetWcmpGroupEntry(kWcmpGroupId1)); + return app_db_entry; +} + +P4WcmpGroupEntry WcmpManagerTest::AddWcmpGroupEntry1() +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, kNexthopOid3); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group(_, Eq(gSwitchId), Eq(1), + Truly(std::bind(MatchSaiNextHopGroupAttribute, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(&app_db_entry)); + EXPECT_NE(nullptr, GetWcmpGroupEntry(kWcmpGroupId1)); + return app_db_entry; +} + +// Create a WCMP group member with the requested attributes +std::shared_ptr WcmpManagerTest::createWcmpGroupMemberEntry(const std::string &next_hop_id, + const int weight) +{ + std::shared_ptr gm = std::make_shared(); + gm->next_hop_id = next_hop_id; + gm->weight = weight; + return gm; +} + +// Create a WCMP group member that uses a watchport with the requested +// attributes +std::shared_ptr WcmpManagerTest::createWcmpGroupMemberEntryWithWatchport( + const std::string &next_hop_id, const int weight, const std::string &watch_port, const std::string &wcmp_group_id, + const sai_object_id_t next_hop_oid) +{ + std::shared_ptr gm = std::make_shared(); + gm->next_hop_id = next_hop_id; + gm->weight = weight; + gm->watch_port = watch_port; + gm->wcmp_group_id = wcmp_group_id; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(next_hop_id), next_hop_oid); + return gm; +} + +TEST_F(WcmpManagerTest, CreateWcmpGroup) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry expect_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm_entry1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + expect_entry.wcmp_group_members.push_back(gm_entry1); + std::shared_ptr gm_entry2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expect_entry.wcmp_group_members.push_back(gm_entry2); + VerifyWcmpGroupEntry(expect_entry, *GetWcmpGroupEntry(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_ITEM_NOT_FOUND)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFailsPlusGroupMemberRecoveryFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_ITEM_NOT_FOUND)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFailsPlusGroupRecoveryFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_ITEM_NOT_FOUND)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupSaiCallFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + app_db_entry.wcmp_group_members.pop_back(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)).WillOnce(Return(SAI_STATUS_TABLE_FULL)); + + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddRequest(&app_db_entry)); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenRefcountIsGtThanZero) +{ + AddWcmpGroupEntry1(); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1)); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_NE(nullptr, GetWcmpGroupEntry(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenNotExist) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenSaiCallFails) +{ + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntry1(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenMemberRemovalFails) +{ + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntry1(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenMemberRemovalFailsPlusRecoveryFails) +{ + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntry1(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_FAILURE))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupMembersSucceed) +{ + AddWcmpGroupEntry1(); + // Update WCMP group member with nexthop_id=kNexthopId1 weight to 3, + // nexthop_id=kNexthopId2 weight to 15. + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 3); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 15); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Remove group member with nexthop_id=kNexthopId1 + wcmp_group.wcmp_group_members.clear(); + gm2 = createWcmpGroupMemberEntry(kNexthopId2, 15); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Add group member with nexthop_id=kNexthopId1 and weight=20 + wcmp_group.wcmp_group_members.clear(); + std::shared_ptr updated_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 15); + std::shared_ptr updated_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 20); + wcmp_group.wcmp_group_members.push_back(updated_gm1); + wcmp_group.wcmp_group_members.push_back(updated_gm2); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 20, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + + // Update WCMP without group members + wcmp_group.wcmp_group_members.clear(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(0, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenRemoveGroupMemberSaiCallFails) +{ + AddWcmpGroupEntry1(); + // Add WCMP group member with nexthop_id=kNexthopId1, weight=3 and + // nexthop_id=kNexthopId3, weight=30, update nexthop_id=kNexthopId2 + // weight to 10. + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 3); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 10); + std::shared_ptr gm3 = createWcmpGroupMemberEntry(kNexthopId3, 30); + + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + wcmp_group.wcmp_group_members.push_back(gm3); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid3, 30, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid3), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(3, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Remove WCMP group member with nexthop_id=kNexthopId1 and + // nexthop_id=kNexthopId3(fail) - succeed to clean up + wcmp_group.wcmp_group_members.clear(); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm3); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid3))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // Clean up - revert deletions -success + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRequest(&wcmp_group)); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + expected_wcmp_group.wcmp_group_members.push_back(gm1); + expected_wcmp_group.wcmp_group_members.push_back(gm2); + expected_wcmp_group.wcmp_group_members.push_back(gm3); + // WCMP group remains as the old one + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(3, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + + // Remove WCMP group member with nexthop_id=kNexthopId1 and + // nexthop_id=kNexthopId3(fail) - fail to clean up + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid3))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // Clean up - revert deletions -failure + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + // (TODO): Expect critical state. + EXPECT_EQ("Failed to remove WCMP group member with nexthop id " + "'ju1u32m3.atl11:qe-3/7'", + ProcessUpdateRequest(&wcmp_group).message()); + // WCMP group is as expected, but refcounts are not + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + // WCMP group is corrupt due to clean up failure + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenCreateNewGroupMemberSaiCallFails) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + + // Remove group member with nexthop_id=kNexthopId1 + wcmp_group.wcmp_group_members.clear(); + std::shared_ptr gm = createWcmpGroupMemberEntry(kNexthopId2, 15); + wcmp_group.wcmp_group_members.push_back(gm); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + // Add WCMP group member with nexthop_id=kNexthopId1, weight=3 and + // nexthop_id=kNexthopId3, weight=30(fail), update nexthop_id=kNexthopId2 + // weight to 10. + P4WcmpGroupEntry updated_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr updated_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 3); + std::shared_ptr updated_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 20); + std::shared_ptr updated_gm3 = createWcmpGroupMemberEntry(kNexthopId3, 30); + updated_wcmp_group.wcmp_group_members.push_back(updated_gm1); + updated_wcmp_group.wcmp_group_members.push_back(updated_gm2); + updated_wcmp_group.wcmp_group_members.push_back(updated_gm3); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 20, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid3, 30, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + // Clean up - success + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_FALSE(ProcessUpdateRequest(&updated_wcmp_group).ok()); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr expected_gm = createWcmpGroupMemberEntry(kNexthopId2, 15); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm); + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + + // Try again, but this time clean up failed to remove created group member + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 20, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid3, 30, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + // Clean up - revert creation - failure + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // (TODO): Expect critical state. + EXPECT_EQ("Failed to create next hop group member 'ju1u32m3.atl11:qe-3/7'", + ProcessUpdateRequest(&updated_wcmp_group).message()); + // WCMP group is as expected, but refcounts are not + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); // Corrupt status due to clean up failure + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); // Corrupt status due to clean up failure + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(2, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenReduceGroupMemberWeightSaiCallFails) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + // Update WCMP group member to nexthop_id=kNexthopId1, weight=1(reduce) and + // nexthop_id=kNexthopId2, weight=10(increase), update nexthop_id=kNexthopId1 + // weight=1(fail). + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 1); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 10); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_FALSE(ProcessUpdateRequest(&wcmp_group).ok()); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr expected_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + std::shared_ptr expected_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm2); + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenIncreaseGroupMemberWeightSaiCallFails) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + // Update WCMP group member to nexthop_id=kNexthopId1, weight=1(reduce) and + // nexthop_id=kNexthopId2, weight=10(increase), update nexthop_id=kNexthopId2 + // weight=10(fail). + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 1); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 10); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + // Clean up modified members - success + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_FALSE(ProcessUpdateRequest(&wcmp_group).ok()); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr expected_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + std::shared_ptr expected_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm2); + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Try again, the same error happens when update and new error during clean up + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + // Clean up modified members - failure + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_NOT_SUPPORTED))); + + // (TODO): Expect critical state. + EXPECT_EQ("Failed to create next hop group member " + "'ju1u32m2.atl11:qe-3/7'", + ProcessUpdateRequest(&wcmp_group).message()); + // weight of wcmp_group_members[kNexthopId1] unable to revert + // SAI object in ASIC DB: missing group member with + // next_hop_id=kNexthopId1 + expected_gm1->weight = 2; + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, ValidateWcmpGroupEntryFailsWhenNextHopDoesNotExist) +{ + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm = createWcmpGroupMemberEntry("Unregistered-Nexthop", 1); + app_db_entry.wcmp_group_members.push_back(gm); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, ValidateWcmpGroupEntryFailsWhenWeightLessThanOne) +{ + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm = createWcmpGroupMemberEntry(kNexthopId1, 0); + app_db_entry.wcmp_group_members.push_back(gm); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, WcmpGroupInvalidOperationInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + // If weight is omitted in the action, then it is set to 1 by default(ECMP) + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + + // Invalid Operation string. Only SET and DEL are allowed + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), "Update", attributes)); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, WcmpGroupUndefinedAttributesInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{"Undefined", "Invalid"}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); +} + +TEST_F(WcmpManagerTest, WcmpGroupCreateAndDeleteInDrainSucceeds) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + // If weight is omitted in the action, then it is set to 1 by default(ECMP) + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), DEL_COMMAND, attributes)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, WcmpGroupCreateAndUpdateInDrainSucceeds) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + // Create WCMP group with member {next_hop_id=kNexthopId1, weight=1} + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId1, 1, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid1, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update WCMP group with exact same members, the same entry will be removed + // and created again + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid3), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId1, 1, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid3, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update WCMP group with member {next_hop_id=kNexthopId2, weight=1} + actions.clear(); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid3))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId2, 1, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid2, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + // Update WCMP group with member {next_hop_id=kNexthopId2, weight=2} + actions.clear(); + action[p4orch::kWeight] = 2; + actions.push_back(action); + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId2, 2, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid4, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroup) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 2; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + auto wcmp_group_entry_or = DeserializeP4WcmpGroupAppDbEntry(key, attributes); + EXPECT_TRUE(wcmp_group_entry_or.ok()); + auto &wcmp_group_entry = *wcmp_group_entry_or; + P4WcmpGroupEntry expect_entry = {}; + expect_entry.wcmp_group_id = "group-a"; + std::shared_ptr gm_entry1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + expect_entry.wcmp_group_members.push_back(gm_entry1); + std::shared_ptr gm_entry2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expect_entry.wcmp_group_members.push_back(gm_entry2); + VerifyWcmpGroupEntry(expect_entry, wcmp_group_entry); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupDuplicateGroupMembers) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + auto return_code_or = DeserializeP4WcmpGroupAppDbEntry(key, attributes); + EXPECT_TRUE(return_code_or.ok()); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupFailsWhenGroupKeyIsInvalidJson) +{ + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + // Invalid JSON + std::string key = R"("match/wcmp_group_id":"group-a"})"; + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + // Is string not JSON + key = R"("group-a")"; + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupFailsWhenActionsStringIsInvalid) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + // Actions field is an invalid JSON + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, "Undefied"}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + + attributes.clear(); + nlohmann::json action; + action[p4orch::kAction] = kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + // Actions field is not an array + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, action.dump()}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + + attributes.clear(); + nlohmann::json actions; + action[p4orch::kAction] = "Undefined"; + actions.push_back(action); + // Actions field has undefiend action + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + + attributes.clear(); + actions.clear(); + action.clear(); + action[p4orch::kAction] = kSetNexthopId; + action[p4orch::kWeight] = 1; + actions.push_back(action); + // Actions field has the group member without next_hop_id field + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + attributes.clear(); + actions.clear(); + action[p4orch::kAction] = kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + actions.push_back(action); + // Actions field has multiple group members have the same next_hop_id + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_TRUE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupFailsWithUndefinedAttributes) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + // Undefined field in attribute list + attributes.push_back(swss::FieldValueTuple{"Undefined", "Undefined"}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); +} + +TEST_F(WcmpManagerTest, ValidateWcmpGroupEntryWithInvalidWatchportAttributeFails) +{ + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 1, "EthernetXX", kWcmpGroupId1, kNexthopOid1); + app_db_entry.wcmp_group_members.push_back(gm); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(&app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(gm, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(gm, false, 0)); +} + +TEST_F(WcmpManagerTest, PruneNextHopSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + // Prune next hops associated with port + PruneNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); +} + +TEST_F(WcmpManagerTest, PruneNextHopFailsWithNextHopRemovalFailure) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // Prune next hops associated with port (fails) + PruneNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, RestorePrunedNextHopSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + + // Restore next hops associated with port + RestorePrunedNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, RestorePrunedNextHopFailsWithNoOidMappingForWcmpGroup) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1)); + // (TODO): Expect critical state. + RestorePrunedNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); +} + +TEST_F(WcmpManagerTest, RestorePrunedNextHopFailsWithNextHopCreationFailure) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_FAILURE))); + // (TODO): Expect critical state. + RestorePrunedNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); +} + +TEST_F(WcmpManagerTest, CreateGroupWithWatchportFailsWithNextHopCreationFailure) +{ + // Add member with operationally up watch port + // Create WCMP group with members kNexthopId1 and kNexthopId2 (fails) + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm1 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 1, port_name, kWcmpGroupId1, kNexthopOid1); + app_db_entry.wcmp_group_members.push_back(gm1); + std::shared_ptr gm2 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 1, port_name, kWcmpGroupId1, kNexthopOid2); + app_db_entry.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group(_, Eq(gSwitchId), Eq(1), + Truly(std::bind(MatchSaiNextHopGroupAttribute, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // Clean up created members + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(&app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(gm1, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(gm2, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(gm1, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(gm2, false, 0)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupAfterPruningSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + PruneNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Remove Wcmp group. No SAI call for member removal is expected as it is + // already pruned. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupWithOperationallyDownWatchportSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport("Ethernet1"); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Remove Wcmp group. No SAI call for member removal is expected as it is + // already pruned. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyUpWatchportMemberSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Update WCMP group to remove kNexthopId1 and add kNexthopId2 + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 1, port_name, kWcmpGroupId1, kNexthopOid2); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm, true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm, false, 0)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyDownWatchportMemberSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Update WCMP group to remove kNexthopId1 and add kNexthopId2. No SAI calls + // are expected as the associated watch port is operationally down. + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 1, port_name, kWcmpGroupId1, kNexthopOid2); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm, true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm, true, 1)); +} + +TEST_F(WcmpManagerTest, PruneAfterWcmpGroupUpdateSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Update WCMP group to modify weight of kNexthopId1. + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 10, port_name, kWcmpGroupId1, kNexthopOid1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], false, 0)); + + // Prune members associated with port. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + PruneNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], true, 1)); + + // Remove Wcmp group. No SAI call for member removal is expected as it is + // already pruned. + // RemoveWcmpGroupWithOperationallyDownWatchportSucceeds verfies that SAI call + // for pruned member is not made on group removal. Hence, the member must be + // removed from SAI during prune. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, PrunedMemberUpdateOnRestoreSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Update WCMP group to modify weight of kNexthopId1. + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 10, port_name, kWcmpGroupId1, kNexthopOid1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], true, 1)); + + // Restore members associated with port. + // Verify that the weight of the restored member is updated. + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + RestorePrunedNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyUpWatchportMemberFailsWithMemberRemovalFailure) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Update WCMP group to remove kNexthopId1(fails) and add kNexthopId2 + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm2 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 10, port_name, kWcmpGroupId1, kNexthopOid2); + std::shared_ptr updated_gm1 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 1, port_name, kWcmpGroupId1, kNexthopOid1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + // Clean up created member-succeeds + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm2, false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm2, false, 0)); + + // Update again, this time clean up fails + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + // Clean up created member(fails) + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_INSUFFICIENT_RESOURCES))); + // (TODO): Expect critical state. + EXPECT_EQ("Failed to create next hop group member " + "'ju1u32m2.atl11:qe-3/7'", + ProcessUpdateRequest(&updated_app_db_entry).message()); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm2, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm2, false, 0)); +} + +TEST_F(WcmpManagerTest, WatchportStateChangetoOperDownSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Send port down signal + // Verify that the next hop member associated with the port is pruned. + std::string op = "port_state_change"; + std::string data = "[{\"port_id\":\"oid:0x56789abcdff\",\"port_state\":\"SAI_PORT_OPER_" + "STATUS_DOWN\"}]"; + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + HandlePortStatusChangeNotification(op, data); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); +} + +TEST_F(WcmpManagerTest, WatchportStateChangeToOperUpSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Send port up signal. + // Verify that the pruned next hop member associated with the port is + // restored. + std::string op = "port_state_change"; + std::string data = "[{\"port_id\":\"oid:0x112233\",\"port_state\":\"SAI_PORT_OPER_" + "STATUS_UP\"}]"; + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + HandlePortStatusChangeNotification(op, data); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, WatchportStateChangeFromOperUnknownToDownPrunesMemberOnlyOnceSuceeds) +{ + // Add member with operationally unknown watch port. Since associated + // watchport is not operationally up, member will not be created in SAI but + // will be directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Send port down signal. + // Verify that the pruned next hop member is not pruned again. + std::string op = "port_state_change"; + std::string data = "[{\"port_id\":\"oid:0x56789abcfff\",\"port_state\":\"SAI_PORT_OPER_" + "STATUS_DOWN\"}]"; + HandlePortStatusChangeNotification(op, data); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); +} +} // namespace test +} // namespace p4orch diff --git a/orchagent/p4orch/wcmp_manager.cpp b/orchagent/p4orch/wcmp_manager.cpp new file mode 100644 index 0000000000..6078f92221 --- /dev/null +++ b/orchagent/p4orch/wcmp_manager.cpp @@ -0,0 +1,760 @@ +#include "p4orch/wcmp_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "sai_serialize.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_next_hop_group_api_t *sai_next_hop_group_api; +extern CrmOrch *gCrmOrch; +extern PortsOrch *gPortsOrch; + +namespace p4orch +{ + +namespace +{ + +std::string getWcmpGroupMemberKey(const std::string &wcmp_group_key, const sai_object_id_t wcmp_member_oid) +{ + return wcmp_group_key + kTableKeyDelimiter + sai_serialize_object_id(wcmp_member_oid); +} + +} // namespace + +ReturnCode WcmpManager::validateWcmpGroupEntry(const P4WcmpGroupEntry &app_db_entry) +{ + for (auto &wcmp_group_member : app_db_entry.wcmp_group_members) + { + if (wcmp_group_member->weight <= 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid WCMP group member weight " << wcmp_group_member->weight << ": should be greater than 0."; + } + sai_object_id_t nexthop_oid = SAI_NULL_OBJECT_ID; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id), &nexthop_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Nexthop id " << QuotedVar(wcmp_group_member->next_hop_id) << " does not exist for WCMP group " + << QuotedVar(app_db_entry.wcmp_group_id); + } + if (!wcmp_group_member->watch_port.empty()) + { + Port port; + if (!gPortsOrch->getPort(wcmp_group_member->watch_port, port)) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid watch_port field " << wcmp_group_member->watch_port + << ": should be a valid port name."; + } + } + } + return ReturnCode(); +} + +ReturnCodeOr WcmpManager::deserializeP4WcmpGroupAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + P4WcmpGroupEntry app_db_entry = {}; + try + { + nlohmann::json j = nlohmann::json::parse(key); + if (!j.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid WCMP group key: should be a JSON object."; + } + app_db_entry.wcmp_group_id = j[prependMatchField(kWcmpGroupId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize WCMP group key"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == kActions) + { + try + { + nlohmann::json j = nlohmann::json::parse(value); + if (!j.is_array()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid WCMP group actions " << QuotedVar(value) << ", expecting an array."; + } + for (auto &action_item : j) + { + std::shared_ptr wcmp_group_member = + std::make_shared(); + std::string action = action_item[kAction]; + if (action != kSetNexthopId) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected action " << QuotedVar(action) << " in WCMP group entry"; + } + if (action_item[prependParamField(kNexthopId)].empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Next hop id was not found in entry member for WCMP " + "group " + << QuotedVar(app_db_entry.wcmp_group_id); + } + wcmp_group_member->next_hop_id = action_item[prependParamField(kNexthopId)]; + if (!action_item[kWeight].empty()) + { + wcmp_group_member->weight = action_item[kWeight]; + } + if (!action_item[kWatchPort].empty()) + { + wcmp_group_member->watch_port = action_item[kWatchPort]; + } + wcmp_group_member->wcmp_group_id = app_db_entry.wcmp_group_id; + app_db_entry.wcmp_group_members.push_back(wcmp_group_member); + } + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to deserialize WCMP group actions fields: " << QuotedVar(value); + } + } + else if (field != kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +P4WcmpGroupEntry *WcmpManager::getWcmpGroupEntry(const std::string &wcmp_group_id) +{ + SWSS_LOG_ENTER(); + const auto &wcmp_group_it = m_wcmpGroupTable.find(wcmp_group_id); + if (wcmp_group_it == m_wcmpGroupTable.end()) + return nullptr; + return &wcmp_group_it->second; +} + +ReturnCode WcmpManager::processAddRequest(P4WcmpGroupEntry *app_db_entry) +{ + SWSS_LOG_ENTER(); + auto status = validateWcmpGroupEntry(*app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Invalid WCMP group with id %s: %s", QuotedVar(app_db_entry->wcmp_group_id).c_str(), + status.message().c_str()); + return status; + } + status = createWcmpGroup(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create WCMP group with id %s: %s", QuotedVar(app_db_entry->wcmp_group_id).c_str(), + status.message().c_str()); + } + return status; +} + +ReturnCode WcmpManager::createWcmpGroupMember(std::shared_ptr wcmp_group_member, + const sai_object_id_t group_oid, const std::string &wcmp_group_key) +{ + std::vector nhgm_attrs; + sai_attribute_t nhgm_attr; + sai_object_id_t next_hop_oid = SAI_NULL_OBJECT_ID; + m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id), + &next_hop_oid); + + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID; + nhgm_attr.value.oid = group_oid; + nhgm_attrs.push_back(nhgm_attr); + + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID; + nhgm_attr.value.oid = next_hop_oid; + nhgm_attrs.push_back(nhgm_attr); + + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT; + nhgm_attr.value.u32 = (uint32_t)wcmp_group_member->weight; + nhgm_attrs.push_back(nhgm_attr); + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_next_hop_group_api->create_next_hop_group_member(&wcmp_group_member->member_oid, gSwitchId, + (uint32_t)nhgm_attrs.size(), nhgm_attrs.data()), + "Failed to create next hop group member " << QuotedVar(wcmp_group_member->next_hop_id)); + + // Update reference count + const auto &next_hop_key = KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + getWcmpGroupMemberKey(wcmp_group_key, wcmp_group_member->member_oid), + wcmp_group_member->member_oid); + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + + return ReturnCode(); +} + +void WcmpManager::insertMemberInPortNameToWcmpGroupMemberMap(std::shared_ptr member) +{ + port_name_to_wcmp_group_member_map[member->watch_port].insert(member); +} + +void WcmpManager::removeMemberFromPortNameToWcmpGroupMemberMap(std::shared_ptr member) +{ + if (port_name_to_wcmp_group_member_map.find(member->watch_port) != port_name_to_wcmp_group_member_map.end()) + { + auto &s = port_name_to_wcmp_group_member_map[member->watch_port]; + auto it = s.find(member); + if (it != s.end()) + { + s.erase(it); + } + } +} + +ReturnCode WcmpManager::fetchPortOperStatus(const std::string &port_name, sai_port_oper_status_t *oper_status) +{ + if (!getPortOperStatusFromMap(port_name, oper_status)) + { + // Get port object for associated watch port + Port port; + if (!gPortsOrch->getPort(port_name, port)) + { + SWSS_LOG_ERROR("Failed to get port object for port %s", port_name.c_str()); + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM); + } + // Get the oper-status of the port from hardware. In case of warm reboot, + // this ensures that actual state of the port oper-status is used to + // determine whether member associated with watch_port is to be created in + // SAI. + if (!gPortsOrch->getPortOperStatus(port, *oper_status)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get port oper-status for port " << port.m_alias); + } + // Update port oper-status in local map + updatePortOperStatusMap(port.m_alias, *oper_status); + } + return ReturnCode(); +} + +ReturnCode WcmpManager::createWcmpGroupMemberWithWatchport(P4WcmpGroupEntry *wcmp_group, + std::shared_ptr member, + const std::string &wcmp_group_key) +{ + // Create member in SAI only for operationally up ports + sai_port_oper_status_t oper_status = SAI_PORT_OPER_STATUS_DOWN; + auto status = fetchPortOperStatus(member->watch_port, &oper_status); + if (!status.ok()) + { + return status; + } + + if (oper_status == SAI_PORT_OPER_STATUS_UP) + { + auto status = createWcmpGroupMember(member, wcmp_group->wcmp_group_oid, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create next hop member %s with watch_port %s", member->next_hop_id.c_str(), + member->watch_port.c_str()); + return status; + } + } + else + { + pruned_wcmp_members_set.emplace(member); + SWSS_LOG_NOTICE("Member %s in group %s not created in asic as the associated watchport " + "(%s) is not operationally up", + member->next_hop_id.c_str(), member->wcmp_group_id.c_str(), member->watch_port.c_str()); + } + // Add member to port_name_to_wcmp_group_member_map + insertMemberInPortNameToWcmpGroupMemberMap(member); + return ReturnCode(); +} + +ReturnCode WcmpManager::processWcmpGroupMemberAddition(std::shared_ptr member, + P4WcmpGroupEntry *wcmp_group, const std::string &wcmp_group_key) +{ + ReturnCode status = ReturnCode(); + if (!member->watch_port.empty()) + { + status = createWcmpGroupMemberWithWatchport(wcmp_group, member, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create WCMP group member %s with watch_port %s", member->next_hop_id.c_str(), + member->watch_port.c_str()); + } + } + else + { + status = createWcmpGroupMember(member, wcmp_group->wcmp_group_oid, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create WCMP group member %s", member->next_hop_id.c_str()); + } + } + return status; +} + +ReturnCode WcmpManager::processWcmpGroupMemberRemoval(std::shared_ptr member, + const std::string &wcmp_group_key) +{ + // If member exists in pruned_wcmp_members_set, remove from set. Else, remove + // member using SAI. + auto it = pruned_wcmp_members_set.find(member); + if (it != pruned_wcmp_members_set.end()) + { + pruned_wcmp_members_set.erase(it); + SWSS_LOG_NOTICE("Removed pruned member %s from group %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str()); + } + else + { + auto status = removeWcmpGroupMember(member, wcmp_group_key); + if (!status.ok()) + { + return status; + } + } + // Remove member from port_name_to_wcmp_group_member_map + removeMemberFromPortNameToWcmpGroupMemberMap(member); + return ReturnCode(); +} + +ReturnCode WcmpManager::createWcmpGroup(P4WcmpGroupEntry *wcmp_group) +{ + SWSS_LOG_ENTER(); + // Create SAI next hop group + sai_attribute_t nhg_attr; + std::vector nhg_attrs; + + // TODO: Update type to WCMP when SAI supports it. + nhg_attr.id = SAI_NEXT_HOP_GROUP_ATTR_TYPE; + nhg_attr.value.s32 = SAI_NEXT_HOP_GROUP_TYPE_ECMP; + nhg_attrs.push_back(nhg_attr); + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_group_api->create_next_hop_group(&wcmp_group->wcmp_group_oid, gSwitchId, + (uint32_t)nhg_attrs.size(), + nhg_attrs.data()), + "Failed to create next hop group " << QuotedVar(wcmp_group->wcmp_group_id)); + // Update reference count + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(wcmp_group->wcmp_group_id); + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, wcmp_group->wcmp_group_oid); + + // Create next hop group members + std::vector> created_wcmp_group_members; + ReturnCode status; + for (auto &wcmp_group_member : wcmp_group->wcmp_group_members) + { + status = processWcmpGroupMemberAddition(wcmp_group_member, wcmp_group, wcmp_group_key); + if (!status.ok()) + { + break; + } + created_wcmp_group_members.push_back(wcmp_group_member); + } + if (!status.ok()) + { + // Clean up created group members and the group + recoverGroupMembers(wcmp_group, wcmp_group_key, created_wcmp_group_members, {}); + auto sai_status = sai_next_hop_group_api->remove_next_hop_group(wcmp_group->wcmp_group_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + std::stringstream ss; + ss << "Failed to delete WCMP group with id " << QuotedVar(wcmp_group->wcmp_group_id); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", ss.str().c_str(), sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE(ss.str()); + } + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + return status; + } + m_wcmpGroupTable[wcmp_group->wcmp_group_id] = *wcmp_group; + return ReturnCode(); +} + +void WcmpManager::recoverGroupMembers( + p4orch::P4WcmpGroupEntry *wcmp_group_entry, const std::string &wcmp_group_key, + const std::vector> &created_wcmp_group_members, + const std::vector> &removed_wcmp_group_members) +{ + // Keep track of recovery status during clean up + ReturnCode recovery_status; + // Clean up created group members - remove created new members + for (const auto &new_member : created_wcmp_group_members) + { + auto status = processWcmpGroupMemberRemoval(new_member, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove created next hop group member %s in " + "processUpdateRequest().", + QuotedVar(new_member->next_hop_id).c_str()); + recovery_status.ok() ? recovery_status = status.prepend("Error during recovery: ") + : recovery_status << "; Error during recovery: " << status.message(); + } + } + // Clean up removed group members - create removed old members + for (auto &old_member : removed_wcmp_group_members) + { + auto status = processWcmpGroupMemberAddition(old_member, wcmp_group_entry, wcmp_group_key); + if (!status.ok()) + { + recovery_status.ok() ? recovery_status = status.prepend("Error during recovery: ") + : recovery_status << "; Error during recovery: " << status.message(); + } + } + if (!recovery_status.ok()) + SWSS_RAISE_CRITICAL_STATE(recovery_status.message()); +} + +ReturnCode WcmpManager::processUpdateRequest(P4WcmpGroupEntry *wcmp_group_entry) +{ + SWSS_LOG_ENTER(); + auto *old_wcmp = getWcmpGroupEntry(wcmp_group_entry->wcmp_group_id); + wcmp_group_entry->wcmp_group_oid = old_wcmp->wcmp_group_oid; + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(wcmp_group_entry->wcmp_group_id); + // Keep record of created next hop group members + std::vector> created_wcmp_group_members; + // Keep record of removed next hop group members + std::vector> removed_wcmp_group_members; + + // Update group members steps: + // 1. Find the old member in the list with the smallest weight + // 2. Find the new member in the list with the smallest weight + // 3. Make SAI calls to remove old members except the reserved member with the + // smallest weight + // 4. Make SAI call to create the new member with the smallest weight + // 5. Make SAI call to remove the reserved old member + // 6. Make SAI calls to create remaining new members + ReturnCode update_request_status; + auto find_smallest_index = [&](p4orch::P4WcmpGroupEntry *wcmp) { + if (wcmp->wcmp_group_members.empty()) + return -1; + int reserved_idx = 0; + for (int i = 1; i < (int)wcmp->wcmp_group_members.size(); i++) + { + if (wcmp->wcmp_group_members[i]->weight < wcmp->wcmp_group_members[reserved_idx]->weight) + { + reserved_idx = i; + } + } + return reserved_idx; + }; + // Find the old member who has the smallest weight, -1 if the member list is + // empty + int reserved_old_member_index = find_smallest_index(old_wcmp); + // Find the new member who has the smallest weight, -1 if the member list is + // empty + int reserved_new_member_index = find_smallest_index(wcmp_group_entry); + + // Remove stale group members except the member with the smallest weight + for (int i = 0; i < (int)old_wcmp->wcmp_group_members.size(); i++) + { + // Reserve the old member with smallest weight + if (i == reserved_old_member_index) + continue; + auto &stale_member = old_wcmp->wcmp_group_members[i]; + update_request_status = processWcmpGroupMemberRemoval(stale_member, wcmp_group_key); + if (!update_request_status.ok()) + { + SWSS_LOG_ERROR("Failed to remove stale next hop group member %s in " + "processUpdateRequest().", + QuotedVar(sai_serialize_object_id(stale_member->member_oid)).c_str()); + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + removed_wcmp_group_members.push_back(stale_member); + } + + // Create the new member with the smallest weight if member list is nonempty + if (!wcmp_group_entry->wcmp_group_members.empty()) + { + auto &member = wcmp_group_entry->wcmp_group_members[reserved_new_member_index]; + update_request_status = processWcmpGroupMemberAddition(member, wcmp_group_entry, wcmp_group_key); + if (!update_request_status.ok()) + { + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + created_wcmp_group_members.push_back(member); + } + + // Remove the old member with the smallest weight if member list is nonempty + if (!old_wcmp->wcmp_group_members.empty()) + { + auto &stale_member = old_wcmp->wcmp_group_members[reserved_old_member_index]; + update_request_status = processWcmpGroupMemberRemoval(stale_member, wcmp_group_key); + if (!update_request_status.ok()) + { + SWSS_LOG_ERROR("Failed to remove stale next hop group member %s in " + "processUpdateRequest().", + QuotedVar(sai_serialize_object_id(stale_member->member_oid)).c_str()); + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + removed_wcmp_group_members.push_back(stale_member); + } + + // Create new group members + for (int i = 0; i < (int)wcmp_group_entry->wcmp_group_members.size(); i++) + { + // Skip the new member with the lowest weight as it is already created + if (i == reserved_new_member_index) + continue; + auto &member = wcmp_group_entry->wcmp_group_members[i]; + // Create new group member + update_request_status = processWcmpGroupMemberAddition(member, wcmp_group_entry, wcmp_group_key); + if (!update_request_status.ok()) + { + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + created_wcmp_group_members.push_back(member); + } + + m_wcmpGroupTable[wcmp_group_entry->wcmp_group_id] = *wcmp_group_entry; + return update_request_status; +} + +ReturnCode WcmpManager::removeWcmpGroupMember(const std::shared_ptr wcmp_group_member, + const std::string &wcmp_group_key) +{ + SWSS_LOG_ENTER(); + const std::string &next_hop_key = KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id); + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_group_api->remove_next_hop_group_member(wcmp_group_member->member_oid), + "Failed to remove WCMP group member with nexthop id " + << QuotedVar(wcmp_group_member->next_hop_id)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + getWcmpGroupMemberKey(wcmp_group_key, wcmp_group_member->member_oid)); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + return ReturnCode(); +} + +ReturnCode WcmpManager::removeWcmpGroup(const std::string &wcmp_group_id) +{ + SWSS_LOG_ENTER(); + auto *wcmp_group = getWcmpGroupEntry(wcmp_group_id); + if (wcmp_group == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "WCMP group with id " << QuotedVar(wcmp_group_id) << " was not found."); + } + // Check refcount before deleting group members + uint32_t expected_refcount = (uint32_t)wcmp_group->wcmp_group_members.size(); + uint32_t wcmp_group_refcount = 0; + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(wcmp_group_id); + m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &wcmp_group_refcount); + if (wcmp_group_refcount > expected_refcount) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "Failed to remove WCMP group with id " << QuotedVar(wcmp_group_id) << ", as it has " + << wcmp_group_refcount - expected_refcount << " more objects than its group members (size=" + << expected_refcount << ") referencing it."); + } + std::vector> removed_wcmp_group_members; + ReturnCode status; + // Delete group members + for (const auto &member : wcmp_group->wcmp_group_members) + { + status = processWcmpGroupMemberRemoval(member, wcmp_group_key); + if (!status.ok()) + { + break; + } + removed_wcmp_group_members.push_back(member); + } + // Delete group + if (status.ok()) + { + auto sai_status = sai_next_hop_group_api->remove_next_hop_group(wcmp_group->wcmp_group_oid); + if (sai_status == SAI_STATUS_SUCCESS) + { + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP); + m_wcmpGroupTable.erase(wcmp_group->wcmp_group_id); + return ReturnCode(); + } + status = ReturnCode(sai_status) << "Failed to delete WCMP group with id " + << QuotedVar(wcmp_group->wcmp_group_id); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", status.message().c_str(), sai_serialize_status(sai_status).c_str()); + } + // Recover group members. + recoverGroupMembers(wcmp_group, wcmp_group_key, {}, removed_wcmp_group_members); + return status; +} + +void WcmpManager::pruneNextHops(const std::string &port) +{ + SWSS_LOG_ENTER(); + + // Get list of WCMP group members associated with the watch_port + if (port_name_to_wcmp_group_member_map.find(port) != port_name_to_wcmp_group_member_map.end()) + { + for (const auto &member : port_name_to_wcmp_group_member_map[port]) + { + auto it = pruned_wcmp_members_set.find(member); + // Prune a member if it is not already pruned. + if (it == pruned_wcmp_members_set.end()) + { + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(member->wcmp_group_id); + auto status = removeWcmpGroupMember(member, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_NOTICE("Failed to remove member %s from group %s, rv: %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str(), status.message().c_str()); + } + else + { + // Add pruned member to pruned set + pruned_wcmp_members_set.emplace(member); + SWSS_LOG_NOTICE("Pruned member %s from group %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str()); + } + } + } + } +} + +void WcmpManager::restorePrunedNextHops(const std::string &port) +{ + SWSS_LOG_ENTER(); + + // Get list of WCMP group members associated with the watch_port that were + // pruned + if (port_name_to_wcmp_group_member_map.find(port) != port_name_to_wcmp_group_member_map.end()) + { + ReturnCode status; + for (auto member : port_name_to_wcmp_group_member_map[port]) + { + auto it = pruned_wcmp_members_set.find(member); + if (it != pruned_wcmp_members_set.end()) + { + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(member->wcmp_group_id); + sai_object_id_t wcmp_group_oid = SAI_NULL_OBJECT_ID; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &wcmp_group_oid)) + { + status = ReturnCode(StatusCode::SWSS_RC_INTERNAL) + << "Error during restoring pruned next hop: Failed to get " + "WCMP group OID for group " + << member->wcmp_group_id; + SWSS_LOG_ERROR("%s", status.message().c_str()); + SWSS_RAISE_CRITICAL_STATE(status.message()); + return; + } + status = createWcmpGroupMember(member, wcmp_group_oid, wcmp_group_key); + if (!status.ok()) + { + status.prepend("Error during restoring pruned next hop: "); + SWSS_LOG_ERROR("%s", status.message().c_str()); + SWSS_RAISE_CRITICAL_STATE(status.message()); + return; + } + pruned_wcmp_members_set.erase(it); + SWSS_LOG_NOTICE("Restored pruned member %s in group %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str()); + } + } + } +} + +bool WcmpManager::getPortOperStatusFromMap(const std::string &port, sai_port_oper_status_t *oper_status) +{ + if (port_oper_status_map.find(port) != port_oper_status_map.end()) + { + *oper_status = port_oper_status_map[port]; + return true; + } + return false; +} + +void WcmpManager::updatePortOperStatusMap(const std::string &port, const sai_port_oper_status_t &status) +{ + port_oper_status_map[port] = status; +} + +void WcmpManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void WcmpManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeP4WcmpGroupAppDbEntry(db_key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB WCMP group entry with key %s: %s", + QuotedVar(table_name + ":" + db_key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *wcmp_group_entry = getWcmpGroupEntry(app_db_entry.wcmp_group_id); + if (wcmp_group_entry == nullptr) + { + // Create WCMP group + status = processAddRequest(&app_db_entry); + } + else + { + // Modify existing WCMP group + status = processUpdateRequest(&app_db_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete WCMP group + status = removeWcmpGroup(app_db_entry.wcmp_group_id); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown operation type: " << QuotedVar(operation) << " for WCMP group entry with key " + << QuotedVar(table_name) << ":" << QuotedVar(db_key) + << "; only SET and DEL operations are allowed."; + SWSS_LOG_ERROR("Unknown operation type %s\n", QuotedVar(operation).c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/wcmp_manager.h b/orchagent/p4orch/wcmp_manager.h new file mode 100644 index 0000000000..4c6629a398 --- /dev/null +++ b/orchagent/p4orch/wcmp_manager.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include + +#include "notificationconsumer.h" +#include "orch.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "response_publisher_interface.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +namespace p4orch +{ +namespace test +{ +class WcmpManagerTest; +} // namespace test + +struct P4WcmpGroupMemberEntry +{ + std::string next_hop_id; + // Default ECMP(weight=1) + int weight = 1; + std::string watch_port; + sai_object_id_t member_oid = SAI_NULL_OBJECT_ID; + std::string wcmp_group_id; +}; + +struct P4WcmpGroupEntry +{ + std::string wcmp_group_id; + // next_hop_id: P4WcmpGroupMemberEntry + std::vector> wcmp_group_members; + sai_object_id_t wcmp_group_oid = SAI_NULL_OBJECT_ID; +}; + +// WcmpManager listens to changes in table APP_P4RT_WCMP_GROUP_TABLE_NAME and +// creates/updates/deletes next hop group SAI object accordingly. Below is +// an example WCMP group table entry in APPL_DB. +// +// P4RT_TABLE:FIXED_WCMP_GROUP_TABLE:{"match/wcmp_group_id":"group-1"} +// "actions" =[ +// { +// "action": "set_nexthop_id", +// "param/nexthop_id": "node-1234:eth-1/2/3", +// "weight": 3, +// "watch_port": "Ethernet0", +// }, +// { +// "action": "set_nexthop_id", +// "param/nexthop_id": "node-2345:eth-1/2/3", +// "weight": 4, +// "watch_port": "Ethernet8", +// }, +// ] +// "controller_metadata" = "..." +class WcmpManager : public ObjectManagerInterface +{ + public: + WcmpManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + virtual ~WcmpManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + // Prunes next hop members egressing through the given port. + void pruneNextHops(const std::string &port); + + // Restores pruned next hop members on link up. Returns an SWSS status code. + void restorePrunedNextHops(const std::string &port); + + // Inserts into/updates port_oper_status_map + void updatePortOperStatusMap(const std::string &port, const sai_port_oper_status_t &status); + + private: + // Gets the internal cached WCMP group entry by its key. + // Return nullptr if corresponding WCMP group entry is not cached. + P4WcmpGroupEntry *getWcmpGroupEntry(const std::string &wcmp_group_id); + + // Deserializes an entry from table APP_P4RT_WCMP_GROUP_TABLE_NAME. + ReturnCodeOr deserializeP4WcmpGroupAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Perform validation on WCMP group entry. Return a SWSS status code + ReturnCode validateWcmpGroupEntry(const P4WcmpGroupEntry &app_db_entry); + + // Processes add operation for an entry. + ReturnCode processAddRequest(P4WcmpGroupEntry *app_db_entry); + + // Creates an WCMP group in the WCMP group table. + // validateWcmpGroupEntry() is required in caller function before + // createWcmpGroup() is called + ReturnCode createWcmpGroup(P4WcmpGroupEntry *wcmp_group_entry); + + // Creates WCMP group member in the WCMP group. + ReturnCode createWcmpGroupMember(std::shared_ptr wcmp_group_member, + const sai_object_id_t group_oid, const std::string &wcmp_group_key); + + // Creates WCMP group member with an associated watch_port. + ReturnCode createWcmpGroupMemberWithWatchport(P4WcmpGroupEntry *wcmp_group, + std::shared_ptr member, + const std::string &wcmp_group_key); + + // Performs watchport related addition operations and creates WCMP group + // member. + ReturnCode processWcmpGroupMemberAddition(std::shared_ptr member, + P4WcmpGroupEntry *wcmp_group, const std::string &wcmp_group_key); + + // Performs watchport related removal operations and removes WCMP group + // member. + ReturnCode processWcmpGroupMemberRemoval(std::shared_ptr member, + const std::string &wcmp_group_key); + + // Processes update operation for a WCMP group entry. + ReturnCode processUpdateRequest(P4WcmpGroupEntry *wcmp_group_entry); + + // Clean up group members when request fails + void recoverGroupMembers( + p4orch::P4WcmpGroupEntry *wcmp_group, const std::string &wcmp_group_key, + const std::vector> &created_wcmp_group_members, + const std::vector> &removed_wcmp_group_members); + + // Deletes a WCMP group in the WCMP group table. + ReturnCode removeWcmpGroup(const std::string &wcmp_group_id); + + // Deletes a WCMP group member in the WCMP group table. + ReturnCode removeWcmpGroupMember(const std::shared_ptr wcmp_group_member, + const std::string &wcmp_group_id); + + // Fetches oper-status of port using port_oper_status_map or SAI. + ReturnCode fetchPortOperStatus(const std::string &port, sai_port_oper_status_t *oper_status); + + // Inserts a next hop member in port_name_to_wcmp_group_member_map + void insertMemberInPortNameToWcmpGroupMemberMap(std::shared_ptr member); + + // Removes a next hop member from port_name_to_wcmp_group_member_map + void removeMemberFromPortNameToWcmpGroupMemberMap(std::shared_ptr member); + + // Gets port oper-status from port_oper_status_map if present + bool getPortOperStatusFromMap(const std::string &port, sai_port_oper_status_t *status); + + // Maps wcmp_group_id to P4WcmpGroupEntry + std::unordered_map m_wcmpGroupTable; + + // Maps port name to P4WcmpGroupMemberEntry + std::unordered_map>> + port_name_to_wcmp_group_member_map; + + // Set of pruned P4WcmpGroupMemberEntry entries + std::unordered_set> pruned_wcmp_members_set; + + // Maps port name to oper-status + std::unordered_map port_oper_status_map; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + std::deque m_entries; + ResponsePublisherInterface *m_publisher; + + friend class p4orch::test::WcmpManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/pbh/pbhrule.cpp b/orchagent/pbh/pbhrule.cpp index 52812e35b6..7d35f4bb8f 100644 --- a/orchagent/pbh/pbhrule.cpp +++ b/orchagent/pbh/pbhrule.cpp @@ -3,7 +3,7 @@ #include "pbhrule.h" AclRulePbh::AclRulePbh(AclOrch *pAclOrch, string rule, string table, bool createCounter) : - AclRule(pAclOrch, rule, table, ACL_TABLE_PBH, createCounter) + AclRule(pAclOrch, rule, table, createCounter) { } @@ -11,15 +11,7 @@ bool AclRulePbh::validateAddPriority(const sai_uint32_t &value) { SWSS_LOG_ENTER(); - if ((value < m_minPriority) || (value > m_maxPriority)) - { - SWSS_LOG_ERROR("Failed to validate priority: invalid value %d", value); - return false; - } - - m_priority = value; - - return true; + return setPriority(value); } bool AclRulePbh::validateAddMatch(const sai_attribute_t &attr) @@ -52,9 +44,7 @@ bool AclRulePbh::validateAddMatch(const sai_attribute_t &attr) return false; } - m_matches[attrId] = attr.value; - - return true; + return setMatch(attrId, attr.value.aclfield); } bool AclRulePbh::validateAddAction(const sai_attribute_t &attr) @@ -83,15 +73,7 @@ bool AclRulePbh::validateAddAction(const sai_attribute_t &attr) return false; } - if (!AclRule::isActionSupported(attrId)) - { - SWSS_LOG_ERROR("Action %s is not supported by ASIC", attrName.c_str()); - return false; - } - - m_actions[attrId] = attr.value; - - return true; + return setAction(attrId, attr.value.aclaction); } bool AclRulePbh::validate() @@ -107,7 +89,12 @@ bool AclRulePbh::validate() return true; } -void AclRulePbh::update(SubjectType, void *) +void AclRulePbh::onUpdate(SubjectType, void *) { // Do nothing } + +bool AclRulePbh::validateAddAction(string attr_name, string attr_value) +{ + SWSS_LOG_THROW("This API should not be used on PbhRule"); +} diff --git a/orchagent/pbh/pbhrule.h b/orchagent/pbh/pbhrule.h index 3e753a0f03..9e661761c4 100644 --- a/orchagent/pbh/pbhrule.h +++ b/orchagent/pbh/pbhrule.h @@ -11,5 +11,6 @@ class AclRulePbh: public AclRule bool validateAddMatch(const sai_attribute_t &attr); bool validateAddAction(const sai_attribute_t &attr); bool validate() override; - void update(SubjectType, void *) override; + void onUpdate(SubjectType, void *) override; + bool validateAddAction(string attr_name, string attr_value) override; }; diff --git a/orchagent/pbhorch.cpp b/orchagent/pbhorch.cpp index 3d0b43ce01..83a1e80bd0 100644 --- a/orchagent/pbhorch.cpp +++ b/orchagent/pbhorch.cpp @@ -180,7 +180,18 @@ bool PbhOrch::createPbhTable(const PbhTable &table) AclTable pbhTable(this->aclOrch, table.name); - if (!pbhTable.validateAddType(acl_table_type_t::ACL_TABLE_PBH)) + static const auto pbhTableType = AclTableTypeBuilder() + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_GRE_KEY)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE)) + .build(); + + if (!pbhTable.validateAddType(pbhTableType)) { SWSS_LOG_ERROR("Failed to configure PBH table(%s) type", table.key.c_str()); return false; diff --git a/orchagent/pfcactionhandler.cpp b/orchagent/pfcactionhandler.cpp index 0a24b1ba01..e44521f849 100644 --- a/orchagent/pfcactionhandler.cpp +++ b/orchagent/pfcactionhandler.cpp @@ -301,20 +301,20 @@ PfcWdAclHandler::PfcWdAclHandler(sai_object_id_t port, sai_object_id_t queue, { SWSS_LOG_ENTER(); - acl_table_type_t table_type; + string table_type; string queuestr = to_string(queueId); m_strRule = "Rule_PfcWdAclHandler_" + queuestr; // Ingress table/rule creation - table_type = ACL_TABLE_DROP; + table_type = TABLE_TYPE_DROP; m_strIngressTable = INGRESS_TABLE_DROP; auto found = m_aclTables.find(m_strIngressTable); if (found == m_aclTables.end()) { // First time of handling PFC for this queue, create ACL table, and bind createPfcAclTable(port, m_strIngressTable, true); - shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable, table_type); + shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable); createPfcAclRule(newRule, queueId, m_strIngressTable, port); } else @@ -322,7 +322,7 @@ PfcWdAclHandler::PfcWdAclHandler(sai_object_id_t port, sai_object_id_t queue, AclRule* rule = gAclOrch->getAclRule(m_strIngressTable, m_strRule); if (rule == nullptr) { - shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable, table_type); + shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable); createPfcAclRule(newRule, queueId, m_strIngressTable, port); } else @@ -332,14 +332,14 @@ PfcWdAclHandler::PfcWdAclHandler(sai_object_id_t port, sai_object_id_t queue, } // Egress table/rule creation - table_type = ACL_TABLE_PFCWD; + table_type = TABLE_TYPE_PFCWD; m_strEgressTable = "EgressTable_PfcWdAclHandler_" + queuestr; found = m_aclTables.find(m_strEgressTable); if (found == m_aclTables.end()) { // First time of handling PFC for this queue, create ACL table, and bind createPfcAclTable(port, m_strEgressTable, false); - shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strEgressTable, table_type); + shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strEgressTable); createPfcAclRule(newRule, queueId, m_strEgressTable, port); } else @@ -392,7 +392,7 @@ void PfcWdAclHandler::createPfcAclTable(sai_object_id_t port, string strTable, b auto inserted = m_aclTables.emplace(piecewise_construct, std::forward_as_tuple(strTable), - std::forward_as_tuple()); + std::forward_as_tuple(gAclOrch, strTable)); assert(inserted.second); @@ -408,23 +408,26 @@ void PfcWdAclHandler::createPfcAclTable(sai_object_id_t port, string strTable, b } aclTable.link(port); - aclTable.id = strTable; if (ingress) { - aclTable.type = ACL_TABLE_DROP; + auto dropType = gAclOrch->getAclTableType(TABLE_TYPE_DROP); + assert(dropType); + aclTable.validateAddType(*dropType); aclTable.stage = ACL_STAGE_INGRESS; } else { - aclTable.type = ACL_TABLE_PFCWD; + auto pfcwdType = gAclOrch->getAclTableType(TABLE_TYPE_PFCWD); + assert(pfcwdType); + aclTable.validateAddType(*pfcwdType); aclTable.stage = ACL_STAGE_EGRESS; } gAclOrch->addAclTable(aclTable); } -void PfcWdAclHandler::createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t portOid) +void PfcWdAclHandler::createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t portOid) { SWSS_LOG_ENTER(); diff --git a/orchagent/pfcactionhandler.h b/orchagent/pfcactionhandler.h index 381f9bdca8..23cabaee10 100644 --- a/orchagent/pfcactionhandler.h +++ b/orchagent/pfcactionhandler.h @@ -111,7 +111,7 @@ class PfcWdAclHandler: public PfcWdLossyHandler string m_strEgressTable; string m_strRule; void createPfcAclTable(sai_object_id_t port, string strTable, bool ingress); - void createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t port); + void createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t port); void updatePfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, vector port); }; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index b112e6765c..b38164d36c 100755 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -5,6 +5,7 @@ #include "gearboxutils.h" #include "vxlanorch.h" #include "directory.h" +#include "subintf.h" #include #include @@ -807,7 +808,7 @@ bool PortsOrch::getPortByBridgePortId(sai_object_id_t bridge_port_id, Port &port return false; } -bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, const uint32_t &mtu) +bool PortsOrch::addSubPort(Port &port, const string &alias, const string &vlan, const bool &adminUp, const uint32_t &mtu) { SWSS_LOG_ENTER(); @@ -817,21 +818,21 @@ bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, SWSS_LOG_ERROR("%s is not a sub interface", alias.c_str()); return false; } - string parentAlias = alias.substr(0, found); - string vlanId = alias.substr(found + 1); + subIntf subIf(alias); + string parentAlias = subIf.parentIntf(); sai_vlan_id_t vlan_id; try { - vlan_id = static_cast(stoul(vlanId)); + vlan_id = static_cast(stoul(vlan)); } catch (const std::invalid_argument &e) { - SWSS_LOG_ERROR("Invalid argument %s to %s()", vlanId.c_str(), e.what()); + SWSS_LOG_ERROR("Invalid argument %s to %s()", vlan.c_str(), e.what()); return false; } catch (const std::out_of_range &e) { - SWSS_LOG_ERROR("Out of range argument %s to %s()", vlanId.c_str(), e.what()); + SWSS_LOG_ERROR("Out of range argument %s to %s()", vlan.c_str(), e.what()); return false; } if (vlan_id > MAX_VALID_VLAN_ID) diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index eb532c4b1b..93ff93bb9e 100755 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -132,7 +132,7 @@ class PortsOrch : public Orch, public Subject void refreshPortStatus(); bool removeAclTableGroup(const Port &p); - bool addSubPort(Port &port, const string &alias, const bool &adminUp = true, const uint32_t &mtu = 0); + bool addSubPort(Port &port, const string &alias, const string &vlan, const bool &adminUp = true, const uint32_t &mtu = 0); bool removeSubPort(const string &alias); bool updateL3VniStatus(uint16_t vlan_id, bool status); void getLagMember(Port &lag, vector &portv); diff --git a/orchagent/qosorch.cpp b/orchagent/qosorch.cpp index 0431b369d3..edd5db3443 100644 --- a/orchagent/qosorch.cpp +++ b/orchagent/qosorch.cpp @@ -14,15 +14,16 @@ using namespace std; +extern sai_switch_api_t *sai_switch_api; extern sai_port_api_t *sai_port_api; extern sai_queue_api_t *sai_queue_api; extern sai_scheduler_api_t *sai_scheduler_api; extern sai_wred_api_t *sai_wred_api; extern sai_qos_map_api_t *sai_qos_map_api; extern sai_scheduler_group_api_t *sai_scheduler_group_api; -extern sai_switch_api_t *sai_switch_api; extern sai_acl_api_t* sai_acl_api; +extern SwitchOrch *gSwitchOrch; extern PortsOrch *gPortsOrch; extern sai_object_id_t gSwitchId; extern CrmOrch *gCrmOrch; @@ -217,6 +218,34 @@ bool DscpToTcMapHandler::convertFieldValuesToAttributes(KeyOpFieldsValuesTuple & return true; } +void DscpToTcMapHandler::applyDscpToTcMapToSwitch(sai_attr_id_t attr_id, sai_object_id_t map_id) +{ + SWSS_LOG_ENTER(); + bool rv = true; + + /* Query DSCP_TO_TC QoS map at switch capability */ + rv = gSwitchOrch->querySwitchDscpToTcCapability(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP); + if (rv == false) + { + SWSS_LOG_ERROR("Switch level DSCP to TC QoS map configuration is not supported"); + return; + } + + /* Apply DSCP_TO_TC QoS map at switch */ + sai_attribute_t attr; + attr.id = attr_id; + attr.value.oid = map_id; + + sai_status_t status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to apply DSCP_TO_TC QoS map to switch rv:%d", status); + return; + } + + SWSS_LOG_NOTICE("Applied DSCP_TO_TC QoS map to switch successfully"); +} + sai_object_id_t DscpToTcMapHandler::addQosItem(const vector &attributes) { SWSS_LOG_ENTER(); @@ -241,6 +270,9 @@ sai_object_id_t DscpToTcMapHandler::addQosItem(const vector &at return SAI_NULL_OBJECT_ID; } SWSS_LOG_DEBUG("created QosMap object:%" PRIx64, sai_object); + + applyDscpToTcMapToSwitch(SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP, sai_object); + return sai_object; } diff --git a/orchagent/qosorch.h b/orchagent/qosorch.h index 69e7cef70b..cd265d59ec 100644 --- a/orchagent/qosorch.h +++ b/orchagent/qosorch.h @@ -5,6 +5,7 @@ #include #include #include "orch.h" +#include "switchorch.h" #include "portsorch.h" const string dscp_to_tc_field_name = "dscp_to_tc_map"; @@ -71,6 +72,8 @@ class DscpToTcMapHandler : public QosMapHandler public: bool convertFieldValuesToAttributes(KeyOpFieldsValuesTuple &tuple, vector &attributes) override; sai_object_id_t addQosItem(const vector &attributes) override; +protected: + void applyDscpToTcMapToSwitch(sai_attr_id_t attr_id, sai_object_id_t sai_dscp_to_tc_map); }; class MplsTcToTcMapHandler : public QosMapHandler diff --git a/orchagent/response_publisher.cpp b/orchagent/response_publisher.cpp new file mode 100644 index 0000000000..5d0490167c --- /dev/null +++ b/orchagent/response_publisher.cpp @@ -0,0 +1,191 @@ +#include "response_publisher.h" + +#include +#include +#include +#include + +#include "timestamp.h" + +extern bool gResponsePublisherRecord; +extern bool gResponsePublisherLogRotate; +extern std::ofstream gResponsePublisherRecordOfs; +extern std::string gResponsePublisherRecordFile; + +namespace +{ + +// Returns the component string that we need to prepend for sending the error +// message. +// Returns an empty string if the status is OK. +// Returns "[SAI] " if the ReturnCode is generated from a SAI status code. +// Else, returns "[OrchAgent] ". +std::string PrependedComponent(const ReturnCode &status) +{ + constexpr char *kOrchagentComponent = "[OrchAgent] "; + constexpr char *kSaiComponent = "[SAI] "; + if (status.ok()) + { + return ""; + } + if (status.isSai()) + { + return kSaiComponent; + } + return kOrchagentComponent; +} + +void PerformLogRotate() +{ + if (!gResponsePublisherLogRotate) + { + return; + } + gResponsePublisherLogRotate = false; + + gResponsePublisherRecordOfs.close(); + gResponsePublisherRecordOfs.open(gResponsePublisherRecordFile); + if (!gResponsePublisherRecordOfs.is_open()) + { + SWSS_LOG_ERROR("Failed to reopen Response Publisher record file %s: %s", gResponsePublisherRecordFile.c_str(), + strerror(errno)); + } +} + +void RecordDBWrite(const std::string &table, const std::string &key, const std::vector &attrs, + const std::string &op) +{ + if (!gResponsePublisherRecord) + { + return; + } + + std::string s = table + ":" + key + "|" + op; + for (const auto &attr : attrs) + { + s += "|" + fvField(attr) + ":" + fvValue(attr); + } + + PerformLogRotate(); + gResponsePublisherRecordOfs << swss::getTimestamp() << "|" << s << std::endl; +} + +void RecordResponse(const std::string &response_channel, const std::string &key, + const std::vector &attrs, const std::string &status) +{ + if (!gResponsePublisherRecord) + { + return; + } + + std::string s = response_channel + ":" + key + "|" + status; + for (const auto &attr : attrs) + { + s += "|" + fvField(attr) + ":" + fvValue(attr); + } + + PerformLogRotate(); + gResponsePublisherRecordOfs << swss::getTimestamp() << "|" << s << std::endl; +} + +} // namespace + +ResponsePublisher::ResponsePublisher() : m_db("APPL_STATE_DB", 0) +{ +} + +void ResponsePublisher::publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + const std::vector &state_attrs, bool replace) +{ + // Write to the DB only if: + // 1) A write operation is being performed and state attributes are specified. + // 2) A successful delete operation. + if ((intent_attrs.size() && state_attrs.size()) || (status.ok() && !intent_attrs.size())) + { + writeToDB(table, key, state_attrs, intent_attrs.size() ? SET_COMMAND : DEL_COMMAND, replace); + } + + std::string response_channel = "APPL_DB_" + table + "_RESPONSE_CHANNEL"; + if (m_notifiers.find(table) == m_notifiers.end()) + { + m_notifiers[table] = std::make_unique(&m_db, response_channel); + } + + auto intent_attrs_copy = intent_attrs; + // Add error message as the first field-value-pair. + swss::FieldValueTuple err_str("err_str", PrependedComponent(status) + status.message()); + intent_attrs_copy.insert(intent_attrs_copy.begin(), err_str); + // Sends the response to the notification channel. + m_notifiers[table]->send(status.codeStr(), key, intent_attrs_copy); + RecordResponse(response_channel, key, intent_attrs_copy, status.codeStr()); +} + +void ResponsePublisher::publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + bool replace) +{ + // If status is OK then intent attributes need to be written in + // APPL_STATE_DB. In this case, pass the intent attributes as state + // attributes. In case of a failure status, nothing needs to be written in + // APPL_STATE_DB. + std::vector state_attrs; + if (status.ok()) + { + state_attrs = intent_attrs; + } + publish(table, key, intent_attrs, status, state_attrs, replace); +} + +void ResponsePublisher::writeToDB(const std::string &table, const std::string &key, + const std::vector &values, const std::string &op, bool replace) +{ + if (m_tables.find(table) == m_tables.end()) + { + m_tables[table] = std::make_unique(&m_db, table); + } + + auto attrs = values; + if (op == SET_COMMAND) + { + if (replace) + { + m_tables[table]->del(key); + } + if (!values.size()) + { + attrs.push_back(swss::FieldValueTuple("NULL", "NULL")); + } + + // Write to DB only if the key does not exist or non-NULL attributes are + // being written to the entry. + std::vector fv; + if (!m_tables[table]->get(key, fv)) + { + m_tables[table]->set(key, attrs); + RecordDBWrite(table, key, attrs, op); + return; + } + for (auto it = attrs.cbegin(); it != attrs.cend();) + { + if (it->first == "NULL") + { + it = attrs.erase(it); + } + else + { + it++; + } + } + if (attrs.size()) + { + m_tables[table]->set(key, attrs); + RecordDBWrite(table, key, attrs, op); + } + } + else if (op == DEL_COMMAND) + { + m_tables[table]->del(key); + RecordDBWrite(table, key, {}, op); + } +} diff --git a/orchagent/response_publisher.h b/orchagent/response_publisher.h new file mode 100644 index 0000000000..cd688112e8 --- /dev/null +++ b/orchagent/response_publisher.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include "dbconnector.h" +#include "notificationproducer.h" +#include "response_publisher_interface.h" +#include "table.h" + +// This class performs two tasks when publish is called: +// 1. Sends a notification into the redis channel. +// 2. Writes the operation into the DB. +class ResponsePublisher : public ResponsePublisherInterface +{ + public: + explicit ResponsePublisher(); + virtual ~ResponsePublisher() = default; + + // Intent attributes are the attributes sent in the notification into the + // redis channel. + // State attributes are the list of attributes that need to be written in + // the DB namespace. These might be different from intent attributes. For + // example: + // 1) If only a subset of the intent attributes were successfully applied, the + // state attributes shall be different from intent attributes. + // 2) If additional state changes occur due to the intent attributes, more + // attributes need to be added in the state DB namespace. + // 3) Invalid attributes are excluded from the state attributes. + // State attributes will be written into the DB even if the status code + // consists of an error. + void publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + const std::vector &state_attrs, bool replace = false) override; + + void publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + bool replace = false) override; + + void writeToDB(const std::string &table, const std::string &key, const std::vector &values, + const std::string &op, bool replace = false) override; + + private: + swss::DBConnector m_db; + // Maps table names to tables. + std::unordered_map> m_tables; + // Maps table names to notifiers. + std::unordered_map> m_notifiers; +}; diff --git a/orchagent/response_publisher_interface.h b/orchagent/response_publisher_interface.h new file mode 100644 index 0000000000..92d364a500 --- /dev/null +++ b/orchagent/response_publisher_interface.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "return_code.h" +#include "table.h" + +class ResponsePublisherInterface { + public: + virtual ~ResponsePublisherInterface() = default; + + // Publishes the response status. + // If intent attributes are empty, it is a delete operation. + // What "publish" needs to do is completely up to implementation. + // This API does not include redis DB namespace. So if implementation chooses + // to write to a redis DB, it will need to use a fixed namespace. + // The replace flag indicates the state attributes will replace the old ones. + virtual void publish(const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, + const std::vector& state_attrs, + bool replace = false) = 0; + + // Publishes response status. If response status is OK then also writes the + // intent attributes into the DB. + // The replace flag indicates a replace operation. + virtual void publish(const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, bool replace = false) = 0; + + // Write to DB only. This API does not send notification. + // The replace flag indicates the new attributes will replace the old ones. + virtual void writeToDB(const std::string& table, const std::string& key, + const std::vector& values, + const std::string& op, bool replace = false) = 0; +}; diff --git a/orchagent/return_code.h b/orchagent/return_code.h new file mode 100644 index 0000000000..87a1a761e1 --- /dev/null +++ b/orchagent/return_code.h @@ -0,0 +1,373 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "sai_serialize.h" +#include "status_code_util.h" + +extern "C" +{ +#include "sai.h" +} + +using swss::StatusCode; + +// RETURN_IF_ERROR evaluates an expression that returns a ReturnCode. If the +// result is not ok, returns the result. Otherwise, continues. +// +// Example: +// ReturnCode Foo() {...} +// ReturnCode Bar() { +// RETURN_IF_ERROR(Foo()); +// return ReturnCode(); +// } +#define RETURN_IF_ERROR(expr) \ + do \ + { \ + ReturnCode RETURN_IF_ERROR_RC_ = expr; \ + if (!RETURN_IF_ERROR_RC_.ok()) \ + return RETURN_IF_ERROR_RC_; \ + } while (0) + +// LOG_ERROR_AND_RETURN evaluates an expression that should returns an error +// ReturnCode. Logs the error message in the ReturnCode by calling +// SWSS_LOG_ERROR and returns. +#define LOG_ERROR_AND_RETURN(expr) \ + do \ + { \ + ReturnCode LOG_ERROR_AND_RETURN_RC_ = expr; \ + SWSS_LOG_ERROR("%s", LOG_ERROR_AND_RETURN_RC_.message().c_str()); \ + return LOG_ERROR_AND_RETURN_RC_; \ + } while (0) + +// Same as RETURN_IF_ERROR, plus a call of SWSS_LOG_ERROR for the return code +// error message. +#define LOG_AND_RETURN_IF_ERROR(expr) \ + do \ + { \ + ReturnCode LOG_AND_RETURN_IF_ERROR_RC_ = expr; \ + if (!LOG_AND_RETURN_IF_ERROR_RC_.ok()) \ + { \ + SWSS_LOG_ERROR("%s", LOG_AND_RETURN_IF_ERROR_RC_.message().c_str()); \ + return LOG_AND_RETURN_IF_ERROR_RC_; \ + } \ + } while (0) + +#define RETURNCODE_MACROS_IMPL_CONCAT_INNER_(x, y) x##y + +#define RETURNCODE_MACROS_IMPL_CONCAT_(x, y) RETURNCODE_MACROS_IMPL_CONCAT_INNER_(x, y) + +// ASSIGN_OR_RETURN evaluates an expression that returns a ReturnCodeOr. If the +// result is ok, the value is saved to dest. Otherwise, the ReturnCode is +// returned. +// +// Example: +// ReturnCodeOr Foo() {...} +// ReturnCode Bar() { +// ASSIGN_OR_RETURN(int value, Foo()); +// std::cout << "value: " << value; +// return ReturnCode(); +// } +#define ASSIGN_OR_RETURN(dest, expr) \ + auto RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__) = expr; \ + if (!RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__).ok()) \ + { \ + return RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__).status(); \ + } \ + dest = std::move(RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__).value()) + +// CHECK_ERROR_AND_LOG evaluates an expression that returns a sai_status_t. If +// the result is not SAI_STATUS_SUCCESS, it will log an error message. +// +// Example: +// CHECK_ERROR_AND_LOG( +// sai_router_intfs_api->set_router_interface_attribute(...), +// "error message" << " stream"); +#define CHECK_ERROR_AND_LOG(expr, msg_stream) \ + do \ + { \ + sai_status_t CHECK_ERROR_AND_LOG_SAI_ = expr; \ + if (CHECK_ERROR_AND_LOG_SAI_ != SAI_STATUS_SUCCESS) \ + { \ + std::stringstream CHECK_ERROR_AND_LOG_SS_; \ + CHECK_ERROR_AND_LOG_SS_ << msg_stream; \ + SWSS_LOG_ERROR("%s SAI_STATUS: %s", CHECK_ERROR_AND_LOG_SS_.str().c_str(), \ + sai_serialize_status(CHECK_ERROR_AND_LOG_SAI_).c_str()); \ + } \ + } while (0) + +// CHECK_ERROR_AND_LOG_AND_RETURN evaluates an expression that returns a +// sai_status_t. If the result is not SAI_STATUS_SUCCESS, it will log an error +// message and return a ReturnCode. +// +// Example: +// CHECK_ERROR_AND_LOG_AND_RETURN( +// sai_router_intfs_api->set_router_interface_attribute(...), +// "error message" << " stream"); +#define CHECK_ERROR_AND_LOG_AND_RETURN(expr, msg_stream) \ + do \ + { \ + sai_status_t CHECK_ERROR_AND_LOG_AND_RETURN_SAI_ = expr; \ + if (CHECK_ERROR_AND_LOG_AND_RETURN_SAI_ != SAI_STATUS_SUCCESS) \ + { \ + ReturnCode CHECK_ERROR_AND_LOG_AND_RETURN_RC_ = ReturnCode(CHECK_ERROR_AND_LOG_AND_RETURN_SAI_) \ + << msg_stream; \ + SWSS_LOG_ERROR("%s SAI_STATUS: %s", CHECK_ERROR_AND_LOG_AND_RETURN_RC_.message().c_str(), \ + sai_serialize_status(CHECK_ERROR_AND_LOG_AND_RETURN_SAI_).c_str()); \ + return CHECK_ERROR_AND_LOG_AND_RETURN_RC_; \ + } \ + } while (0) + +// This macro raises critical state to indicate that something is seriously +// wrong in the system. Currently, this macro just logs an error message. +// TODO: Implement this macro. +#define SWSS_RAISE_CRITICAL_STATE(err_str) \ + do \ + { \ + std::string err_msge = err_str; \ + SWSS_LOG_ERROR("Orchagent is in critical state: %s", err_msge.c_str()); \ + } while (0) + +// RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL returns an error status of +// SWSS_RC_INTERNAL. It also logs the error message and reports critical state. +#define RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL(msg_stream) \ + do \ + { \ + ReturnCode RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_ = ReturnCode(StatusCode::SWSS_RC_INTERNAL) \ + << msg_stream; \ + SWSS_LOG_ERROR("%s", RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_.message().c_str()); \ + SWSS_RAISE_CRITICAL_STATE(RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_.message()); \ + return RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_; \ + } while (0) + +class ReturnCode +{ + public: + ReturnCode() + : status_(StatusCode::SWSS_RC_SUCCESS), stream_(std::ios_base::out | std::ios_base::ate), is_sai_(false) + { + } + + ReturnCode(const StatusCode &status, const std::string &message = "") + : status_(status), stream_(std::ios_base::out | std::ios_base::ate), is_sai_(false) + { + stream_ << message; + } + + ReturnCode(const sai_status_t &status, const std::string &message = "") + : stream_(std::ios_base::out | std::ios_base::ate), is_sai_(true) + { + if (m_saiStatusCodeLookup.find(status) == m_saiStatusCodeLookup.end()) + { + status_ = StatusCode::SWSS_RC_UNKNOWN; + } + else + { + status_ = m_saiStatusCodeLookup[status]; + } + stream_ << message; + } + + ReturnCode(const ReturnCode &return_code) : stream_(std::ios_base::out | std::ios_base::ate) + { + status_ = return_code.status_; + stream_ << return_code.stream_.str(); + is_sai_ = return_code.is_sai_; + } + + ReturnCode &operator=(const ReturnCode &return_code) + { + status_ = return_code.status_; + stream_.str(return_code.stream_.str()); + is_sai_ = return_code.is_sai_; + return *this; + } + + ~ReturnCode() = default; + + bool ok() const + { + return status_ == StatusCode::SWSS_RC_SUCCESS; + } + + StatusCode code() const + { + return status_; + } + + std::string codeStr() const + { + return swss::statusCodeToStr(status_); + } + + std::string message() const + { + if (stream_.str().empty()) + { + return codeStr(); + } + return stream_.str(); + } + + ReturnCode &prepend(const std::string &msg) + { + const std::string &tmp = stream_.str(); + stream_.str(msg + tmp); + return *this; + } + + std::string toString() const + { + return codeStr() + ":" + message(); + } + + // Whether the ReturnCode is generated from a SAI status code or not. + bool isSai() const + { + return is_sai_; + } + + template ReturnCode &operator<<(T val) + { + stream_ << val; + return *this; + } + + bool operator==(const ReturnCode &x) const + { + return status_ == x.status_ && message() == x.message(); + } + + bool operator!=(const ReturnCode &x) const + { + return status_ != x.status_ || message() != x.message(); + } + + bool operator==(const StatusCode &x) const + { + return status_ == x; + } + + bool operator!=(const StatusCode &x) const + { + return status_ != x; + } + + private: + // SAI codes that are not included in this lookup map will map to + // SWSS_RC_UNKNOWN. This includes the general SAI failure: SAI_STATUS_FAILURE. + std::unordered_map m_saiStatusCodeLookup = { + {SAI_STATUS_SUCCESS, StatusCode::SWSS_RC_SUCCESS}, + {SAI_STATUS_NOT_SUPPORTED, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_NO_MEMORY, StatusCode::SWSS_RC_NO_MEMORY}, + {SAI_STATUS_INSUFFICIENT_RESOURCES, StatusCode::SWSS_RC_FULL}, + {SAI_STATUS_INVALID_PARAMETER, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_ITEM_ALREADY_EXISTS, StatusCode::SWSS_RC_EXISTS}, + {SAI_STATUS_ITEM_NOT_FOUND, StatusCode::SWSS_RC_NOT_FOUND}, + {SAI_STATUS_TABLE_FULL, StatusCode::SWSS_RC_FULL}, + {SAI_STATUS_NOT_IMPLEMENTED, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_OBJECT_IN_USE, StatusCode::SWSS_RC_IN_USE}, + }; + + StatusCode status_; + std::stringstream stream_; + // Whether the ReturnCode is generated from a SAI status code or not. + bool is_sai_; +}; + +inline bool operator==(const StatusCode &lhs, const ReturnCode &rhs) +{ + return lhs == rhs.code(); +} + +inline bool operator!=(const StatusCode &lhs, const ReturnCode &rhs) +{ + return lhs != rhs.code(); +} + +template class ReturnCodeOr +{ + public: + using value_type = T; + + // Value Constructors. + ReturnCodeOr(const T &value) : return_code_(ReturnCode()), value_(std::unique_ptr(new T(value))) + { + } + ReturnCodeOr(T &&value) : return_code_(ReturnCode()), value_(std::unique_ptr(new T(std::move(value)))) + { + } + + // ReturnCode constructors. + ReturnCodeOr(const ReturnCode &return_code) : return_code_(return_code) + { + assert(!return_code.ok()); + } + + // ReturnCode accessors. + bool ok() const + { + return return_code_.ok(); + } + const ReturnCode &status() const + { + return return_code_; + } + + // Value accessors. + const T &value() const & + { + assert(return_code_.ok()); + return *value_; + } + T &value() & + { + assert(return_code_.ok()); + return *value_; + } + const T &&value() const && + { + assert(return_code_.ok()); + return std::move(*value_); + } + T &&value() && + { + assert(return_code_.ok()); + return std::move(*value_); + } + + const T &operator*() const & + { + return value(); + } + T &operator*() & + { + return value(); + } + const T &&operator*() const && + { + return value(); + } + T &&operator*() && + { + return value(); + } + + const T *operator->() const + { + return value_.get(); + } + T *operator->() + { + return value_.get(); + } + + private: + ReturnCode return_code_; + std::unique_ptr value_; +}; diff --git a/orchagent/saiattr.cpp b/orchagent/saiattr.cpp new file mode 100644 index 0000000000..1c24489ed5 --- /dev/null +++ b/orchagent/saiattr.cpp @@ -0,0 +1,91 @@ +#include "saiattr.h" + +#include +#include + +SaiAttrWrapper::SaiAttrWrapper(sai_object_type_t objectType, const sai_attribute_t& attr) +{ + auto meta = sai_metadata_get_attr_metadata(objectType, attr.id); + if (!meta) + { + SWSS_LOG_THROW("Failed to get attribute %d metadata", attr.id); + } + + init(objectType, *meta, attr); +} + +SaiAttrWrapper::~SaiAttrWrapper() +{ + if (m_meta) + { + sai_deserialize_free_attribute_value(m_meta->attrvaluetype, m_attr); + } +} + +SaiAttrWrapper::SaiAttrWrapper(const SaiAttrWrapper& other) +{ + init(other.m_objectType, *other.m_meta, other.m_attr); +} + +SaiAttrWrapper& SaiAttrWrapper::operator=(const SaiAttrWrapper& other) +{ + init(other.m_objectType, *other.m_meta, other.m_attr); + return *this; +} + +SaiAttrWrapper::SaiAttrWrapper(SaiAttrWrapper&& other) +{ + swap(std::move(other)); +} + +SaiAttrWrapper& SaiAttrWrapper::operator=(SaiAttrWrapper&& other) +{ + swap(std::move(other)); + return *this; +} + +bool SaiAttrWrapper::operator<(const SaiAttrWrapper& other) const +{ + return m_serializedAttr < other.m_serializedAttr; +} + +const sai_attribute_t& SaiAttrWrapper::getSaiAttr() const +{ + return m_attr; +} + +std::string SaiAttrWrapper::toString() const +{ + return m_serializedAttr; +} + +sai_attr_id_t SaiAttrWrapper::getAttrId() const +{ + return m_attr.id; +} + +void SaiAttrWrapper::swap(SaiAttrWrapper&& other) +{ + m_objectType = other.m_objectType; + m_meta = other.m_meta; + m_attr = other.m_attr; + m_serializedAttr = other.m_serializedAttr; + other.m_attr = sai_attribute_t{}; + other.m_serializedAttr.clear(); +} + +void SaiAttrWrapper::init( + sai_object_type_t objectType, + const sai_attr_metadata_t& meta, + const sai_attribute_t& attr) +{ + m_objectType = objectType; + m_attr.id = attr.id; + m_meta = &meta; + + m_serializedAttr = sai_serialize_attr_value(*m_meta, attr); + + // deserialize to actually preform a deep copy of attr + // and attribute value's dynamically allocated lists. + sai_deserialize_attr_value(m_serializedAttr, *m_meta, m_attr); +} diff --git a/orchagent/saiattr.h b/orchagent/saiattr.h new file mode 100644 index 0000000000..c4fef0ba0d --- /dev/null +++ b/orchagent/saiattr.h @@ -0,0 +1,41 @@ +#pragma once + +extern "C" +{ +#include +#include +} + +#include + +class SaiAttrWrapper +{ +public: + SaiAttrWrapper() = default; + + SaiAttrWrapper(sai_object_type_t objectType, const sai_attribute_t& attr); + SaiAttrWrapper(const SaiAttrWrapper& other); + SaiAttrWrapper(SaiAttrWrapper&& other); + SaiAttrWrapper& operator=(const SaiAttrWrapper& other); + SaiAttrWrapper& operator=(SaiAttrWrapper&& other); + virtual ~SaiAttrWrapper(); + + bool operator<(const SaiAttrWrapper& other) const; + + const sai_attribute_t& getSaiAttr() const; + std::string toString() const; + sai_attr_id_t getAttrId() const; + +private: + + void init( + sai_object_type_t objectType, + const sai_attr_metadata_t& meta, + const sai_attribute_t& attr); + void swap(SaiAttrWrapper&& other); + + sai_object_type_t m_objectType {SAI_OBJECT_TYPE_NULL}; + const sai_attr_metadata_t* m_meta {nullptr}; + sai_attribute_t m_attr {}; + std::string m_serializedAttr; +}; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 90578dc8a9..8db9676f39 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -68,6 +68,7 @@ sai_system_port_api_t* sai_system_port_api; sai_macsec_api_t* sai_macsec_api; sai_srv6_api_t** sai_srv6_api;; sai_l2mc_group_api_t* sai_l2mc_group_api; +sai_counter_api_t* sai_counter_api; sai_bfd_api_t* sai_bfd_api; extern sai_object_id_t gSwitchId; @@ -85,15 +86,15 @@ static map hardware_access_map = map gProfileMap; -sai_status_t mdio_read(uint64_t platform_context, - uint32_t mdio_addr, uint32_t reg_addr, +sai_status_t mdio_read(uint64_t platform_context, + uint32_t mdio_addr, uint32_t reg_addr, uint32_t number_of_registers, uint32_t *data) { return SAI_STATUS_NOT_IMPLEMENTED; } -sai_status_t mdio_write(uint64_t platform_context, - uint32_t mdio_addr, uint32_t reg_addr, +sai_status_t mdio_write(uint64_t platform_context, + uint32_t mdio_addr, uint32_t reg_addr, uint32_t number_of_registers, uint32_t *data) { return SAI_STATUS_NOT_IMPLEMENTED; @@ -194,6 +195,7 @@ void initSaiApi() sai_api_query(SAI_API_MACSEC, (void **)&sai_macsec_api); sai_api_query(SAI_API_SRV6, (void **)&sai_srv6_api); sai_api_query(SAI_API_L2MC_GROUP, (void **)&sai_l2mc_group_api); + sai_api_query(SAI_API_COUNTER, (void **)&sai_counter_api); sai_api_query(SAI_API_BFD, (void **)&sai_bfd_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); @@ -229,6 +231,7 @@ void initSaiApi() sai_log_set(SAI_API_MACSEC, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SRV6, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_L2MC_GROUP, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_COUNTER, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BFD, SAI_LOG_LEVEL_NOTICE); } @@ -452,8 +455,8 @@ sai_status_t initSaiPhyApi(swss::gearbox_phy_t *phy) { SWSS_LOG_ERROR("BOX: Failed to get firmware major version:%d rtn:%d", phy->phy_id, status); return status; - } - else + } + else { phy->firmware_major_version = string(attr.value.chardata); } diff --git a/orchagent/saihelper.h b/orchagent/saihelper.h index 450acf8b69..a0b2aa2fac 100644 --- a/orchagent/saihelper.h +++ b/orchagent/saihelper.h @@ -4,6 +4,9 @@ #include +#define IS_ATTR_ID_IN_RANGE(attrId, objectType, attrPrefix) \ + ((attrId) >= SAI_ ## objectType ## _ATTR_ ## attrPrefix ## _START && (attrId) <= SAI_ ## objectType ## _ATTR_ ## attrPrefix ## _END) + void initSaiApi(); void initSaiRedis(const std::string &record_location, const std::string &record_filename); sai_status_t initSaiPhyApi(swss::gearbox_phy_t *phy); diff --git a/orchagent/switchorch.cpp b/orchagent/switchorch.cpp index 05a36ec87c..daeace8b08 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -613,3 +613,31 @@ void SwitchOrch::querySwitchTpidCapability() set_switch_capability(fvVector); } } + +bool SwitchOrch::querySwitchDscpToTcCapability(sai_object_type_t sai_object, sai_attr_id_t attr_id) +{ + SWSS_LOG_ENTER(); + + /* Check if SAI is capable of handling Switch level DSCP to TC QoS map */ + vector fvVector; + sai_status_t status = SAI_STATUS_SUCCESS; + sai_attr_capability_t capability; + + status = sai_query_attribute_capability(gSwitchId, sai_object, attr_id, &capability); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Could not query switch level DSCP to TC map %d", status); + return false; + } + else + { + if (capability.set_implemented) + { + return true; + } + else + { + return false; + } + } +} diff --git a/orchagent/switchorch.h b/orchagent/switchorch.h index 86181e19df..46d165bd48 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -28,6 +28,7 @@ class SwitchOrch : public Orch void restartCheckReply(const std::string &op, const std::string &data, std::vector &values); bool setAgingFDB(uint32_t sec); void set_switch_capability(const std::vector& values); + bool querySwitchDscpToTcCapability(sai_object_type_t sai_object, sai_attr_id_t attr_id); private: void doTask(Consumer &consumer); void doTask(swss::SelectableTimer &timer); diff --git a/orchagent/trap_rates.lua b/orchagent/trap_rates.lua new file mode 100644 index 0000000000..69b9c5cd3f --- /dev/null +++ b/orchagent/trap_rates.lua @@ -0,0 +1,67 @@ +-- KEYS - generic counter IDs +-- ARGV[1] - counters db index +-- ARGV[2] - counters table name +-- ARGV[3] - poll time interval +-- return log + +local logtable = {} + +local function logit(msg) + logtable[#logtable+1] = tostring(msg) +end + +local counters_db = ARGV[1] +local counters_table_name = ARGV[2] +local rates_table_name = "RATES" + +-- Get configuration +redis.call('SELECT', counters_db) +local smooth_interval = redis.call('HGET', rates_table_name .. ':' .. 'TRAP', 'TRAP_SMOOTH_INTERVAL') +local alpha = redis.call('HGET', rates_table_name .. ':' .. 'TRAP', 'TRAP_ALPHA') +if not alpha then + logit("Alpha is not defined") + return logtable +end +local one_minus_alpha = 1.0 - alpha +local delta = tonumber(ARGV[3]) + +logit(alpha) +logit(one_minus_alpha) +logit(delta) + +local n = table.getn(KEYS) +for i = 1, n do + local state_table = rates_table_name .. ':' .. KEYS[i] .. ':' .. 'TRAP' + local initialized = redis.call('HGET', state_table, 'INIT_DONE') + logit(initialized) + + -- Get new COUNTERS values + local in_pkts = redis.call('HGET', counters_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS') + + if initialized == 'DONE' or initialized == 'COUNTERS_LAST' then + -- Get old COUNTERS values + local in_pkts_last = redis.call('HGET', rates_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS_last') + + -- Calculate new rates values + local rx_pps_new = (in_pkts - in_pkts_last) / delta * 1000 + + if initialized == "DONE" then + -- Get old rates values + local rx_pps_old = redis.call('HGET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS') + + -- Smooth the rates values and store them in DB + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS', alpha*rx_pps_new + one_minus_alpha*rx_pps_old) + else + -- Store unsmoothed initial rates values in DB + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS', rx_pps_new) + redis.call('HSET', state_table, 'INIT_DONE', 'DONE') + end + else + redis.call('HSET', state_table, 'INIT_DONE', 'COUNTERS_LAST') + end + + -- Set old COUNTERS values + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS_last', in_pkts) +end + +return logtable diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index 053784e298..dc5838d8a5 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -39,6 +39,7 @@ extern NeighOrch *gNeighOrch; extern CrmOrch *gCrmOrch; extern RouteOrch *gRouteOrch; extern MacAddress gVxlanMacAddress; +extern BfdOrch *gBfdOrch; /* * VRF Modeling and VNetVrf class definitions @@ -558,9 +559,14 @@ static bool del_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx) route_entry.destination = ip_pfx; sai_status_t status = sai_route_api->remove_route_entry(&route_entry); - if (status != SAI_STATUS_SUCCESS) + if (status == SAI_STATUS_ITEM_NOT_FOUND || status == SAI_STATUS_INVALID_PARAMETER) + { + SWSS_LOG_INFO("Unable to remove route since route is already removed"); + return true; + } + else if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("SAI Failed to remove route"); + SWSS_LOG_ERROR("SAI Failed to remove route, rv: %d", status); return false; } @@ -630,12 +636,17 @@ static bool update_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx, sai_obj } VNetRouteOrch::VNetRouteOrch(DBConnector *db, vector &tableNames, VNetOrch *vnetOrch) - : Orch2(db, tableNames, request_), vnet_orch_(vnetOrch) + : Orch2(db, tableNames, request_), vnet_orch_(vnetOrch), bfd_session_producer_(db, APP_BFD_SESSION_TABLE_NAME) { SWSS_LOG_ENTER(); handler_map_.insert(handler_pair(APP_VNET_RT_TABLE_NAME, &VNetRouteOrch::handleRoutes)); handler_map_.insert(handler_pair(APP_VNET_RT_TUNNEL_TABLE_NAME, &VNetRouteOrch::handleTunnel)); + + state_db_ = shared_ptr(new DBConnector("STATE_DB", 0)); + state_vnet_rt_tunnel_table_ = unique_ptr
(new Table(state_db_.get(), STATE_VNET_RT_TUNNEL_TABLE_NAME)); + + gBfdOrch->attach(this); } bool VNetRouteOrch::hasNextHopGroup(const string& vnet, const NextHopGroupKey& nexthops) @@ -667,6 +678,10 @@ bool VNetRouteOrch::addNextHopGroup(const string& vnet, const NextHopGroupKey &n for (auto it : next_hop_set) { + if (nexthop_info_[vnet].find(it.ip_address) != nexthop_info_[vnet].end() && nexthop_info_[vnet][it.ip_address].bfd_state != SAI_BFD_SESSION_STATE_UP) + { + continue; + } sai_object_id_t next_hop_id = vrf_obj->getTunnelNextHop(it); next_hop_ids.push_back(next_hop_id); nhopgroup_members_set[next_hop_id] = it; @@ -797,7 +812,8 @@ bool VNetRouteOrch::removeNextHopGroup(const string& vnet, const NextHopGroupKey template<> bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipPrefix, - NextHopGroupKey& nexthops, string& op) + NextHopGroupKey& nexthops, string& op, + const map& monitors) { SWSS_LOG_ENTER(); @@ -835,9 +851,9 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP if (op == SET_COMMAND) { sai_object_id_t nh_id; - /* The route is pointing to one single endpoint */ if (!hasNextHopGroup(vnet, nexthops)) { + setEndpointMonitor(vnet, monitors, nexthops); if (nexthops.getSize() == 1) { NextHopKey nexthop(nexthops.to_string(), true); @@ -851,6 +867,7 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP { if (!addNextHopGroup(vnet, nexthops, vrf_obj)) { + delEndpointMonitor(vnet, nexthops); SWSS_LOG_ERROR("Failed to create next hop group %s", nexthops.to_string().c_str()); return false; } @@ -863,13 +880,37 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP { bool route_status = true; - if (it_route == syncd_tunnel_routes_[vnet].end()) + // Remove route if the nexthop group has no active endpoint + if (syncd_nexthop_groups_[vnet][nexthops].active_members.empty()) { - route_status = add_route(vr_id, pfx, nh_id); + if (it_route != syncd_tunnel_routes_[vnet].end()) + { + NextHopGroupKey nhg = it_route->second; + // Remove route when updating from a nhg with active member to another nhg without + if (!syncd_nexthop_groups_[vnet][nhg].active_members.empty()) + { + del_route(vr_id, pfx); + } + } } else { - route_status = update_route(vr_id, pfx, nh_id); + if (it_route == syncd_tunnel_routes_[vnet].end()) + { + route_status = add_route(vr_id, pfx, nh_id); + } + else + { + NextHopGroupKey nhg = it_route->second; + if (syncd_nexthop_groups_[vnet][nhg].active_members.empty()) + { + route_status = add_route(vr_id, pfx, nh_id); + } + else + { + route_status = update_route(vr_id, pfx, nh_id); + } + } } if (!route_status) @@ -900,13 +941,22 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP NextHopKey nexthop(nhg.to_string(), true); vrf_obj->removeTunnelNextHop(nexthop); } + delEndpointMonitor(vnet, nhg); + } + else + { + syncd_nexthop_groups_[vnet][nhg].tunnel_routes.erase(ipPrefix); } vrf_obj->removeRoute(ipPrefix); } + syncd_nexthop_groups_[vnet][nexthops].tunnel_routes.insert(ipPrefix); + syncd_tunnel_routes_[vnet][ipPrefix] = nexthops; syncd_nexthop_groups_[vnet][nexthops].ref_count++; vrf_obj->addRoute(ipPrefix, nexthops); + + postRouteState(vnet, ipPrefix, nexthops); } else if (op == DEL_COMMAND) { @@ -921,10 +971,14 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP for (auto vr_id : vr_set) { - if (!del_route(vr_id, pfx)) + // If an nhg has no active member, the route should already be removed + if (!syncd_nexthop_groups_[vnet][nhg].active_members.empty()) { - SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); - return false; + if (!del_route(vr_id, pfx)) + { + SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); + return false; + } } } @@ -940,6 +994,11 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP NextHopKey nexthop(nhg.to_string(), true); vrf_obj->removeTunnelNextHop(nexthop); } + delEndpointMonitor(vnet, nhg); + } + else + { + syncd_nexthop_groups_[vnet][nhg].tunnel_routes.erase(ipPrefix); } syncd_tunnel_routes_[vnet].erase(ipPrefix); @@ -949,6 +1008,84 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP } vrf_obj->removeRoute(ipPrefix); + + removeRouteState(vnet, ipPrefix); + } + + return true; +} + +bool VNetRouteOrch::updateTunnelRoute(const string& vnet, IpPrefix& ipPrefix, + NextHopGroupKey& nexthops, string& op) +{ + SWSS_LOG_ENTER(); + + if (!vnet_orch_->isVnetExists(vnet)) + { + SWSS_LOG_WARN("VNET %s doesn't exist for prefix %s, op %s", + vnet.c_str(), ipPrefix.to_string().c_str(), op.c_str()); + return (op == DEL_COMMAND)?true:false; + } + + set vr_set; + auto& peer_list = vnet_orch_->getPeerList(vnet); + + auto l_fn = [&] (const string& vnet) { + auto *vnet_obj = vnet_orch_->getTypePtr(vnet); + sai_object_id_t vr_id = vnet_obj->getVRidIngress(); + vr_set.insert(vr_id); + }; + + l_fn(vnet); + for (auto peer : peer_list) + { + if (!vnet_orch_->isVnetExists(peer)) + { + SWSS_LOG_INFO("Peer VNET %s not yet created", peer.c_str()); + return false; + } + l_fn(peer); + } + + sai_ip_prefix_t pfx; + copy(pfx, ipPrefix); + + if (op == SET_COMMAND) + { + sai_object_id_t nh_id = syncd_nexthop_groups_[vnet][nexthops].next_hop_group_id; + + for (auto vr_id : vr_set) + { + bool route_status = true; + + route_status = add_route(vr_id, pfx, nh_id); + + if (!route_status) + { + SWSS_LOG_ERROR("Route add failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); + return false; + } + } + } + else if (op == DEL_COMMAND) + { + auto it_route = syncd_tunnel_routes_[vnet].find(ipPrefix); + if (it_route == syncd_tunnel_routes_[vnet].end()) + { + SWSS_LOG_INFO("Failed to find tunnel route entry, prefix %s\n", + ipPrefix.to_string().c_str()); + return true; + } + NextHopGroupKey nhg = it_route->second; + + for (auto vr_id : vr_set) + { + if (!del_route(vr_id, pfx)) + { + SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); + return false; + } + } } return true; @@ -1315,6 +1452,311 @@ void VNetRouteOrch::delRoute(const IpPrefix& ipPrefix) syncd_routes_.erase(route_itr); } +void VNetRouteOrch::createBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& monitor_addr) +{ + SWSS_LOG_ENTER(); + + IpAddress endpoint_addr = endpoint.ip_address; + if (nexthop_info_[vnet].find(endpoint_addr) != nexthop_info_[vnet].end()) + { + SWSS_LOG_ERROR("BFD session for endpoint %s already exist", endpoint_addr.to_string().c_str()); + return; + } + + if (bfd_sessions_.find(monitor_addr) == bfd_sessions_.end()) + { + vector data; + string key = "default:default:" + monitor_addr.to_string(); + + auto tun_name = vnet_orch_->getTunnelName(vnet); + VxlanTunnelOrch* vxlan_orch = gDirectory.get(); + auto tunnel_obj = vxlan_orch->getVxlanTunnel(tun_name); + IpAddress src_ip = tunnel_obj->getSrcIP(); + + FieldValueTuple fvTuple("local_addr", src_ip.to_string()); + data.push_back(fvTuple); + + bfd_session_producer_.set(key, data); + + bfd_sessions_[monitor_addr].bfd_state = SAI_BFD_SESSION_STATE_DOWN; + } + + BfdSessionInfo& bfd_info = bfd_sessions_[monitor_addr]; + bfd_info.vnet = vnet; + bfd_info.endpoint = endpoint; + VNetNextHopInfo nexthop_info; + nexthop_info.monitor_addr = monitor_addr; + nexthop_info.bfd_state = bfd_info.bfd_state; + nexthop_info.ref_count = 0; + nexthop_info_[vnet][endpoint_addr] = nexthop_info; +} + +void VNetRouteOrch::removeBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& monitor_addr) +{ + SWSS_LOG_ENTER(); + + IpAddress endpoint_addr = endpoint.ip_address; + if (nexthop_info_[vnet].find(endpoint_addr) == nexthop_info_[vnet].end()) + { + SWSS_LOG_ERROR("BFD session for endpoint %s does not exist", endpoint_addr.to_string().c_str()); + } + nexthop_info_[vnet].erase(endpoint_addr); + + string key = "default:default:" + monitor_addr.to_string(); + + bfd_session_producer_.del(key); + + bfd_sessions_.erase(monitor_addr); +} + +void VNetRouteOrch::setEndpointMonitor(const string& vnet, const map& monitors, NextHopGroupKey& nexthops) +{ + SWSS_LOG_ENTER(); + + for (auto monitor : monitors) + { + NextHopKey nh = monitor.first; + IpAddress monitor_ip = monitor.second; + if (nexthop_info_[vnet].find(nh.ip_address) == nexthop_info_[vnet].end()) + { + createBfdSession(vnet, nh, monitor_ip); + } + + nexthop_info_[vnet][nh.ip_address].ref_count++; + } +} + +void VNetRouteOrch::delEndpointMonitor(const string& vnet, NextHopGroupKey& nexthops) +{ + SWSS_LOG_ENTER(); + + std::set nhks = nexthops.getNextHops(); + for (auto nhk: nhks) + { + IpAddress ip = nhk.ip_address; + if (nexthop_info_[vnet].find(ip) != nexthop_info_[vnet].end()) { + if (--nexthop_info_[vnet][ip].ref_count == 0) + { + removeBfdSession(vnet, nhk, nexthop_info_[vnet][ip].monitor_addr); + } + } + } +} + +void VNetRouteOrch::postRouteState(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops) +{ + const string state_db_key = vnet + state_db_key_delimiter + ipPrefix.to_string(); + vector fvVector; + + NextHopGroupInfo& nhg_info = syncd_nexthop_groups_[vnet][nexthops]; + string route_state = nhg_info.active_members.empty() ? "inactive" : "active"; + string ep_str = ""; + int idx_ep = 0; + for (auto nh_pair : nhg_info.active_members) + { + NextHopKey nh = nh_pair.first; + ep_str += idx_ep == 0 ? nh.ip_address.to_string() : "," + nh.ip_address.to_string(); + idx_ep++; + } + + fvVector.emplace_back("active_endpoints", ep_str); + fvVector.emplace_back("state", route_state); + + state_vnet_rt_tunnel_table_->set(state_db_key, fvVector); +} + +void VNetRouteOrch::removeRouteState(const string& vnet, IpPrefix& ipPrefix) +{ + const string state_db_key = vnet + state_db_key_delimiter + ipPrefix.to_string(); + state_vnet_rt_tunnel_table_->del(state_db_key); +} + +void VNetRouteOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); + + assert(cntx); + + switch(type) { + case SUBJECT_TYPE_BFD_SESSION_STATE_CHANGE: + { + BfdUpdate *update = static_cast(cntx); + updateVnetTunnel(*update); + break; + } + default: + // Received update in which we are not interested + // Ignore it + return; + } +} + +void VNetRouteOrch::updateVnetTunnel(const BfdUpdate& update) +{ + SWSS_LOG_ENTER(); + + auto key = update.peer; + sai_bfd_session_state_t state = update.state; + + size_t found_vrf = key.find(state_db_key_delimiter); + if (found_vrf == string::npos) + { + SWSS_LOG_ERROR("Failed to parse key %s, no vrf is given", key.c_str()); + return; + } + + size_t found_ifname = key.find(state_db_key_delimiter, found_vrf + 1); + if (found_ifname == string::npos) + { + SWSS_LOG_ERROR("Failed to parse key %s, no ifname is given", key.c_str()); + return; + } + + string vrf_name = key.substr(0, found_vrf); + string alias = key.substr(found_vrf + 1, found_ifname - found_vrf - 1); + IpAddress peer_address(key.substr(found_ifname + 1)); + + if (alias != "default" || vrf_name != "default") + { + return; + } + + auto it_peer = bfd_sessions_.find(peer_address); + + if (it_peer == bfd_sessions_.end()) { + SWSS_LOG_INFO("No endpoint for BFD peer %s", peer_address.to_string().c_str()); + return; + } + + BfdSessionInfo& bfd_info = it_peer->second; + bfd_info.bfd_state = state; + + string vnet = bfd_info.vnet; + NextHopKey endpoint = bfd_info.endpoint; + auto *vrf_obj = vnet_orch_->getTypePtr(vnet); + + if (syncd_nexthop_groups_.find(vnet) == syncd_nexthop_groups_.end()) + { + SWSS_LOG_ERROR("Vnet %s not found", vnet.c_str()); + return; + } + + nexthop_info_[vnet][endpoint.ip_address].bfd_state = state; + + for (auto& nhg_info_pair : syncd_nexthop_groups_[vnet]) + { + NextHopGroupKey nexthops = nhg_info_pair.first; + NextHopGroupInfo& nhg_info = nhg_info_pair.second; + + if (!(nexthops.contains(endpoint))) + { + continue; + } + + if (state == SAI_BFD_SESSION_STATE_UP) + { + sai_object_id_t next_hop_group_member_id = SAI_NULL_OBJECT_ID; + if (nexthops.getSize() > 1) + { + // Create a next hop group member + vector nhgm_attrs; + + sai_attribute_t nhgm_attr; + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID; + nhgm_attr.value.oid = nhg_info.next_hop_group_id; + nhgm_attrs.push_back(nhgm_attr); + + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID; + nhgm_attr.value.oid = vrf_obj->getTunnelNextHop(endpoint); + nhgm_attrs.push_back(nhgm_attr); + + sai_status_t status = sai_next_hop_group_api->create_next_hop_group_member(&next_hop_group_member_id, + gSwitchId, + (uint32_t)nhgm_attrs.size(), + nhgm_attrs.data()); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to add next hop member to group %" PRIx64 ": %d\n", + nhg_info.next_hop_group_id, status); + task_process_status handle_status = handleSaiCreateStatus(SAI_API_NEXT_HOP_GROUP, status); + if (handle_status != task_success) + { + continue; + } + } + + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + } + + // Re-create routes when it was temporarily removed + if (nhg_info.active_members.empty()) + { + nhg_info.active_members[endpoint] = next_hop_group_member_id; + if (vnet_orch_->isVnetExecVrf()) + { + for (auto ip_pfx : syncd_nexthop_groups_[vnet][nexthops].tunnel_routes) + { + string op = SET_COMMAND; + updateTunnelRoute(vnet, ip_pfx, nexthops, op); + } + } + } + else + { + nhg_info.active_members[endpoint] = next_hop_group_member_id; + } + } + else + { + if (nexthops.getSize() > 1 && nhg_info.active_members.find(endpoint) != nhg_info.active_members.end()) + { + sai_object_id_t nexthop_id = nhg_info.active_members[endpoint]; + sai_status_t status = sai_next_hop_group_api->remove_next_hop_group_member(nexthop_id); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove next hop member %" PRIx64 " from group %" PRIx64 ": %d\n", + nexthop_id, nhg_info.next_hop_group_id, status); + task_process_status handle_status = handleSaiRemoveStatus(SAI_API_NEXT_HOP_GROUP, status); + if (handle_status != task_success) + { + continue; + } + } + + vrf_obj->removeTunnelNextHop(endpoint); + + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + } + + if (nhg_info.active_members.find(endpoint) != nhg_info.active_members.end()) + { + nhg_info.active_members.erase(endpoint); + + // Remove routes when nexthop group has no active endpoint + if (nhg_info.active_members.empty()) + { + if (vnet_orch_->isVnetExecVrf()) + { + for (auto ip_pfx : syncd_nexthop_groups_[vnet][nexthops].tunnel_routes) + { + string op = DEL_COMMAND; + updateTunnelRoute(vnet, ip_pfx, nexthops, op); + } + } + } + } + } + + // Post configured in State DB + for (auto ip_pfx : syncd_nexthop_groups_[vnet][nexthops].tunnel_routes) + { + postRouteState(vnet, ip_pfx, nexthops); + } + } +} + bool VNetRouteOrch::handleTunnel(const Request& request) { SWSS_LOG_ENTER(); @@ -1322,6 +1764,7 @@ bool VNetRouteOrch::handleTunnel(const Request& request) vector ip_list; vector mac_list; vector vni_list; + vector monitor_list; for (const auto& name: request.getAttrFieldNames()) { @@ -1339,6 +1782,10 @@ bool VNetRouteOrch::handleTunnel(const Request& request) string mac_str = request.getAttrString(name); mac_list = tokenize(mac_str, ','); } + else if (name == "endpoint_monitor") + { + monitor_list = request.getAttrIPList(name); + } else { SWSS_LOG_INFO("Unknown attribute: %s", name.c_str()); @@ -1358,6 +1805,12 @@ bool VNetRouteOrch::handleTunnel(const Request& request) return false; } + if (!monitor_list.empty() && monitor_list.size() != ip_list.size()) + { + SWSS_LOG_ERROR("Peer monitor size of %zu does not match endpoint size of %zu", monitor_list.size(), ip_list.size()); + return false; + } + const std::string& vnet_name = request.getKeyString(0); auto ip_pfx = request.getKeyIpPrefix(1); auto op = request.getOperation(); @@ -1366,6 +1819,7 @@ bool VNetRouteOrch::handleTunnel(const Request& request) op.c_str(), ip_pfx.to_string().c_str()); NextHopGroupKey nhg("", true); + map monitors; for (size_t idx_ip = 0; idx_ip < ip_list.size(); idx_ip++) { IpAddress ip = ip_list[idx_ip]; @@ -1387,11 +1841,15 @@ bool VNetRouteOrch::handleTunnel(const Request& request) NextHopKey nh(ip, mac, vni, true); nhg.add(nh); + if (!monitor_list.empty()) + { + monitors[nh] = monitor_list[idx_ip]; + } } if (vnet_orch_->isVnetExecVrf()) { - return doRouteTask(vnet_name, ip_pfx, nhg, op); + return doRouteTask(vnet_name, ip_pfx, nhg, op, monitors); } return true; diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index 569a23f2e0..7e493c5f30 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -13,6 +13,7 @@ #include "producerstatetable.h" #include "observer.h" #include "nexthopgroupkey.h" +#include "bfdorch.h" #define VNET_BITMAP_SIZE 32 #define VNET_TUNNEL_SIZE 40960 @@ -72,6 +73,7 @@ struct NextHopGroupInfo sai_object_id_t next_hop_group_id; // next hop group id (null for single nexthop) int ref_count; // reference count std::map active_members; // active nexthops and nexthop group member id (null for single nexthop) + std::set tunnel_routes; }; class VNetObject @@ -252,7 +254,7 @@ const request_description_t vnet_route_description = { { "nexthop", REQ_T_STRING }, { "vni", REQ_T_STRING }, { "mac_address", REQ_T_STRING }, - { "endpoint_monitor", REQ_T_STRING }, + { "endpoint_monitor", REQ_T_IP_LIST }, }, { } }; @@ -283,10 +285,26 @@ struct VNetNextHopObserverEntry /* NextHopObserverTable: Destination IP address, next hop observer entry */ typedef std::map VNetNextHopObserverTable; +struct VNetNextHopInfo +{ + IpAddress monitor_addr; + sai_bfd_session_state_t bfd_state; + int ref_count; +}; + +struct BfdSessionInfo +{ + sai_bfd_session_state_t bfd_state; + std::string vnet; + NextHopKey endpoint; +}; + typedef std::map VNetNextHopGroupInfoTable; typedef std::map VNetTunnelRouteTable; +typedef std::map BfdSessionTable; +typedef std::map VNetEndpointInfoTable; -class VNetRouteOrch : public Orch2, public Subject +class VNetRouteOrch : public Orch2, public Subject, public Observer { public: VNetRouteOrch(DBConnector *db, vector &tableNames, VNetOrch *); @@ -297,6 +315,8 @@ class VNetRouteOrch : public Orch2, public Subject void attach(Observer* observer, const IpAddress& dstAddr); void detach(Observer* observer, const IpAddress& dstAddr); + void update(SubjectType, void *); + private: virtual bool addOperation(const Request& request); virtual bool delOperation(const Request& request); @@ -312,8 +332,19 @@ class VNetRouteOrch : public Orch2, public Subject bool addNextHopGroup(const string&, const NextHopGroupKey&, VNetVrfObject *vrf_obj); bool removeNextHopGroup(const string&, const NextHopGroupKey&, VNetVrfObject *vrf_obj); + void createBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& ipAddr); + void removeBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& ipAddr); + void setEndpointMonitor(const string& vnet, const map& monitors, NextHopGroupKey& nexthops); + void delEndpointMonitor(const string& vnet, NextHopGroupKey& nexthops); + void postRouteState(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops); + void removeRouteState(const string& vnet, IpPrefix& ipPrefix); + + void updateVnetTunnel(const BfdUpdate&); + bool updateTunnelRoute(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op); + template - bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op); + bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op, + const std::map& monitors=std::map()); template bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, nextHop& nh, string& op); @@ -326,6 +357,11 @@ class VNetRouteOrch : public Orch2, public Subject VNetNextHopObserverTable next_hop_observers_; std::map syncd_nexthop_groups_; std::map syncd_tunnel_routes_; + BfdSessionTable bfd_sessions_; + std::map nexthop_info_; + ProducerStateTable bfd_session_producer_; + shared_ptr state_db_; + unique_ptr
state_vnet_rt_tunnel_table_; }; class VNetCfgRouteOrch : public Orch diff --git a/portsyncd/linksync.cpp b/portsyncd/linksync.cpp index 08df3ff9ec..4a2b351ee0 100644 --- a/portsyncd/linksync.cpp +++ b/portsyncd/linksync.cpp @@ -182,6 +182,7 @@ void LinkSync::onMsg(int nlmsg_type, struct nl_object *obj) unsigned int ifindex = rtnl_link_get_ifindex(link); int master = rtnl_link_get_master(link); char *type = rtnl_link_get_type(link); + unsigned int mtu = rtnl_link_get_mtu(link); if (type) { @@ -251,10 +252,14 @@ void LinkSync::onMsg(int nlmsg_type, struct nl_object *obj) { g_portSet.erase(key); FieldValueTuple tuple("state", "ok"); + FieldValueTuple admin_status("admin_status", (admin ? "up" : "down")); + FieldValueTuple port_mtu("mtu", to_string(mtu)); vector vector; vector.push_back(tuple); FieldValueTuple op("netdev_oper_status", oper ? "up" : "down"); vector.push_back(op); + vector.push_back(admin_status); + vector.push_back(port_mtu); m_statePortTable.set(key, vector); SWSS_LOG_NOTICE("Publish %s(ok:%s) to state db", key.c_str(), oper ? "up" : "down"); } diff --git a/teamsyncd/teamsync.cpp b/teamsyncd/teamsync.cpp index 846d474a6c..6d8c025911 100644 --- a/teamsyncd/teamsync.cpp +++ b/teamsyncd/teamsync.cpp @@ -157,11 +157,18 @@ void TeamSync::addLag(const string &lagName, int ifindex, bool admin_state, SWSS_LOG_INFO("Add %s admin_status:%s oper_status:%s, mtu: %d", lagName.c_str(), admin_state ? "up" : "down", oper_state ? "up" : "down", mtu); + bool lag_update = true; /* Return when the team instance has already been tracked */ if (m_teamSelectables.find(lagName) != m_teamSelectables.end()) - return; + { + auto tsync = m_teamSelectables[lagName]; + if (tsync->admin_state == admin_state && tsync->mtu == mtu) + return; + tsync->admin_state = admin_state; + tsync->mtu = mtu; + lag_update = false; + } - fvVector.clear(); FieldValueTuple s("state", "ok"); fvVector.push_back(s); if (m_warmstart) @@ -173,10 +180,13 @@ void TeamSync::addLag(const string &lagName, int ifindex, bool admin_state, m_stateLagTable.set(lagName, fvVector); } - /* Create the team instance */ - auto sync = make_shared(lagName, ifindex, &m_lagMemberTable); - m_teamSelectables[lagName] = sync; - m_selectablesToAdd.insert(lagName); + if (lag_update) + { + /* Create the team instance */ + auto sync = make_shared(lagName, ifindex, &m_lagMemberTable); + m_teamSelectables[lagName] = sync; + m_selectablesToAdd.insert(lagName); + } } void TeamSync::removeLag(const string &lagName) diff --git a/teamsyncd/teamsync.h b/teamsyncd/teamsync.h index 406953e312..deb5d84129 100644 --- a/teamsyncd/teamsync.h +++ b/teamsyncd/teamsync.h @@ -43,6 +43,8 @@ class TeamSync : public NetMsg /* member_name -> enabled|disabled */ std::map m_lagMembers; + bool admin_state; + unsigned int mtu; protected: int onChange(); static int teamdHandler(struct team_handle *th, void *arg, diff --git a/tests/dvslib/dvs_acl.py b/tests/dvslib/dvs_acl.py index dbf9791b53..9111de7a8e 100644 --- a/tests/dvslib/dvs_acl.py +++ b/tests/dvslib/dvs_acl.py @@ -6,6 +6,7 @@ class DVSAcl: """Manage ACL tables and rules on the virtual switch.""" CDB_ACL_TABLE_NAME = "ACL_TABLE" + CDB_ACL_TABLE_TYPE_NAME = "ACL_TABLE_TYPE" CDB_MIRROR_ACTION_LOOKUP = { "ingress": "MIRROR_INGRESS_ACTION", @@ -48,6 +49,26 @@ def __init__(self, asic_db, config_db, state_db, counters_db): self.state_db = state_db self.counters_db = counters_db + def create_acl_table_type( + self, + name: str, + matches: List[str], + bpoint_types: List[str] + ) -> None: + """Create a new ACL table type in Config DB. + + Args: + name: The name for the new ACL table type. + matches: A list of matches to use in ACL table. + bpoint_types: A list of bind point types to use in ACL table. + """ + table_type_attrs = { + "matches@": ",".join(matches), + "bind_points@": ",".join(bpoint_types) + } + + self.config_db.create_entry(self.CDB_ACL_TABLE_TYPE_NAME, name, table_type_attrs) + def create_acl_table( self, table_name: str, @@ -111,6 +132,14 @@ def remove_acl_table(self, table_name: str) -> None: """ self.config_db.delete_entry(self.CDB_ACL_TABLE_NAME, table_name) + def remove_acl_table_type(self, name: str) -> None: + """Remove an ACL table type from Config DB. + + Args: + name: The name of the ACL table type to delete. + """ + self.config_db.delete_entry(self.CDB_ACL_TABLE_TYPE_NAME, name) + def get_acl_table_ids(self, expected: int) -> List[str]: """Get all of the ACL table IDs in ASIC DB. diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 51bcf30547..51df17f729 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -32,7 +32,9 @@ tests_SOURCES = aclorch_ut.cpp \ mock_hiredis.cpp \ mock_redisreply.cpp \ bulker_ut.cpp \ + fake_response_publisher.cpp \ $(top_srcdir)/lib/gearboxutils.cpp \ + $(top_srcdir)/lib/subintf.cpp \ $(top_srcdir)/orchagent/orchdaemon.cpp \ $(top_srcdir)/orchagent/orch.cpp \ $(top_srcdir)/orchagent/notifications.cpp \ @@ -59,6 +61,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/pbh/pbhrule.cpp \ $(top_srcdir)/orchagent/pbhorch.cpp \ $(top_srcdir)/orchagent/saihelper.cpp \ + $(top_srcdir)/orchagent/saiattr.cpp \ $(top_srcdir)/orchagent/switchorch.cpp \ $(top_srcdir)/orchagent/pfcwdorch.cpp \ $(top_srcdir)/orchagent/pfcactionhandler.cpp \ @@ -84,7 +87,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/bfdorch.cpp \ $(top_srcdir)/orchagent/srv6orch.cpp -tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp +tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp $(FLEX_CTR_DIR)/flow_counter_handler.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index d95ae2f87e..c0d7399570 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -99,8 +99,25 @@ namespace aclorch_test TEST_F(AclTest, Create_L3_Acl_Table) { - AclTable acltable; - acltable.type = ACL_TABLE_L3; + AclTable acltable; /* this test shouldn't trigger a call to gAclOrch because it's nullptr */ + AclTableTypeBuilder builder; + auto l3TableType = builder + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(set({SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}))) + .build(); + acltable.type = l3TableType; auto res = createAclTable(acltable); ASSERT_TRUE(res->ret_val); @@ -139,7 +156,7 @@ namespace aclorch_test vector acl_table_connectors = { confDbAclTable, confDbAclRuleTable }; - m_aclOrch = new AclOrch(acl_table_connectors, switchOrch, portsOrch, mirrorOrch, + m_aclOrch = new AclOrch(acl_table_connectors, state_db, switchOrch, portsOrch, mirrorOrch, neighOrch, routeOrch); } @@ -153,6 +170,15 @@ namespace aclorch_test return m_aclOrch; } + void doAclTableTypeTask(const deque &entries) + { + auto consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(config_db, CFG_ACL_TABLE_TYPE_TABLE_NAME, 1, 1), m_aclOrch, CFG_ACL_TABLE_TYPE_TABLE_NAME)); + + consumer->addToSync(entries); + static_cast(m_aclOrch)->doTask(*consumer); + } + void doAclTableTask(const deque &entries) { auto consumer = unique_ptr(new Consumer( @@ -176,6 +202,27 @@ namespace aclorch_test return m_aclOrch->getTableById(table_id); } + const AclRule* getAclRule(string tableName, string ruleName) + { + return m_aclOrch->getAclRule(tableName, ruleName); + } + + const AclTable* getTableByOid(sai_object_id_t oid) + { + return m_aclOrch->getTableByOid(oid); + } + + const AclTable* getAclTable(string tableName) + { + auto oid = m_aclOrch->getTableById(tableName); + return getTableByOid(oid); + } + + const AclTableType* getAclTableType(string tableTypeName) + { + return m_aclOrch->getAclTableType(tableTypeName); + } + const map &getAclTables() const { return Portal::AclOrchInternal::getAclTables(m_aclOrch); @@ -448,24 +495,22 @@ namespace aclorch_test fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS", "true" }); fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", "2:SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE,SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE" }); - switch (acl_table.type) + if (acl_table.type.getName() == TABLE_TYPE_L3) + { + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IP", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", "true" }); + } + else if (acl_table.type.getName() == TABLE_TYPE_L3V6) { - case ACL_TABLE_L3: - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IP", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", "true" }); - break; - - case ACL_TABLE_L3V6: - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true" }); - break; - - default: - // We shouldn't get here. Will continue to add more test cases ...; - ; + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true" }); + } + else + { + // We shouldn't get here. Will continue to add more test cases ...; } if (ACL_STAGE_INGRESS == acl_table.stage) @@ -492,19 +537,17 @@ namespace aclorch_test fields.push_back({ "SAI_ACL_ENTRY_ATTR_ADMIN_STATE", "true" }); fields.push_back({ "SAI_ACL_ENTRY_ATTR_ACTION_COUNTER", counter_id }); - switch (acl_table.type) + if (acl_table.type.getName() == TABLE_TYPE_L3) { - case ACL_TABLE_L3: - fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP", "1.2.3.4&mask:255.255.255.255" }); - break; - - case ACL_TABLE_L3V6: - fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6", "::1.2.3.4&mask:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" }); - break; - - default: - // We shouldn't get here. Will continue to add more test cases ... - ; + fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP", "1.2.3.4&mask:255.255.255.255" }); + } + if (acl_table.type.getName() == TABLE_TYPE_L3V6) + { + fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6", "::1.2.3.4&mask:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" }); + } + else + { + // We shouldn't get here. Will continue to add more test cases ... } return shared_ptr(new SaiAttributeList(objecttype, fields, false)); @@ -569,13 +612,17 @@ namespace aclorch_test return true; } - bool validateAclTable(sai_object_id_t acl_table_oid, const AclTable &acl_table) + bool validateAclTable(sai_object_id_t acl_table_oid, const AclTable &acl_table, shared_ptr expAttrList = nullptr) { const sai_object_type_t objecttype = SAI_OBJECT_TYPE_ACL_TABLE; - auto exp_attrlist_2 = getAclTableAttributeList(objecttype, acl_table); + if (!expAttrList) + { + expAttrList = getAclTableAttributeList(objecttype, acl_table); + + } { - auto &exp_attrlist = *exp_attrlist_2; + auto &exp_attrlist = *expAttrList; vector act_attr; @@ -641,23 +688,16 @@ namespace aclorch_test auto const &resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); // Verify the ACL tables - uint32_t crmAclTableBindingCount = 0; + size_t crmAclTableBindingCount = 0; for (auto const &kv: resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap) { crmAclTableBindingCount += kv.second.usedCounter; } - uint32_t aclorchAclTableBindingCount = 0; + size_t aclorchAclTableBindingCount = 0; for (auto const &kv: Portal::AclOrchInternal::getAclTables(aclOrch)) { - if (kv.second.type == ACL_TABLE_PFCWD) - { - aclorchAclTableBindingCount += 1; // port binding only - } - else - { - aclorchAclTableBindingCount += 2; // port + LAG binding - } + aclorchAclTableBindingCount += kv.second.type.getBindPointTypes().size(); } if (crmAclTableBindingCount != aclorchAclTableBindingCount) @@ -770,21 +810,7 @@ namespace aclorch_test { if (fv.first == ACL_TABLE_TYPE) { - if (fv.second == TABLE_TYPE_L3) - { - if (acl_table.type != ACL_TABLE_L3) - { - return false; - } - } - else if (fv.second == TABLE_TYPE_L3V6) - { - if (acl_table.type != ACL_TABLE_L3V6) - { - return false; - } - } - else + if (acl_table.type.getName() != fv.second) { return false; } @@ -827,21 +853,21 @@ namespace aclorch_test return false; } - if (it->second.aclaction.enable != true) + if (it->second.getSaiAttr().value.aclaction.enable != true) { return false; } if (attr_value == PACKET_ACTION_FORWARD) { - if (it->second.aclaction.parameter.s32 != SAI_PACKET_ACTION_FORWARD) + if (it->second.getSaiAttr().value.aclaction.parameter.s32 != SAI_PACKET_ACTION_FORWARD) { return false; } } else if (attr_value == PACKET_ACTION_DROP) { - if (it->second.aclaction.parameter.s32 != SAI_PACKET_ACTION_DROP) + if (it->second.getSaiAttr().value.aclaction.parameter.s32 != SAI_PACKET_ACTION_DROP) { return false; } @@ -874,14 +900,14 @@ namespace aclorch_test } char addr[20]; - sai_serialize_ip4(addr, it_field->second.aclfield.data.ip4); + sai_serialize_ip4(addr, it_field->second.getSaiAttr().value.aclfield.data.ip4); if (attr_value != addr) { return false; } char mask[20]; - sai_serialize_ip4(mask, it_field->second.aclfield.mask.ip4); + sai_serialize_ip4(mask, it_field->second.getSaiAttr().value.aclfield.mask.ip4); if (string(mask) != "255.255.255.255") { return false; @@ -896,14 +922,14 @@ namespace aclorch_test } char addr[46]; - sai_serialize_ip6(addr, it_field->second.aclfield.data.ip6); + sai_serialize_ip6(addr, it_field->second.getSaiAttr().value.aclfield.data.ip6); if (attr_value != addr) { return false; } char mask[46]; - sai_serialize_ip6(mask, it_field->second.aclfield.mask.ip6); + sai_serialize_ip6(mask, it_field->second.getSaiAttr().value.aclfield.mask.ip6); if (string(mask) != "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") { return false; @@ -976,6 +1002,25 @@ namespace aclorch_test return !aclEnable && aclOid == SAI_NULL_OBJECT_ID; } + + string getAclRuleSaiAttribute(const AclRule& rule, sai_acl_entry_attr_t attrId) + { + sai_attribute_t attr{}; + attr.id = attrId; + auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attrId); + if (!meta) + { + SWSS_LOG_THROW("SAI BUG: Failed to get attribute metadata for SAI_OBJECT_TYPE_ACL_ENTRY attribute id %d", attrId); + } + + auto status = sai_acl_api->get_acl_entry_attribute(rule.m_ruleOid, 1, &attr); + EXPECT_TRUE(status == SAI_STATUS_SUCCESS); + + auto actualSaiValue = sai_serialize_attr_value(*meta, attr); + + return actualSaiValue; + } + }; map AclOrchTest::gProfileMap; @@ -1317,4 +1362,361 @@ namespace aclorch_test ASSERT_EQ(tableIt, orch->getAclTables().end()); } + TEST_F(AclOrchTest, AclTableType_Configuration) + { + const string aclTableTypeName = "TEST_TYPE"; + const string aclTableName = "TEST_TABLE"; + const string aclRuleName = "TEST_RULE"; + + auto orch = createAclOrch(); + + auto tableKofvt = deque( + { + { + aclTableName, + SET_COMMAND, + { + { ACL_TABLE_DESCRIPTION, "Test table" }, + { ACL_TABLE_TYPE, aclTableTypeName}, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } + } + } + } + ); + + orch->doAclTableTask(tableKofvt); + + // Table not created without table type + ASSERT_FALSE(orch->getAclTable(aclTableName)); + + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + SET_COMMAND, + { + { + ACL_TABLE_TYPE_MATCHES, + string(MATCH_SRC_IP) + comma + MATCH_ETHER_TYPE + comma + MATCH_L4_SRC_PORT_RANGE + }, + { + ACL_TABLE_TYPE_BPOINT_TYPES, + string(BIND_POINT_TYPE_PORT) + comma + BIND_POINT_TYPE_PORTCHANNEL + }, + } + } + } + ) + ); + + orch->doAclTableTask(tableKofvt); + + // Table is created now + ASSERT_TRUE(orch->getAclTable(aclTableName)); + + auto fvs = vector{ + { "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST", "2:SAI_ACL_BIND_POINT_TYPE_PORT,SAI_ACL_BIND_POINT_TYPE_LAG" }, + { "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", "1:SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE" }, + }; + + ASSERT_TRUE(validateAclTable( + orch->getAclTable(aclTableName)->getOid(), + *orch->getAclTable(aclTableName), + make_shared(SAI_OBJECT_TYPE_ACL_TABLE, fvs, false)) + ); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_SRC_IP, "1.1.1.1/32" }, + { MATCH_L4_DST_PORT_RANGE, "80..100" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // L4_DST_PORT_RANGE is not in the table type + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_SRC_IP, "1.1.1.1/32" }, + { MATCH_DST_IP, "2.2.2.2/32" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // DST_IP is not in the table type + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_SRC_IP, "1.1.1.1/32" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // Now it is valid for this table. + ASSERT_TRUE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + DEL_COMMAND, + {} + } + } + ) + ); + + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + DEL_COMMAND, + {} + } + } + ) + ); + + // Table still exists + ASSERT_TRUE(orch->getAclTable(aclTableName)); + ASSERT_FALSE(orch->getAclTableType(aclTableTypeName)); + + orch->doAclTableTask( + deque( + { + { + aclTableName, + DEL_COMMAND, + {} + } + } + ) + ); + + // Table is removed + ASSERT_FALSE(orch->getAclTable(aclTableName)); + } + + TEST_F(AclOrchTest, AclTableType_ActionValidation) + { + const string aclTableTypeName = "TEST_TYPE"; + const string aclTableName = "TEST_TABLE"; + const string aclRuleName = "TEST_RULE"; + + auto orch = createAclOrch(); + + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + SET_COMMAND, + { + { + ACL_TABLE_TYPE_MATCHES, + string(MATCH_ETHER_TYPE) + comma + MATCH_L4_SRC_PORT_RANGE + comma + MATCH_L4_DST_PORT_RANGE + }, + { + ACL_TABLE_TYPE_BPOINT_TYPES, + BIND_POINT_TYPE_PORTCHANNEL + }, + { + ACL_TABLE_TYPE_ACTIONS, + ACTION_MIRROR_INGRESS_ACTION + } + } + } + } + ) + ); + + orch->doAclTableTask( + deque( + { + { + aclTableName, + SET_COMMAND, + { + { ACL_TABLE_DESCRIPTION, "Test table" }, + { ACL_TABLE_TYPE, aclTableTypeName}, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } + } + } + } + ) + ); + + ASSERT_TRUE(orch->getAclTable(aclTableName)); + + auto fvs = vector{ + { "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST", "1:SAI_ACL_BIND_POINT_TYPE_LAG" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", "2:SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE,SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE" }, + { "SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST", "1:SAI_ACL_ACTION_TYPE_MIRROR_INGRESS" }, + }; + + ASSERT_TRUE(validateAclTable( + orch->getAclTable(aclTableName)->getOid(), + *orch->getAclTable(aclTableName), + make_shared(SAI_OBJECT_TYPE_ACL_TABLE, fvs, false)) + ); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_ETHER_TYPE, "2048" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // Packet action is not supported on this table + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + const auto testSessionName = "test_session"; + gMirrorOrch->createEntry(testSessionName, {}); + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_ETHER_TYPE, "2048" }, + { ACTION_MIRROR_INGRESS_ACTION, testSessionName }, + } + } + } + ) + ); + + // Mirror action is supported on this table + ASSERT_TRUE(orch->getAclRule(aclTableName, aclRuleName)); + } + + TEST_F(AclOrchTest, AclRuleUpdate) + { + string acl_table_id = "acl_table_1"; + string acl_rule_id = "acl_rule_1"; + + auto orch = createAclOrch(); + + auto kvfAclTable = deque( + { { acl_table_id, + SET_COMMAND, + { { ACL_TABLE_DESCRIPTION, "TEST" }, + { ACL_TABLE_TYPE, TABLE_TYPE_L3 }, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } } } }); + + orch->doAclTableTask(kvfAclTable); + + // validate acl table ... + + auto acl_table_oid = orch->getTableById(acl_table_id); + ASSERT_NE(acl_table_oid, SAI_NULL_OBJECT_ID); + + const auto &acl_tables = orch->getAclTables(); + auto it_table = acl_tables.find(acl_table_oid); + ASSERT_NE(it_table, acl_tables.end()); + + class AclRuleTest : public AclRulePacket + { + public: + AclRuleTest(AclOrch* orch, string rule, string table): + AclRulePacket(orch, rule, table, true) + {} + + void setCounterEnabled(bool enabled) + { + m_createCounter = enabled; + } + + void disableMatch(sai_acl_entry_attr_t attr) + { + m_matches.erase(attr); + } + }; + + auto rule = make_shared(orch->m_aclOrch, acl_rule_id, acl_table_id); + ASSERT_TRUE(rule->validateAddPriority(RULE_PRIORITY, "800")); + ASSERT_TRUE(rule->validateAddMatch(MATCH_SRC_IP, "1.1.1.1/32")); + ASSERT_TRUE(rule->validateAddAction(ACTION_PACKET_ACTION, PACKET_ACTION_FORWARD)); + + ASSERT_TRUE(orch->m_aclOrch->addAclRule(rule, acl_table_id)); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_PRIORITY), "800"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP), "1.1.1.1&mask:255.255.255.255"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION), "SAI_PACKET_ACTION_FORWARD"); + + auto updatedRule = make_shared(*rule); + ASSERT_TRUE(updatedRule->validateAddPriority(RULE_PRIORITY, "900")); + ASSERT_TRUE(updatedRule->validateAddMatch(MATCH_SRC_IP, "2.2.2.2/24")); + ASSERT_TRUE(updatedRule->validateAddMatch(MATCH_DST_IP, "3.3.3.3/24")); + ASSERT_TRUE(updatedRule->validateAddAction(ACTION_PACKET_ACTION, PACKET_ACTION_DROP)); + + ASSERT_TRUE(orch->m_aclOrch->updateAclRule(updatedRule)); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_PRIORITY), "900"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP), "2.2.2.2&mask:255.255.255.0"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_DST_IP), "3.3.3.3&mask:255.255.255.0"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION), "SAI_PACKET_ACTION_DROP"); + + auto updatedRule2 = make_shared(*updatedRule); + updatedRule2->setCounterEnabled(false); + updatedRule2->disableMatch(SAI_ACL_ENTRY_ATTR_FIELD_DST_IP); + ASSERT_TRUE(orch->m_aclOrch->updateAclRule(updatedRule2)); + ASSERT_TRUE(validateAclRuleCounter(*orch->m_aclOrch->getAclRule(acl_table_id, acl_rule_id), false)); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_PRIORITY), "900"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP), "2.2.2.2&mask:255.255.255.0"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_DST_IP), "disabled"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION), "SAI_PACKET_ACTION_DROP"); + + auto updatedRule3 = make_shared(*updatedRule2); + updatedRule3->setCounterEnabled(true); + ASSERT_TRUE(orch->m_aclOrch->updateAclRule(updatedRule3)); + ASSERT_TRUE(validateAclRuleCounter(*orch->m_aclOrch->getAclRule(acl_table_id, acl_rule_id), true)); + + ASSERT_TRUE(orch->m_aclOrch->removeAclRule(rule->getTableId(), rule->getId())); + } + } // namespace nsAclOrchTest diff --git a/tests/mock_tests/database_config.json b/tests/mock_tests/database_config.json index 1b6343d20e..8301848683 100644 --- a/tests/mock_tests/database_config.json +++ b/tests/mock_tests/database_config.json @@ -61,6 +61,11 @@ "id" : 12, "separator": "|", "instance" : "redis_chassis" + }, + "APPL_STATE_DB" : { + "id" : 14, + "separator": ":", + "instance" : "redis" } }, "VERSION" : "1.0" diff --git a/tests/mock_tests/fake_response_publisher.cpp b/tests/mock_tests/fake_response_publisher.cpp new file mode 100644 index 0000000000..94480913d5 --- /dev/null +++ b/tests/mock_tests/fake_response_publisher.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include "response_publisher.h" + +ResponsePublisher::ResponsePublisher() : m_db("APPL_STATE_DB", 0) {} + +void ResponsePublisher::publish( + const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, + const std::vector& state_attrs, bool replace) {} + +void ResponsePublisher::publish( + const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, bool replace) {} + +void ResponsePublisher::writeToDB( + const std::string& table, const std::string& key, + const std::vector& values, const std::string& op, + bool replace) {} diff --git a/tests/mock_tests/portal.h b/tests/mock_tests/portal.h index 7dab40036b..c2438e8e1c 100644 --- a/tests/mock_tests/portal.h +++ b/tests/mock_tests/portal.h @@ -18,12 +18,12 @@ struct Portal return aclRule->m_ruleOid; } - static const map &getMatches(const AclRule *aclRule) + static const map &getMatches(const AclRule *aclRule) { return aclRule->m_matches; } - static const map &getActions(const AclRule *aclRule) + static const map &getActions(const AclRule *aclRule) { return aclRule->m_actions; } diff --git a/tests/test_acl.py b/tests/test_acl.py index d4b317bf55..fb8aecb0ea 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -10,6 +10,15 @@ L3V6_BIND_PORTS = ["Ethernet0", "Ethernet4", "Ethernet8"] L3V6_RULE_NAME = "L3V6_TEST_RULE" +MCLAG_TABLE_TYPE = "MCLAG" +MCLAG_TABLE_NAME = "MCLAG_TEST" +MCLAG_BIND_PORTS = ["Ethernet0", "Ethernet4", "Ethernet8", "Ethernet12"] +MCLAG_RULE_NAME = "MCLAG_TEST_RULE" + +MIRROR_TABLE_TYPE = "MIRROR" +MIRROR_TABLE_NAME = "MIRROR_TEST" +MIRROR_BIND_PORTS = ["Ethernet0", "Ethernet4", "Ethernet8", "Ethernet12"] +MIRROR_RULE_NAME = "MIRROR_TEST_RULE" class TestAcl: @pytest.yield_fixture @@ -32,6 +41,24 @@ def l3v6_acl_table(self, dvs_acl): dvs_acl.remove_acl_table(L3V6_TABLE_NAME) dvs_acl.verify_acl_table_count(0) + @pytest.yield_fixture + def mclag_acl_table(self, dvs_acl): + try: + dvs_acl.create_acl_table(MCLAG_TABLE_NAME, MCLAG_TABLE_TYPE, MCLAG_BIND_PORTS) + yield dvs_acl.get_acl_table_ids(1)[0] + finally: + dvs_acl.remove_acl_table(MCLAG_TABLE_NAME) + dvs_acl.verify_acl_table_count(0) + + @pytest.yield_fixture + def mirror_acl_table(self, dvs_acl): + try: + dvs_acl.create_acl_table(MIRROR_TABLE_NAME, MIRROR_TABLE_TYPE, MIRROR_BIND_PORTS) + yield dvs_acl.get_acl_table_ids(1)[0] + finally: + dvs_acl.remove_acl_table(MIRROR_TABLE_NAME) + dvs_acl.verify_acl_table_count(0) + @pytest.yield_fixture def setup_teardown_neighbor(self, dvs): try: @@ -133,42 +160,69 @@ def test_V6AclRuleNextHeaderAppendedForTCPFlags(self, dvs_acl, l3v6_acl_table): dvs_acl.remove_acl_rule(L3V6_TABLE_NAME, L3V6_RULE_NAME) dvs_acl.verify_no_acl_rules() - def test_AclRuleInOutPorts(self, dvs_acl, l3_acl_table): + def test_AclRuleInPorts(self, dvs_acl, mirror_acl_table): + """ + Verify IN_PORTS matches on ACL rule. + Using MIRROR table type for IN_PORTS matches. + """ config_qualifiers = { - "IN_PORTS": "Ethernet0,Ethernet4", - "OUT_PORTS": "Ethernet8,Ethernet12" + "IN_PORTS": "Ethernet8,Ethernet12", + } + + expected_sai_qualifiers = { + "SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS": dvs_acl.get_port_list_comparator(["Ethernet8", "Ethernet12"]) + } + + dvs_acl.create_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME, config_qualifiers) + dvs_acl.verify_acl_rule(expected_sai_qualifiers) + + dvs_acl.remove_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME) + dvs_acl.verify_no_acl_rules() + + def test_AclRuleOutPorts(self, dvs_acl, mclag_acl_table): + """ + Verify OUT_PORTS matches on ACL rule. + Using MCLAG table type for OUT_PORTS matches. + """ + config_qualifiers = { + "OUT_PORTS": "Ethernet8,Ethernet12", } expected_sai_qualifiers = { - "SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS": dvs_acl.get_port_list_comparator(["Ethernet0", "Ethernet4"]), "SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS": dvs_acl.get_port_list_comparator(["Ethernet8", "Ethernet12"]) } - dvs_acl.create_acl_rule(L3_TABLE_NAME, L3_RULE_NAME, config_qualifiers) + dvs_acl.create_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME, config_qualifiers) dvs_acl.verify_acl_rule(expected_sai_qualifiers) - dvs_acl.remove_acl_rule(L3_TABLE_NAME, L3_RULE_NAME) + dvs_acl.remove_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME) dvs_acl.verify_no_acl_rules() - def test_AclRuleInPortsNonExistingInterface(self, dvs_acl, l3_acl_table): + def test_AclRuleInPortsNonExistingInterface(self, dvs_acl, mirror_acl_table): + """ + Using MIRROR table type as it has IN_PORTS matches. + """ config_qualifiers = { "IN_PORTS": "FOO_BAR_BAZ" } - dvs_acl.create_acl_rule(L3_TABLE_NAME, L3_RULE_NAME, config_qualifiers) + dvs_acl.create_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME, config_qualifiers) dvs_acl.verify_no_acl_rules() - dvs_acl.remove_acl_rule(L3_TABLE_NAME, L3_RULE_NAME) + dvs_acl.remove_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME) - def test_AclRuleOutPortsNonExistingInterface(self, dvs_acl, l3_acl_table): + def test_AclRuleOutPortsNonExistingInterface(self, dvs_acl, mclag_acl_table): + """ + Using MCLAG table type as it has OUT_PORTS matches. + """ config_qualifiers = { "OUT_PORTS": "FOO_BAR_BAZ" } - dvs_acl.create_acl_rule(L3_TABLE_NAME, L3_RULE_NAME, config_qualifiers) + dvs_acl.create_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME, config_qualifiers) dvs_acl.verify_no_acl_rules() - dvs_acl.remove_acl_rule(L3_TABLE_NAME, L3_RULE_NAME) + dvs_acl.remove_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME) def test_AclRuleVlanId(self, dvs_acl, l3_acl_table): config_qualifiers = {"VLAN_ID": "100"} @@ -550,15 +604,12 @@ def test_ValidateAclTableBindingCrmUtilization(self, dvs, dvs_acl): class TestAclRuleValidation: """Test class for cases that check if orchagent corectly validates ACL rules input.""" - SWITCH_CAPABILITY_TABLE = "SWITCH_CAPABILITY" + ACL_STAGE_CAPABILITY_TABLE_NAME = "ACL_STAGE_CAPABILITY_TABLE" + ACL_ACTION_LIST_FIELD_NAME = "action_list" def get_acl_actions_supported(self, dvs_acl, stage): - switch_id = dvs_acl.state_db.wait_for_n_keys(self.SWITCH_CAPABILITY_TABLE, 1)[0] - switch = dvs_acl.state_db.wait_for_entry(self.SWITCH_CAPABILITY_TABLE, switch_id) - - field = "ACL_ACTIONS|{}".format(stage.upper()) - - supported_actions = switch.get(field, None) + switch = dvs_acl.state_db.wait_for_entry(self.ACL_STAGE_CAPABILITY_TABLE_NAME, stage.upper()) + supported_actions = switch.get(self.ACL_ACTION_LIST_FIELD_NAME, None) if supported_actions: supported_actions = supported_actions.split(",") diff --git a/tests/test_acl_egress_table.py b/tests/test_acl_egress_table.py index f2b917ebc6..01800d6b20 100644 --- a/tests/test_acl_egress_table.py +++ b/tests/test_acl_egress_table.py @@ -1,6 +1,19 @@ import pytest -TABLE_TYPE = "L3" +TABLE_TYPE = "CUSTOM_L3" +CUSTOM_TABLE_TYPE_MATCHES = [ + "L4_SRC_PORT_RANGE", + "L4_DST_PORT_RANGE", + "ETHER_TYPE", + "TUNNEL_VNI", + "TC", + "INNER_IP_PROTOCOL", + "INNER_ETHER_TYPE", + "INNER_L4_SRC_PORT", + "INNER_L4_DST_PORT", + "VLAN_ID" +] +CUSTOM_TABLE_TYPE_BPOINT_TYPES = ["PORT","PORTCHANNEL"] TABLE_NAME = "EGRESS_TEST" BIND_PORTS = ["Ethernet0", "Ethernet4"] RULE_NAME = "EGRESS_TEST_RULE" @@ -10,14 +23,17 @@ class TestEgressAclTable: @pytest.yield_fixture def egress_acl_table(self, dvs_acl): try: + dvs_acl.create_acl_table_type(TABLE_TYPE, CUSTOM_TABLE_TYPE_MATCHES, CUSTOM_TABLE_TYPE_BPOINT_TYPES) dvs_acl.create_acl_table(TABLE_NAME, TABLE_TYPE, BIND_PORTS, stage="egress") yield dvs_acl.get_acl_table_ids(1)[0] finally: dvs_acl.remove_acl_table(TABLE_NAME) + dvs_acl.remove_acl_table_type(TABLE_TYPE) dvs_acl.verify_acl_table_count(0) def test_EgressAclTableCreationDeletion(self, dvs_acl): try: + dvs_acl.create_acl_table_type(TABLE_TYPE, CUSTOM_TABLE_TYPE_MATCHES, CUSTOM_TABLE_TYPE_BPOINT_TYPES) dvs_acl.create_acl_table(TABLE_NAME, TABLE_TYPE, BIND_PORTS, stage="egress") acl_table_id = dvs_acl.get_acl_table_ids(1)[0] @@ -27,6 +43,7 @@ def test_EgressAclTableCreationDeletion(self, dvs_acl): dvs_acl.verify_acl_table_port_binding(acl_table_id, BIND_PORTS, 1, stage="egress") finally: dvs_acl.remove_acl_table(TABLE_NAME) + dvs_acl.remove_acl_table_type(TABLE_TYPE) dvs_acl.verify_acl_table_count(0) def test_EgressAclRuleL4SrcPortRange(self, dvs_acl, egress_acl_table): diff --git a/tests/test_buffer_dynamic.py b/tests/test_buffer_dynamic.py index 329279ab44..e44f2824f5 100644 --- a/tests/test_buffer_dynamic.py +++ b/tests/test_buffer_dynamic.py @@ -39,13 +39,19 @@ def setup_db(self, dvs): # Check whether cable length has been configured fvs = self.config_db.wait_for_entry("CABLE_LENGTH", "AZURE") self.originalCableLen = fvs["Ethernet0"] + self.cableLenBeforeTest = self.originalCableLen if self.originalCableLen == self.cableLenTest1: self.cableLenTest1 = "20m" elif self.originalCableLen == self.cableLenTest2: self.cableLenTest2 = "20m" + elif self.originalCableLen == "0m": + fvs["Ethernet0"] = "5m" + self.originalCableLen = "5m" + self.config_db.update_entry("CABLE_LENGTH", "AZURE", fvs) fvs = {"mmu_size": "12766208"} self.state_db.create_entry("BUFFER_MAX_PARAM_TABLE", "global", fvs) + self.bufferMaxParameter = self.state_db.wait_for_entry("BUFFER_MAX_PARAM_TABLE", "Ethernet0") # The default lossless priority group will be removed ahead of staring test # By doing so all the dynamically generated profiles will be removed and @@ -78,10 +84,18 @@ def setup_db(self, dvs): assert not lossless_profile, \ "There is still lossless profile ({}) {} seconds after all lossless PGs have been removed".format(lossless_profile, seconds_delayed) + time.sleep(10) + self.setup_asic_db(dvs) self.initialized = True + def cleanup_db(self, dvs): + # Clean up: restore the origin cable length + fvs = self.config_db.wait_for_entry("CABLE_LENGTH", "AZURE") + fvs["Ethernet0"] = self.cableLenBeforeTest + self.config_db.update_entry("CABLE_LENGTH", "AZURE", fvs) + def setup_asic_db(self, dvs): buffer_pool_set = set(self.asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_BUFFER_POOL")) self.initProfileSet = set(self.asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_BUFFER_PROFILE")) @@ -141,11 +155,17 @@ def change_cable_length(self, cable_length): cable_lengths['Ethernet0'] = cable_length self.config_db.update_entry('CABLE_LENGTH', 'AZURE', cable_lengths) + def check_queues_after_port_startup(self, dvs): + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "{}:0-2".format("Ethernet0"), {"profile": "egress_lossy_profile"}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "{}:3-4".format("Ethernet0"), {"profile": "egress_lossless_profile"}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "{}:5-6".format("Ethernet0"), {"profile": "egress_lossy_profile"}) + def test_changeSpeed(self, dvs, testlog): self.setup_db(dvs) # Startup interface dvs.runcmd('config interface startup Ethernet0') + self.check_queues_after_port_startup(dvs) # Configure lossless PG 3-4 on interface self.config_db.update_entry('BUFFER_PG', 'Ethernet0|3-4', {'profile': 'NULL'}) @@ -192,6 +212,8 @@ def test_changeSpeed(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_changeCableLen(self, dvs, testlog): self.setup_db(dvs) @@ -242,6 +264,8 @@ def test_changeCableLen(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_MultipleLosslessPg(self, dvs, testlog): self.setup_db(dvs) @@ -289,6 +313,8 @@ def test_MultipleLosslessPg(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_headroomOverride(self, dvs, testlog): self.setup_db(dvs) @@ -372,6 +398,8 @@ def test_headroomOverride(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_mtuUpdate(self, dvs, testlog): self.setup_db(dvs) @@ -406,6 +434,8 @@ def test_mtuUpdate(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_nonDefaultAlpha(self, dvs, testlog): self.setup_db(dvs) @@ -448,6 +478,8 @@ def test_nonDefaultAlpha(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_sharedHeadroomPool(self, dvs, testlog): self.setup_db(dvs) @@ -546,11 +578,20 @@ def test_sharedHeadroomPool(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_shutdownPort(self, dvs, testlog): self.setup_db(dvs) lossy_pg_reference_config_db = 'ingress_lossy_profile' lossy_pg_reference_appl_db = 'ingress_lossy_profile' + lossy_queue_reference_config_db = 'egress_lossy_profile' + lossy_queue_reference_appl_db = 'egress_lossy_profile' + lossless_queue_reference_appl_db = 'egress_lossless_profile' + + lossy_pg_zero_reference = 'ingress_lossy_pg_zero_profile' + lossy_queue_zero_reference = 'egress_lossy_zero_profile' + lossless_queue_zero_reference = 'egress_lossless_zero_profile' # Startup interface dvs.runcmd('config interface startup Ethernet0') @@ -560,9 +601,14 @@ def test_shutdownPort(self, dvs, testlog): expectedProfile = self.make_lossless_profile_name(self.originalSpeed, self.originalCableLen) self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile}) - # Shutdown port and check whether all the PGs have been removed + # Shutdown port and check whether zero profiles have been applied on queues and the PG 0 + maximumQueues = int(self.bufferMaxParameter['max_queues']) - 1 dvs.runcmd("config interface shutdown Ethernet0") - self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:0") + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:3-4") self.app_db.wait_for_deleted_entry("BUFFER_PROFILE", expectedProfile) @@ -570,25 +616,76 @@ def test_shutdownPort(self, dvs, testlog): self.config_db.update_entry('BUFFER_PG', 'Ethernet0|1', {'profile': lossy_pg_reference_config_db}) self.config_db.update_entry('BUFFER_PG', 'Ethernet0|6', {'profile': 'NULL'}) + # Add extra queue when a port is administratively down + self.config_db.update_entry('BUFFER_QUEUE', 'Ethernet0|8', {"profile": lossy_queue_reference_config_db}) + + # For queues, the slice in supported but not configured list should be '7-19'. + # After queue '8' is added, '7-19' should be removed and '7', '8', and '9-19' will be added + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues)) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:8", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) # Make sure they have not been added to APPL_DB time.sleep(30) self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:1") self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:6") - # Startup port and check whether all the PGs haved been added + # Startup port and check whether all the PGs have been added dvs.runcmd("config interface startup Ethernet0") self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_reference_appl_db}) self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:1", {"profile": lossy_pg_reference_appl_db}) - self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile }) + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile}) self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:6", {"profile": expectedProfile}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:8", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_reference_appl_db}) + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7") + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues)) + + # Shutdown the port again to verify flow to remove buffer objects from an admin down port + dvs.runcmd("config interface shutdown Ethernet0") + # First, check whether the objects have been correctly handled + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:8", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:3-4") + self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:1") + self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:6") + self.app_db.wait_for_deleted_entry("BUFFER_PROFILE", expectedProfile) + + # Remove buffer objects from an admon down port + self.config_db.delete_entry('BUFFER_QUEUE', 'Ethernet0|8') + self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|1') + self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|6') + + # Checking + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7") + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:8") + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues)) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) + + # Startup again + dvs.runcmd("config interface startup Ethernet0") + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile}) + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues)) # Remove lossless PG 3-4 on interface self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|3-4') - self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|6') # Shutdown interface dvs.runcmd("config interface shutdown Ethernet0") + self.cleanup_db(dvs) + def test_autoNegPort(self, dvs, testlog): self.setup_db(dvs) @@ -637,6 +734,8 @@ def test_autoNegPort(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_removeBufferPool(self, dvs, testlog): self.setup_db(dvs) # Initialize additional databases that are used by this test only @@ -668,9 +767,13 @@ def test_removeBufferPool(self, dvs, testlog): if counter_poll_disabled: self.config_db.delete_entry("FLEX_COUNTER_TABLE", "BUFFER_POOL_WATERMARK") + self.cleanup_db(dvs) + def test_bufferPortMaxParameter(self, dvs, testlog): self.setup_db(dvs) # Check whether port's maximum parameter has been exposed to STATE_DB fvs = self.state_db.wait_for_entry("BUFFER_MAX_PARAM_TABLE", "Ethernet0") assert int(fvs["max_queues"]) and int(fvs["max_priority_groups"]) + + self.cleanup_db(dvs) diff --git a/tests/test_flex_counters.py b/tests/test_flex_counters.py index e64c4e0222..a6e7153ac1 100644 --- a/tests/test_flex_counters.py +++ b/tests/test_flex_counters.py @@ -1,35 +1,7 @@ import time import pytest -# Counter keys on ConfigDB -PORT_KEY = "PORT" -QUEUE_KEY = "QUEUE" -RIF_KEY = "RIF" -BUFFER_POOL_WATERMARK_KEY = "BUFFER_POOL_WATERMARK" -PORT_BUFFER_DROP_KEY = "PORT_BUFFER_DROP" -PG_WATERMARK_KEY = "PG_WATERMARK" -ACL_KEY = "ACL" -TUNNEL_KEY = "TUNNEL" - -# Counter stats on FlexCountersDB -PORT_STAT = "PORT_STAT_COUNTER" -QUEUE_STAT = "QUEUE_STAT_COUNTER" -RIF_STAT = "RIF_STAT_COUNTER" -BUFFER_POOL_WATERMARK_STAT = "BUFFER_POOL_WATERMARK_STAT_COUNTER" -PORT_BUFFER_DROP_STAT = "PORT_BUFFER_DROP_STAT" -PG_WATERMARK_STAT = "PG_WATERMARK_STAT_COUNTER" -ACL_STAT = "ACL_STAT_COUNTER" -TUNNEL_STAT = "TUNNEL_STAT_COUNTER" - -# Counter maps on CountersDB -PORT_MAP = "COUNTERS_PORT_NAME_MAP" -QUEUE_MAP = "COUNTERS_QUEUE_NAME_MAP" -RIF_MAP = "COUNTERS_RIF_NAME_MAP" -BUFFER_POOL_WATERMARK_MAP = "COUNTERS_BUFFER_POOL_NAME_MAP" -PORT_BUFFER_DROP_MAP = "COUNTERS_PORT_NAME_MAP" -PG_WATERMARK_MAP = "COUNTERS_PG_NAME_MAP" -ACL_MAP = "ACL_COUNTER_RULE_MAP" -TUNNEL_MAP = "COUNTERS_TUNNEL_NAME_MAP" +from swsscommon import swsscommon TUNNEL_TYPE_MAP = "COUNTERS_TUNNEL_TYPE_MAP" NUMBER_OF_RETRIES = 10 @@ -38,14 +10,62 @@ # port to be added and removed PORT = "Ethernet0" -counter_type_dict = {"port_counter":[PORT_KEY, PORT_STAT, PORT_MAP], - "queue_counter":[QUEUE_KEY, QUEUE_STAT, QUEUE_MAP], - "rif_counter":[RIF_KEY, RIF_STAT, RIF_MAP], - "buffer_pool_watermark_counter":[BUFFER_POOL_WATERMARK_KEY, BUFFER_POOL_WATERMARK_STAT, BUFFER_POOL_WATERMARK_MAP], - "port_buffer_drop_counter":[PORT_BUFFER_DROP_KEY, PORT_BUFFER_DROP_STAT, PORT_BUFFER_DROP_MAP], - "pg_watermark_counter":[PG_WATERMARK_KEY, PG_WATERMARK_STAT, PG_WATERMARK_MAP], - "acl_counter":[ACL_KEY, ACL_STAT, ACL_MAP], - "vxlan_tunnel_counter":[TUNNEL_KEY, TUNNEL_STAT, TUNNEL_MAP]} +counter_group_meta = { + 'port_counter': { + 'key': 'PORT', + 'group_name': 'PORT_STAT_COUNTER', + 'name_map': 'COUNTERS_PORT_NAME_MAP', + 'post_test': 'post_port_counter_test', + }, + 'queue_counter': { + 'key': 'QUEUE', + 'group_name': 'QUEUE_STAT_COUNTER', + 'name_map': 'COUNTERS_QUEUE_NAME_MAP', + }, + 'rif_counter': { + 'key': 'RIF', + 'group_name': 'RIF_STAT_COUNTER', + 'name_map': 'COUNTERS_RIF_NAME_MAP', + 'pre_test': 'pre_rif_counter_test', + 'post_test': 'post_rif_counter_test', + }, + 'buffer_pool_watermark_counter': { + 'key': 'BUFFER_POOL_WATERMARK', + 'group_name': 'BUFFER_POOL_WATERMARK_STAT_COUNTER', + 'name_map': 'COUNTERS_BUFFER_POOL_NAME_MAP', + }, + 'port_buffer_drop_counter': { + 'key': 'PORT_BUFFER_DROP', + 'group_name': 'PORT_BUFFER_DROP_STAT', + 'name_map': 'COUNTERS_PORT_NAME_MAP', + }, + 'pg_watermark_counter': { + 'key': 'PG_WATERMARK', + 'group_name': 'PG_WATERMARK_STAT_COUNTER', + 'name_map': 'COUNTERS_PG_NAME_MAP', + }, + 'trap_flow_counter': { + 'key': 'FLOW_CNT_TRAP', + 'group_name': 'HOSTIF_TRAP_FLOW_COUNTER', + 'name_map': 'COUNTERS_TRAP_NAME_MAP', + 'post_test': 'post_trap_flow_counter_test', + }, + 'tunnel_counter': { + 'key': 'TUNNEL', + 'group_name': 'TUNNEL_STAT_COUNTER', + 'name_map': 'COUNTERS_TUNNEL_NAME_MAP', + 'pre_test': 'pre_vxlan_tunnel_counter_test', + 'post_test': 'post_vxlan_tunnel_counter_test', + }, + 'acl_counter': { + 'key': 'ACL', + 'group_name': 'ACL_STAT_COUNTER', + 'name_map': 'ACL_COUNTER_RULE_MAP', + 'pre_test': 'pre_acl_tunnel_counter_test', + 'post_test': 'post_acl_tunnel_counter_test', + } +} + @pytest.mark.usefixtures('dvs_port_manager') class TestFlexCounters(object): @@ -54,6 +74,7 @@ def setup_dbs(self, dvs): self.config_db = dvs.get_config_db() self.flex_db = dvs.get_flex_db() self.counters_db = dvs.get_counters_db() + self.app_db = dvs.get_app_db() def wait_for_table(self, table): for retry in range(NUMBER_OF_RETRIES): @@ -65,6 +86,16 @@ def wait_for_table(self, table): assert False, str(table) + " not created in Counters DB" + def wait_for_table_empty(self, table): + for retry in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(table) + if len(counters_keys) == 0: + return + else: + time.sleep(1) + + assert False, str(table) + " is still in Counters DB" + def wait_for_id_list(self, stat, name, oid): for retry in range(NUMBER_OF_RETRIES): id_list = self.flex_db.db_connection.hgetall("FLEX_COUNTER_TABLE:" + stat + ":" + oid).items() @@ -75,6 +106,27 @@ def wait_for_id_list(self, stat, name, oid): assert False, "No ID list for counter " + str(name) + def wait_for_id_list_remove(self, stat, name, oid): + for retry in range(NUMBER_OF_RETRIES): + id_list = self.flex_db.db_connection.hgetall("FLEX_COUNTER_TABLE:" + stat + ":" + oid).items() + if len(id_list) == 0: + return + else: + time.sleep(1) + + assert False, "ID list for counter " + str(name) + " is still there" + + def wait_for_interval_set(self, group, interval): + interval_value = None + for retry in range(NUMBER_OF_RETRIES): + interval_value = self.flex_db.db_connection.hget("FLEX_COUNTER_GROUP_TABLE:" + group, 'POLL_INTERVAL') + if interval_value == interval: + return + else: + time.sleep(1) + + assert False, "Polling interval is not applied to FLEX_COUNTER_GROUP_TABLE for group {}, expect={}, actual={}".format(group, interval, interval_value) + def verify_no_flex_counters_tables(self, counter_stat): counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + counter_stat) assert len(counters_stat_keys) == 0, "FLEX_COUNTER_TABLE:" + str(counter_stat) + " tables exist before enabling the flex counter group" @@ -95,26 +147,34 @@ def verify_flex_counters_populated(self, map, stat): oid = counter_entry[1] self.wait_for_id_list(stat, name, oid) - def verify_tunnel_type_vxlan(self, name_map, type_map): - counters_keys = self.counters_db.db_connection.hgetall(name_map) + def verify_tunnel_type_vxlan(self, meta_data, type_map): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) for counter_entry in counters_keys.items(): oid = counter_entry[1] fvs = self.counters_db.get_entry(type_map, "") assert fvs != {} assert fvs.get(oid) == "SAI_TUNNEL_TYPE_VXLAN" - def verify_only_phy_ports_created(self): - port_counters_keys = self.counters_db.db_connection.hgetall(PORT_MAP) - port_counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + PORT_STAT) + def verify_only_phy_ports_created(self, meta_data): + port_counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + port_counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + meta_data['group_name']) for port_stat in port_counters_stat_keys: assert port_stat in dict(port_counters_keys.items()).values(), "Non PHY port created on PORT_STAT_COUNTER group: {}".format(port_stat) - def enable_flex_counter_group(self, group, map): - group_stats_entry = {"FLEX_COUNTER_STATUS": "enable"} + def set_flex_counter_group_status(self, group, map, status='enable'): + group_stats_entry = {"FLEX_COUNTER_STATUS": status} self.config_db.create_entry("FLEX_COUNTER_TABLE", group, group_stats_entry) - self.wait_for_table(map) + if status == 'enable': + self.wait_for_table(map) + else: + self.wait_for_table_empty(map) + + def set_flex_counter_group_interval(self, key, group, interval): + group_stats_entry = {"POLL_INTERVAL": interval} + self.config_db.create_entry("FLEX_COUNTER_TABLE", key, group_stats_entry) + self.wait_for_interval_set(group, interval) - @pytest.mark.parametrize("counter_type", counter_type_dict.keys()) + @pytest.mark.parametrize("counter_type", counter_group_meta.keys()) def test_flex_counters(self, dvs, counter_type): """ The test will check there are no flex counters tables on FlexCounter DB when the counters are disabled. @@ -122,61 +182,208 @@ def test_flex_counters(self, dvs, counter_type): For some counter types the MAPS on COUNTERS DB will be created as well after enabling the counter group, this will be also verified on this test. """ self.setup_dbs(dvs) - counter_key = counter_type_dict[counter_type][0] - counter_stat = counter_type_dict[counter_type][1] - counter_map = counter_type_dict[counter_type][2] + meta_data = counter_group_meta[counter_type] + counter_key = meta_data['key'] + counter_stat = meta_data['group_name'] + counter_map = meta_data['name_map'] + pre_test = meta_data.get('pre_test') + post_test = meta_data.get('post_test') self.verify_no_flex_counters_tables(counter_stat) - if counter_type == "rif_counter": - self.config_db.db_connection.hset('INTERFACE|Ethernet0', "NULL", "NULL") - self.config_db.db_connection.hset('INTERFACE|Ethernet0|192.168.0.1/24', "NULL", "NULL") - elif counter_type == "acl_counter": - self.config_db.create_entry('ACL_TABLE', 'DATAACL', - { - 'STAGE': 'INGRESS', - 'PORTS': 'Ethernet0', - 'TYPE': 'L3' - } - ) - self.config_db.create_entry('ACL_RULE', 'DATAACL|RULE0', - { - 'ETHER_TYPE': '2048', - 'PACKET_ACTION': 'FORWARD', - 'PRIORITY': '9999' - } - ) - - if counter_type == "vxlan_tunnel_counter": - self.config_db.db_connection.hset("VLAN|Vlan10", "vlanid", "10") - self.config_db.db_connection.hset("VXLAN_TUNNEL|vtep1", "src_ip", "1.1.1.1") - self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vlan", "Vlan10") - self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vni", "100") + if pre_test: + cb = getattr(self, pre_test) + cb(meta_data) - self.enable_flex_counter_group(counter_key, counter_map) + self.set_flex_counter_group_status(counter_key, counter_map) self.verify_flex_counters_populated(counter_map, counter_stat) + self.set_flex_counter_group_interval(counter_key, counter_stat, '2500') + + if post_test: + cb = getattr(self, post_test) + cb(meta_data) + + def pre_rif_counter_test(self, meta_data): + self.config_db.db_connection.hset('INTERFACE|Ethernet0', "NULL", "NULL") + self.config_db.db_connection.hset('INTERFACE|Ethernet0|192.168.0.1/24', "NULL", "NULL") + + def pre_vxlan_tunnel_counter_test(self, meta_data): + self.config_db.db_connection.hset("VLAN|Vlan10", "vlanid", "10") + self.config_db.db_connection.hset("VXLAN_TUNNEL|vtep1", "src_ip", "1.1.1.1") + self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vlan", "Vlan10") + self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vni", "100") + + def pre_acl_tunnel_counter_test(self, meta_data): + self.config_db.create_entry('ACL_TABLE', 'DATAACL', + { + 'STAGE': 'INGRESS', + 'PORTS': 'Ethernet0', + 'TYPE': 'L3' + } + ) + self.config_db.create_entry('ACL_RULE', 'DATAACL|RULE0', + { + 'ETHER_TYPE': '2048', + 'PACKET_ACTION': 'FORWARD', + 'PRIORITY': '9999' + } + ) + + def post_rif_counter_test(self, meta_data): + self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") + + def post_port_counter_test(self, meta_data): + self.verify_only_phy_ports_created(meta_data) + + def post_trap_flow_counter_test(self, meta_data): + """Post verification for test_flex_counters for trap_flow_counter. Steps: + 1. Disable test_flex_counters + 2. Verify name map and counter ID list are cleared + 3. Clear trap ids to avoid affecting further test cases + + Args: + meta_data (object): flex counter meta data + """ + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + for counter_entry in counters_keys.items(): + self.wait_for_id_list_remove(meta_data['group_name'], counter_entry[0], counter_entry[1]) + self.wait_for_table_empty(meta_data['name_map']) + + def post_vxlan_tunnel_counter_test(self, meta_data): + self.verify_tunnel_type_vxlan(meta_data, TUNNEL_TYPE_MAP) + self.config_db.delete_entry("VLAN","Vlan10") + self.config_db.delete_entry("VLAN_TUNNEL","vtep1") + self.config_db.delete_entry("VLAN_TUNNEL_MAP","vtep1|map_100_Vlan10") + self.verify_no_flex_counters_tables_after_delete(meta_data['group_name']) + + def post_acl_tunnel_counter_test(self, meta_data): + self.config_db.delete_entry('ACL_RULE', 'DATAACL|RULE0') + self.config_db.delete_entry('ACL_TABLE', 'DATAACL') + + def test_add_remove_trap(self, dvs): + """Test steps: + 1. Enable trap_flow_counter + 2. Remove a COPP trap + 3. Verify counter is automatically unbind + 4. Add the COPP trap back + 5. Verify counter is added back + + Args: + dvs (object): virtual switch object + """ + self.setup_dbs(dvs) + meta_data = counter_group_meta['trap_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + + removed_trap = None + changed_group = None + trap_ids = None + copp_groups = self.app_db.db_connection.keys('COPP_TABLE:*') + for copp_group in copp_groups: + trap_ids = self.app_db.db_connection.hget(copp_group, 'trap_ids') + if trap_ids and ',' in trap_ids: + trap_ids = [x.strip() for x in trap_ids.split(',')] + removed_trap = trap_ids.pop() + changed_group = copp_group.split(':')[1] + break + + if not removed_trap: + pytest.skip('There is not copp group with more than 1 traps, skip rest of the test') + + oid = None + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if removed_trap in counters_keys: + oid = counters_keys[removed_trap] + break + else: + time.sleep(1) + + assert oid, 'trap counter is not created for {}'.format(removed_trap) + self.wait_for_id_list(meta_data['group_name'], removed_trap, oid) + + app_copp_table = swsscommon.ProducerStateTable(self.app_db.db_connection, 'COPP_TABLE') + app_copp_table.set(changed_group, [('trap_ids', ','.join(trap_ids))]) + self.wait_for_id_list_remove(meta_data['group_name'], removed_trap, oid) + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + assert removed_trap not in counters_keys + + trap_ids.append(removed_trap) + app_copp_table.set(changed_group, [('trap_ids', ','.join(trap_ids))]) + + oid = None + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if removed_trap in counters_keys: + oid = counters_keys[removed_trap] + break + else: + time.sleep(1) + + assert oid, 'Add trap {}, but trap counter is not created'.format(removed_trap) + self.wait_for_id_list(meta_data['group_name'], removed_trap, oid) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + def test_remove_trap_group(self, dvs): + """Remove trap group and verify that all related trap counters are removed + + Args: + dvs (object): virtual switch object + """ + self.setup_dbs(dvs) + meta_data = counter_group_meta['trap_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + + removed_group = None + trap_ids = None + copp_groups = self.app_db.db_connection.keys('COPP_TABLE:*') + for copp_group in copp_groups: + trap_ids = self.app_db.db_connection.hget(copp_group, 'trap_ids') + if trap_ids and trap_ids.strip(): + removed_group = copp_group.split(':')[1] + break + + if not removed_group: + pytest.skip('There is not copp group with at least 1 traps, skip rest of the test') + + trap_ids = [x.strip() for x in trap_ids.split(',')] + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + found = True + for trap_id in trap_ids: + if trap_id not in counters_keys: + found = False + break + if found: + break + else: + time.sleep(1) + + assert found, 'Not all trap id found in name map' + for trap_id in trap_ids: + self.wait_for_id_list(meta_data['group_name'], trap_id, counters_keys[trap_id]) + + app_copp_table = swsscommon.ProducerStateTable(self.app_db.db_connection, 'COPP_TABLE') + app_copp_table._del(removed_group) + + for trap_id in trap_ids: + self.wait_for_id_list_remove(meta_data['group_name'], trap_id, counters_keys[trap_id]) + + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + for trap_id in trap_ids: + assert trap_id not in counters_keys + + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') - if counter_type == "port_counter": - self.verify_only_phy_ports_created() - elif counter_type == "rif_counter": - self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") - elif counter_type == "acl_counter": - self.config_db.delete_entry('ACL_RULE', 'DATAACL|RULE0') - self.config_db.delete_entry('ACL_TABLE', 'DATAACL') - elif counter_type == "vxlan_tunnel_counter": - self.verify_tunnel_type_vxlan(counter_map, TUNNEL_TYPE_MAP) - self.config_db.delete_entry("VLAN","Vlan10") - self.config_db.delete_entry("VLAN_TUNNEL","vtep1") - self.config_db.delete_entry("VLAN_TUNNEL_MAP","vtep1|map_100_Vlan10") - self.verify_no_flex_counters_tables_after_delete(counter_stat) - def test_add_remove_ports(self, dvs): self.setup_dbs(dvs) # set flex counter - counter_key = counter_type_dict['queue_counter'][0] - counter_stat = counter_type_dict['queue_counter'][1] - counter_map = counter_type_dict['queue_counter'][2] + counter_key = counter_type_dict['queue_counter']['key'] + counter_stat = counter_type_dict['queue_counter']['group_name'] + counter_map = counter_type_dict['queue_counter']['name_map'] self.enable_flex_counter_group(counter_key, counter_map) # receive port info @@ -229,3 +436,4 @@ def test_add_remove_ports(self, dvs): assert len(fields) == 1 # the number of the oids needs to be the same as the original number of oids (before removing a port and adding) assert oid_list_len == len(oid_list) + diff --git a/tests/test_ipv6_link_local.py b/tests/test_ipv6_link_local.py new file mode 100644 index 0000000000..048b8f2e17 --- /dev/null +++ b/tests/test_ipv6_link_local.py @@ -0,0 +1,112 @@ +import time +import json +import pytest + +from swsscommon import swsscommon + +class TestIPv6LinkLocal(object): + def setup_db(self, dvs): + self.pdb = dvs.get_app_db() + self.adb = dvs.get_asic_db() + self.cdb = dvs.get_config_db() + + def set_admin_status(self, interface, status): + self.cdb.update_entry("PORT", interface, {"admin_status": status}) + + def add_ip_address(self, interface, ip): + self.cdb.create_entry("INTERFACE", interface + "|" + ip, {"NULL": "NULL"}) + time.sleep(2) + + def remove_ip_address(self, interface, ip): + self.cdb.delete_entry("INTERFACE", interface + "|" + ip) + time.sleep(2) + + def create_ipv6_link_local_intf(self, interface): + self.cdb.create_entry("INTERFACE", interface, {"ipv6_use_link_local_only": "enable"}) + time.sleep(2) + + def remove_ipv6_link_local_intf(self, interface): + self.cdb.delete_entry("INTERFACE", interface) + time.sleep(2) + + def test_NeighborAddRemoveIpv6LinkLocal(self, dvs, testlog): + self.setup_db(dvs) + + # create ipv6 link local intf + self.create_ipv6_link_local_intf("Ethernet0") + self.create_ipv6_link_local_intf("Ethernet4") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + + # set ip address + self.add_ip_address("Ethernet0", "2000::1/64") + self.add_ip_address("Ethernet4", "2001::1/64") + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") + dvs.runcmd("sysctl -w net.ipv6.conf.default.forwarding=1") + + # set ip address and default route + dvs.servers[0].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[0].runcmd("ip -6 route add default via 2000::1") + + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) + + # get neighbor entry + dvs.servers[0].runcmd("ping -6 -c 1 2001::2") + time.sleep(2) + + # Neigh entries should contain Ipv6-link-local neighbors, should be 4 + neigh_entries = self.pdb.get_keys("NEIGH_TABLE") + assert (len(neigh_entries) == 4) + + found_entry = False + for key in neigh_entries: + if (key.find("Ethernet4:2001::2") or key.find("Ethernet0:2000::2")): + found_entry = True + + assert found_entry + + # remove ip address + self.remove_ip_address("Ethernet0", "2000::1/64") + self.remove_ip_address("Ethernet4", "2001::1/64") + + # remove ipv6 link local intf + self.remove_ipv6_link_local_intf("Ethernet0") + self.remove_ipv6_link_local_intf("Ethernet4") + + # Neigh entries should not contain Ipv6-link-local neighbors, should be 2 + neigh_after_entries = self.pdb.get_keys("NEIGH_TABLE") + print(neigh_after_entries) + assert (len(neigh_after_entries) == 2) + + found_existing_entry = False + for key in neigh_entries: + if (key.find("Ethernet4:2001::2") or key.find("Ethernet0:2000::2")): + found_existing_entry = True + + assert found_existing_entry + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip -6 route del default dev eth0") + dvs.servers[0].runcmd("ip -6 address del 2000::2/64 dev eth0") + + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") + + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=0") + dvs.runcmd("sysctl -w net.ipv6.conf.default.forwarding=0") + + neigh_entries = self.pdb.get_keys("NEIGH_TABLE") + assert (len(neigh_entries) == 0) + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down before retrying +def test_nonflaky_dummy(): + pass + diff --git a/tests/test_mirror_ipv6_combined.py b/tests/test_mirror_ipv6_combined.py index cafa5eaf47..e75f6af72d 100644 --- a/tests/test_mirror_ipv6_combined.py +++ b/tests/test_mirror_ipv6_combined.py @@ -473,85 +473,6 @@ def test_AclBindMirrorV6Reorder2(self, dvs, testlog): self.set_interface_status("Ethernet32", "down") - # Test case - create ACL rules associated with wrong table - # 0. predefine the VS platform: mellanox platform - # 1. create a mirror session - # 2. create the ipv4 ACL table - # 3. create the ipv6 ACL rule associated with ipv4 table - # 4. create the ipv6 ACL table - # 5. create the ipv4 ACL rule associated with ipv6 table - # 6. verify two rules are inserted successfully - def test_AclBindMirrorV6WrongConfig(self, dvs, testlog): - """ - This test verifies IPv6 rules cannot be inserted into MIRROR table - """ - self.setup_db(dvs) - - session = "MIRROR_SESSION" - acl_table = "MIRROR_TABLE" - acl_table_v6 = "MIRROR_TABLE_V6" - acl_rule_1 = "MIRROR_RULE_1" - acl_rule_2 = "MIRROR_RULE_2" - - # bring up port; assign ip; create neighbor; create route - self.set_interface_status("Ethernet32", "up") - self.add_ip_address("Ethernet32", "20.0.0.0/31") - self.add_neighbor("Ethernet32", "20.0.0.1", "02:04:06:08:10:12") - self.add_route(dvs, "4.4.4.4", "20.0.0.1") - - # create mirror session - self.create_mirror_session(session, "3.3.3.3", "4.4.4.4", "0x6558", "8", "100", "0") - assert self.get_mirror_session_state(session)["status"] == "active" - - # assert mirror session in asic database - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_MIRROR_SESSION") - assert len(tbl.getKeys()) == 1 - mirror_session_oid = tbl.getKeys()[0] - - # create acl table ipv4 - self.create_acl_table(acl_table, ["Ethernet0", "Ethernet4"], "MIRROR") - - # create WRONG acl rule with IPv6 addresses - self.create_mirror_acl_ipv6_rule(acl_table, acl_rule_2, session) - - # assert acl rule is not created - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") - rule_entries = [k for k in tbl.getKeys() if k not in dvs.asicdb.default_acl_entries] - assert len(rule_entries) == 0 - - # create acl table ipv6 - self.create_acl_table(acl_table_v6, ["Ethernet0", "Ethernet4"], "MIRRORV6") - - # create WRONG acl rule with IPv4 addresses - self.create_mirror_acl_ipv4_rule(acl_table_v6, acl_rule_1, session) - - # assert acl rules are created - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") - rule_entries = [k for k in tbl.getKeys() if k not in dvs.asicdb.default_acl_entries] - assert len(rule_entries) == 0 - - # remove acl rule - self.remove_mirror_acl_rule(acl_table, acl_rule_1) - self.remove_mirror_acl_rule(acl_table_v6, acl_rule_2) - - # remove acl table - self.remove_acl_table(acl_table) - self.remove_acl_table(acl_table_v6) - - # remove mirror session - self.remove_mirror_session(session) - - # assert no mirror session in asic database - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_MIRROR_SESSION") - assert len(tbl.getKeys()) == 0 - - # remove route; remove neighbor; remove ip; bring down port - self.remove_route(dvs, "4.4.4.4") - self.remove_neighbor("Ethernet32", "20.0.0.1") - self.remove_ip_address("Ethernet32", "20.0.0.0/31") - self.set_interface_status("Ethernet32", "down") - - # Add Dummy always-pass test at end as workaroud # for issue when Flaky fail on final test it invokes module tear-down before retrying def test_nonflaky_dummy(): diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index ac65a30696..748e680e2a 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -11,6 +11,8 @@ CFG_LAG_TABLE_NAME = "PORTCHANNEL" CFG_LAG_MEMBER_TABLE_NAME = "PORTCHANNEL_MEMBER" CFG_VRF_TABLE_NAME = "VRF" +CFG_VXLAN_TUNNEL_TABLE_NAME = "VXLAN_TUNNEL" +CFG_VNET_TABLE_NAME = "VNET" STATE_PORT_TABLE_NAME = "PORT_TABLE" STATE_LAG_TABLE_NAME = "LAG_TABLE" @@ -20,6 +22,7 @@ APP_ROUTE_TABLE_NAME = "ROUTE_TABLE" APP_PORT_TABLE_NAME = "PORT_TABLE" APP_LAG_TABLE_NAME = "LAG_TABLE" +APP_VNET_TABLE_NAME = "VNET_TABLE" ASIC_RIF_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE" ASIC_ROUTE_ENTRY_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY" @@ -31,16 +34,27 @@ ASIC_VIRTUAL_ROUTER_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER" ASIC_PORT_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_PORT" ASIC_LAG_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_LAG" +ASIC_TUNNEL_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" ADMIN_STATUS = "admin_status" VRF_NAME = "vrf_name" +VNET_NAME = "vnet_name" +SRC_IP = "src_ip" +VXLAN_TUNNEL = "vxlan_tunnel" +VNI = "vni" +PEER_LIST = "peer_list" ETHERNET_PREFIX = "Ethernet" +SUBINTF_LAG_PREFIX = "Po" LAG_PREFIX = "PortChannel" +VRF_PREFIX = "Vrf" +VNET_PREFIX = "Vnet" VLAN_SUB_INTERFACE_SEPARATOR = "." APPL_DB_SEPARATOR = ":" +ETHERNET_PORT_DEFAULT_MTU = "1500" + class TestSubPortIntf(object): SUB_PORT_INTERFACE_UNDER_TEST = "Ethernet64.10" @@ -57,6 +71,11 @@ class TestSubPortIntf(object): VRF_UNDER_TEST = "Vrf0" + TUNNEL_UNDER_TEST = "Tunnel1" + VTEP_IP_UNDER_TEST = "1.1.1.1" + VNET_UNDER_TEST = "Vnet1000" + VNI_UNDER_TEST = "1000" + def connect_dbs(self, dvs): self.app_db = dvs.get_app_db() self.asic_db = dvs.get_asic_db() @@ -81,7 +100,62 @@ def connect_dbs(self, dvs): if phy_port in key: self.buf_q_fvs[key] = self.config_db.get_entry("BUFFER_QUEUE", key) + def get_subintf_longname(self, port_name): + if port_name is None: + return None + sub_intf_sep_idx = port_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + if sub_intf_sep_idx == -1: + return str(port_name) + parent_intf = port_name[:sub_intf_sep_idx] + sub_intf_idx = port_name[(sub_intf_sep_idx+1):] + if port_name.startswith("Eth"): + if port_name.startswith("Ethernet"): + intf_index=port_name[len("Ethernet"):sub_intf_sep_idx] + else: + intf_index=port_name[len("Eth"):sub_intf_sep_idx] + return "Ethernet"+intf_index+VLAN_SUB_INTERFACE_SEPARATOR+sub_intf_idx + elif port_name.startswith("Po"): + if port_name.startswith("PortChannel"): + intf_index=port_name[len("PortChannel"):sub_intf_sep_idx] + else: + intf_index=port_name[len("Po"):sub_intf_sep_idx] + return "PortChannel"+intf_index+VLAN_SUB_INTERFACE_SEPARATOR+sub_intf_idx + else: + return str(port_name) + + def get_port_longname(self, port_name): + if port_name is None: + return None + + if VLAN_SUB_INTERFACE_SEPARATOR in port_name: + return self.get_subintf_longname(port_name) + else: + if port_name.startswith("Eth"): + if port_name.startswith("Ethernet"): + return port_name + intf_index=port_name[len("Eth"):len(port_name)] + return "Ethernet"+intf_index + elif port_name.startswith("Po"): + if port_name.startswith("PortChannel"): + return port_name + intf_index=port_name[len("Po"):len(port_name)] + return "PortChannel"+intf_index + else: + return port_name + + def get_parent_port(self, port_name): + port = self.get_port_longname(port_name) + idx = port.find(VLAN_SUB_INTERFACE_SEPARATOR) + parent_port = "" + if port.startswith(ETHERNET_PREFIX): + parent_port = port[:idx] + else: + assert port.startswith(SUBINTF_LAG_PREFIX) + parent_port = port[:idx] + return parent_port + def get_parent_port_index(self, port_name): + port_name = self.get_port_longname(port_name) if port_name.startswith(ETHERNET_PREFIX): idx = int(port_name[len(ETHERNET_PREFIX):]) else: @@ -94,7 +168,7 @@ def set_parent_port_oper_status(self, dvs, port_name, status): srv_idx = self.get_parent_port_index(port_name) // 4 dvs.servers[srv_idx].runcmd("ip link set dev eth0 " + status) else: - assert port_name.startswith(LAG_PREFIX) + assert port_name.startswith(SUBINTF_LAG_PREFIX) dvs.runcmd("bash -c 'echo " + ("1" if status == "up" else "0") + " > /sys/class/net/" + port_name + "/carrier'") time.sleep(1) @@ -105,7 +179,7 @@ def set_parent_port_admin_status(self, dvs, port_name, status): if port_name.startswith(ETHERNET_PREFIX): tbl_name = CFG_PORT_TABLE_NAME else: - assert port_name.startswith(LAG_PREFIX) + assert port_name.startswith(SUBINTF_LAG_PREFIX) tbl_name = CFG_LAG_TABLE_NAME self.config_db.create_entry(tbl_name, port_name, fvs) time.sleep(1) @@ -116,13 +190,52 @@ def set_parent_port_admin_status(self, dvs, port_name, status): else: self.set_parent_port_oper_status(dvs, port_name, "up") + def create_vxlan_tunnel(self, tunnel_name, vtep_ip): + fvs = { + SRC_IP: vtep_ip, + } + self.config_db.create_entry(CFG_VXLAN_TUNNEL_TABLE_NAME, tunnel_name, fvs) + + def create_vnet(self, vnet_name, tunnel_name, vni, peer_list=""): + fvs = { + VXLAN_TUNNEL: tunnel_name, + VNI: vni, + PEER_LIST: peer_list, + } + self.config_db.create_entry(CFG_VNET_TABLE_NAME, vnet_name, fvs) + def create_vrf(self, vrf_name): - self.config_db.create_entry(CFG_VRF_TABLE_NAME, vrf_name, {"NULL": "NULL"}) + if vrf_name.startswith(VRF_PREFIX): + self.config_db.create_entry(CFG_VRF_TABLE_NAME, vrf_name, {"NULL": "NULL"}) + else: + assert vrf_name.startswith(VNET_PREFIX) + self.create_vxlan_tunnel(self.TUNNEL_UNDER_TEST, self.VTEP_IP_UNDER_TEST) + self.create_vnet(vrf_name, self.TUNNEL_UNDER_TEST, self.VNI_UNDER_TEST) + + def is_short_name(self, port_name): + idx = port_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + parent_port = port_name[:idx] + is_short = False + if parent_port.startswith("Eth"): + if parent_port.startswith(ETHERNET_PREFIX): + is_short = False + else: + is_short = True + elif parent_port.startswith("Po"): + if parent_port.startswith("PortChannel"): + is_short = False + else: + is_short = True + return is_short def create_sub_port_intf_profile(self, sub_port_intf_name, vrf_name=None): fvs = {ADMIN_STATUS: "up"} + idx = sub_port_intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + sub_port_idx = sub_port_intf_name[(idx+1):] + if self.is_short_name(sub_port_intf_name) == True: + fvs["vlan"] = sub_port_idx if vrf_name: - fvs[VRF_NAME] = vrf_name + fvs[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.config_db.create_entry(CFG_VLAN_SUB_INTF_TABLE_NAME, sub_port_intf_name, fvs) @@ -154,12 +267,16 @@ def add_lag_members(self, lag, members): self.config_db.create_entry(CFG_LAG_MEMBER_TABLE_NAME, key, fvs) def create_sub_port_intf_profile_appl_db(self, sub_port_intf_name, admin_status, vrf_name=None): + substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) + parent_port = self.get_parent_port(sub_port_intf_name) + vlan_id = substrs[1] pairs = [ (ADMIN_STATUS, admin_status), + ("vlan", vlan_id), ("mtu", "0"), ] if vrf_name: - pairs.append((VRF_NAME, vrf_name)) + pairs.append((VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME, vrf_name)) fvs = swsscommon.FieldValuePairs(pairs) tbl = swsscommon.ProducerStateTable(self.app_db.db_connection, APP_INTF_TABLE_NAME) @@ -192,8 +309,18 @@ def set_sub_port_intf_admin_status(self, sub_port_intf_name, status): self.config_db.create_entry(CFG_VLAN_SUB_INTF_TABLE_NAME, sub_port_intf_name, fvs) + def remove_vxlan_tunnel(self, tunnel_name): + self.config_db.delete_entry(CFG_VXLAN_TUNNEL_TABLE_NAME, tunnel_name) + + def remove_vnet(self, vnet_name): + self.config_db.delete_entry(CFG_VNET_TABLE_NAME, vnet_name) + def remove_vrf(self, vrf_name): - self.config_db.delete_entry(CFG_VRF_TABLE_NAME, vrf_name) + if vrf_name.startswith(VRF_PREFIX): + self.config_db.delete_entry(CFG_VRF_TABLE_NAME, vrf_name) + else: + assert vrf_name.startswith(VNET_PREFIX) + self.remove_vnet(vrf_name) def check_vrf_removal(self, vrf_oid): self.asic_db.wait_for_deleted_keys(ASIC_VIRTUAL_ROUTER_TABLE, [vrf_oid]) @@ -210,7 +337,7 @@ def remove_parent_port_appl_db(self, port_name): if port_name in key: self.config_db.delete_entry("BUFFER_QUEUE", key) else: - assert port_name.startswith(LAG_PREFIX) + assert port_name.startswith(SUBINTF_LAG_PREFIX) tbl_name = APP_LAG_TABLE_NAME tbl = swsscommon.ProducerStateTable(self.app_db.db_connection, tbl_name) tbl._del(port_name) @@ -298,17 +425,23 @@ def check_sub_port_intf_key_existence(self, db, table_name, key): def check_sub_port_intf_fvs(self, db, table_name, key, fv_dict): db.wait_for_field_match(table_name, key, fv_dict) - def check_sub_port_intf_route_entries(self, vrf_oid=None): + def check_sub_port_intf_route_entries(self, vrf_oid=None, check_ipv6=True): expected_dests = [self.IPV4_TOME_UNDER_TEST, self.IPV4_SUBNET_UNDER_TEST, self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST] + if not check_ipv6: + expected_dests.pop() + expected_dests.pop() if vrf_oid is None: vrf_oid = self.default_vrf_oid expected_vrf_oids = [vrf_oid, vrf_oid, vrf_oid, vrf_oid] + if not check_ipv6: + expected_vrf_oids.pop() + expected_vrf_oids.pop() def _access_function(): raw_route_entries = self.asic_db.get_keys(ASIC_ROUTE_ENTRY_TABLE) @@ -354,14 +487,14 @@ def _access_function(): def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) - parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] if parent_port.startswith(ETHERNET_PREFIX): state_tbl_name = STATE_PORT_TABLE_NAME phy_ports = [parent_port] parent_port_oid = dvs.asicdb.portnamemap[parent_port] else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) state_tbl_name = STATE_LAG_TABLE_NAME phy_ports = self.LAG_MEMBERS_UNDER_TEST old_lag_oids = self.get_oids(ASIC_LAG_TABLE) @@ -370,7 +503,7 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): old_rif_oids = self.get_oids(ASIC_RIF_TABLE) self.set_parent_port_admin_status(dvs, parent_port, "up") - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): parent_port_oid = self.get_newly_created_oid(ASIC_LAG_TABLE, old_lag_oids) # Add lag members to test physical port host interface vlan tag attribute self.add_lag_members(parent_port, self.LAG_MEMBERS_UNDER_TEST) @@ -403,7 +536,7 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that a sub port router interface entry is created in ASIC_DB @@ -435,8 +568,11 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): # Remove lag members from lag parent port self.remove_lag_members(parent_port, self.LAG_MEMBERS_UNDER_TEST) self.asic_db.wait_for_n_keys(ASIC_LAG_MEMBER_TABLE, 0) @@ -454,9 +590,12 @@ def test_sub_port_intf_creation(self, dvs): self._test_sub_port_intf_creation(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_creation(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_creation(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_creation(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) - parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vrf_oid = self.default_vrf_oid old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -468,7 +607,8 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) @@ -478,8 +618,9 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non } self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, sub_port_intf_name + "|" + self.IPV4_ADDR_UNDER_TEST, fv_dict) - self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, - sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST, fv_dict) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, + sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST, fv_dict) # Verify that ip address configuration is synced to APPL_DB INTF_TABLE by Intfmgrd fv_dict = { @@ -488,22 +629,26 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non } self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name + ":" + self.IPV4_ADDR_UNDER_TEST, fv_dict) - fv_dict["family"] = "IPv6" - self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, - sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST, fv_dict) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + fv_dict["family"] = "IPv6" + self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, + sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST, fv_dict) # Verify that an IPv4 ip2me route entry is created in ASIC_DB # Verify that an IPv4 subnet route entry is created in ASIC_DB - # Verify that an IPv6 ip2me route entry is created in ASIC_DB - # Verify that an IPv6 subnet route entry is created in ASIC_DB - self.check_sub_port_intf_route_entries(vrf_oid) + # Verify that an IPv6 ip2me route entry is created in ASIC_DB for non-vnet case + # Verify that an IPv6 subnet route entry is created in ASIC_DB for non-vnet case + self.check_sub_port_intf_route_entries(vrf_oid, check_ipv6=True if vrf_name is None or not vrf_name.startswith(VNET_PREFIX) else False) # Remove IP addresses + ip_addrs = [ + self.IPV4_ADDR_UNDER_TEST, + ] self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, - [self.IPV4_ADDR_UNDER_TEST, - self.IPV6_ADDR_UNDER_TEST]) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + ip_addrs.append(self.IPV6_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, ip_addrs) # Remove a sub port interface self.remove_sub_port_intf_profile(sub_port_intf_name) @@ -513,9 +658,12 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -528,9 +676,12 @@ def test_sub_port_intf_add_ip_addrs(self, dvs): self._test_sub_port_intf_add_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_add_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_add_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_add_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) - parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] vrf_oid = self.default_vrf_oid @@ -541,7 +692,7 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up if parent_port.startswith(ETHERNET_PREFIX): parent_port_oid = dvs.asicdb.portnamemap[parent_port] else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) parent_port_oid = self.get_newly_created_oid(ASIC_LAG_TABLE, old_lag_oids) if vrf_name: self.create_vrf(vrf_name) @@ -549,7 +700,8 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up # Create ip address configuration in APPL_DB before creating configuration for sub port interface itself self.add_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) time.sleep(2) # Create sub port interface configuration in APPL_DB @@ -570,7 +722,9 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up # Remove ip addresses from APPL_DB self.remove_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.remove_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + # Remove sub port interface from APPL_DB self.remove_sub_port_intf_profile_appl_db(sub_port_intf_name) self.check_sub_port_intf_profile_removal(rif_oid) @@ -579,9 +733,12 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.check_lag_removal(parent_port_oid) @@ -600,9 +757,16 @@ def test_sub_port_intf_appl_db_proc_seq(self, dvs): self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=True, vrf_name=self.VRF_UNDER_TEST) self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=False, vrf_name=self.VRF_UNDER_TEST) + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, admin_up=True, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, admin_up=False, vrf_name=self.VNET_UNDER_TEST) + + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=True, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=False, vrf_name=self.VNET_UNDER_TEST) + def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vrf_oid = self.default_vrf_oid old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -614,13 +778,14 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) fv_dict = { ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) fv_dict = { @@ -640,7 +805,7 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n ADMIN_STATUS: "down", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that sub port router interface entry in ASIC_DB has the updated admin status @@ -661,7 +826,7 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that sub port router interface entry in ASIC_DB has the updated admin status @@ -675,11 +840,14 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) # Remove IP addresses + ip_addrs = [ + self.IPV4_ADDR_UNDER_TEST, + ] self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, - [self.IPV4_ADDR_UNDER_TEST, - self.IPV6_ADDR_UNDER_TEST]) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + ip_addrs.append(self.IPV6_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, ip_addrs) # Remove a sub port interface self.remove_sub_port_intf_profile(sub_port_intf_name) @@ -689,9 +857,12 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -704,9 +875,13 @@ def test_sub_port_intf_admin_status_change(self, dvs): self._test_sub_port_intf_admin_status_change(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_admin_status_change(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_admin_status_change(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_admin_status_change(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -717,7 +892,8 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) @@ -737,21 +913,22 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= removed_route_entries = set([self.IPV4_TOME_UNDER_TEST, self.IPV4_SUBNET_UNDER_TEST]) self.check_sub_port_intf_route_entries_removal(removed_route_entries) - # Remove IPv6 address - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + # Remove IPv6 address for non-vnet cases + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - # Verify that IPv6 address state ok is removed from STATE_DB INTERFACE_TABLE by Intfmgrd - self.check_sub_port_intf_key_removal(self.state_db, STATE_INTERFACE_TABLE_NAME, - sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST) + # Verify that IPv6 address state ok is removed from STATE_DB INTERFACE_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.state_db, STATE_INTERFACE_TABLE_NAME, + sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST) - # Verify that IPv6 address configuration is removed from APPL_DB INTF_TABLE by Intfmgrd - self.check_sub_port_intf_key_removal(self.app_db, APP_INTF_TABLE_NAME, - sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST) + # Verify that IPv6 address configuration is removed from APPL_DB INTF_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.app_db, APP_INTF_TABLE_NAME, + sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST) - # Verify that IPv6 subnet route entry is removed from ASIC_DB - # Verify that IPv6 ip2me route entry is removed from ASIC_DB - removed_route_entries.update([self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST]) - self.check_sub_port_intf_route_entries_removal(removed_route_entries) + # Verify that IPv6 subnet route entry is removed from ASIC_DB + # Verify that IPv6 ip2me route entry is removed from ASIC_DB + removed_route_entries.update([self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST]) + self.check_sub_port_intf_route_entries_removal(removed_route_entries) # Verify that sub port router interface entry still exists in ASIC_DB self.check_sub_port_intf_key_existence(self.asic_db, ASIC_RIF_TABLE, rif_oid) @@ -764,9 +941,12 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= if vrf_name: self.remove_vrf(vrf_name) self.asic_db.wait_for_n_keys(ASIC_VIRTUAL_ROUTER_TABLE, 1) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -779,9 +959,13 @@ def test_sub_port_intf_remove_ip_addrs(self, dvs): self._test_sub_port_intf_remove_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_remove_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_remove_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_remove_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] if parent_port.startswith(ETHERNET_PREFIX): state_tbl_name = STATE_PORT_TABLE_NAME @@ -789,7 +973,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= parent_port_oid = dvs.asicdb.portnamemap[parent_port] asic_tbl_name = ASIC_PORT_TABLE else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) state_tbl_name = STATE_LAG_TABLE_NAME phy_ports = self.LAG_MEMBERS_UNDER_TEST old_lag_oids = self.get_oids(ASIC_LAG_TABLE) @@ -799,7 +983,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= old_rif_oids = self.get_oids(ASIC_RIF_TABLE) self.set_parent_port_admin_status(dvs, parent_port, "up") - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): parent_port_oid = self.get_newly_created_oid(ASIC_LAG_TABLE, old_lag_oids) if removal_seq_test == False: # Add lag members to test physical port host interface vlan tag attribute @@ -811,7 +995,8 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) fv_dict = { "state": "ok", @@ -831,7 +1016,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) if removal_seq_test == True: @@ -867,7 +1052,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that a sub port router interface entry persists in ASIC_DB @@ -881,16 +1066,23 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= "SAI_ROUTER_INTERFACE_ATTR_PORT_ID": parent_port_oid, } rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + #If subintf mtu deleted, it inherits from parent + if vrf_name == self.VRF_UNDER_TEST or vrf_name == self.VNET_UNDER_TEST: + if parent_port.startswith(ETHERNET_PREFIX): + fv_dict["SAI_ROUTER_INTERFACE_ATTR_MTU"] = ETHERNET_PORT_DEFAULT_MTU self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) else: rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) # Remove IP addresses + ip_addrs = [ + self.IPV4_ADDR_UNDER_TEST, + ] self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, - [self.IPV4_ADDR_UNDER_TEST, - self.IPV6_ADDR_UNDER_TEST]) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + ip_addrs.append(self.IPV6_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, ip_addrs) if removal_seq_test == False: # Remove a sub port interface @@ -931,6 +1123,9 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= if vrf_name: self.remove_vrf(vrf_name) self.asic_db.wait_for_n_keys(ASIC_VIRTUAL_ROUTER_TABLE, 1) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) if parent_port.startswith(ETHERNET_PREFIX): if removal_seq_test == True: @@ -955,15 +1150,22 @@ def test_sub_port_intf_removal(self, dvs): self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VRF_UNDER_TEST) self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VRF_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True) self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True) self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VRF_UNDER_TEST) self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VRF_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VNET_UNDER_TEST) + def _test_sub_port_intf_mtu(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vrf_oid = self.default_vrf_oid old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -1005,9 +1207,12 @@ def _test_sub_port_intf_mtu(self, dvs, sub_port_intf_name, vrf_name=None): if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -1020,6 +1225,9 @@ def test_sub_port_intf_mtu(self, dvs): self._test_sub_port_intf_mtu(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_mtu(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_mtu(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_mtu(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def create_nhg_router_intfs(self, dvs, parent_port_prefix, parent_port_idx_base, vlan_id, nhop_num, vrf_name=None): ifnames = [] parent_port_idx = parent_port_idx_base @@ -1126,13 +1334,14 @@ def check_nhg_members_on_parent_port_oper_status_change(self, dvs, parent_port_p def _test_sub_port_intf_nhg_accel(self, dvs, sub_port_intf_name, nhop_num=3, create_intf_on_parent_port=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] assert len(vlan_id) == 2 if parent_port.startswith(ETHERNET_PREFIX): parent_port_prefix = ETHERNET_PREFIX else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) parent_port_prefix = LAG_PREFIX parent_port_idx_base = self.get_parent_port_index(parent_port) @@ -1254,13 +1463,14 @@ def _test_sub_port_intf_oper_down_with_pending_neigh_route_tasks(self, dvs, sub_ create_intf_on_parent_port=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] assert len(vlan_id) == 2 if parent_port.startswith(ETHERNET_PREFIX): parent_port_prefix = ETHERNET_PREFIX else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) parent_port_prefix = LAG_PREFIX parent_port_idx_base = self.get_parent_port_index(parent_port) diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 595c80a28b..a41f9ee39f 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -420,6 +420,69 @@ def check_linux_intf_arp_proxy(dvs, ifname): assert out != "1", "ARP proxy is not enabled for VNET interface in Linux kernel" +def update_bfd_session_state(dvs, addr, state): + bfd_id = get_bfd_session_id(dvs, addr) + assert bfd_id is not None + + bfd_sai_state = {"Admin_Down": "SAI_BFD_SESSION_STATE_ADMIN_DOWN", + "Down": "SAI_BFD_SESSION_STATE_DOWN", + "Init": "SAI_BFD_SESSION_STATE_INIT", + "Up": "SAI_BFD_SESSION_STATE_UP"} + + asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + ntf = swsscommon.NotificationProducer(asic_db, "NOTIFICATIONS") + fvp = swsscommon.FieldValuePairs() + ntf_data = "[{\"bfd_session_id\":\""+bfd_id+"\",\"session_state\":\""+bfd_sai_state[state]+"\"}]" + ntf.send("bfd_session_state_change", ntf_data, fvp) + + +def get_bfd_session_id(dvs, addr): + asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_BFD_SESSION") + entries = set(tbl.getKeys()) + for entry in entries: + status, fvs = tbl.get(entry) + fvs = dict(fvs) + assert status, "Got an error when get a key" + if fvs["SAI_BFD_SESSION_ATTR_DST_IP_ADDRESS"] == addr: + return entry + + return None + + +def check_del_bfd_session(dvs, addrs): + for addr in addrs: + assert get_bfd_session_id(dvs, addr) is None + + +def check_bfd_session(dvs, addrs): + for addr in addrs: + assert get_bfd_session_id(dvs, addr) is not None + + +def check_state_db_routes(dvs, vnet, prefix, endpoints): + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(state_db, "VNET_ROUTE_TUNNEL_TABLE") + + status, fvs = tbl.get(vnet + '|' + prefix) + assert status, "Got an error when get a key" + + fvs = dict(fvs) + assert fvs['active_endpoints'] == ','.join(endpoints) + + if endpoints: + assert fvs['state'] == 'active' + else: + assert fvs['state'] == 'inactive' + + +def check_remove_state_db_routes(dvs, vnet, prefix): + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(state_db, "VNET_ROUTE_TUNNEL_TABLE") + keys = tbl.getKeys() + + assert vnet + '|' + prefix not in keys + loopback_id = 0 def_vr_id = 0 switch_mac = None @@ -438,6 +501,7 @@ class VnetVxlanVrfTunnel(object): ASIC_VLAN_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_VLAN" ASIC_NEXT_HOP_GROUP = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP" ASIC_NEXT_HOP_GROUP_MEMBER = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER" + ASIC_BFD_SESSION = "ASIC_STATE:SAI_OBJECT_TYPE_BFD_SESSION" tunnel_map_ids = set() tunnel_map_entry_ids = set() @@ -460,6 +524,7 @@ def fetch_exist_entries(self, dvs): self.routes = get_exist_entries(dvs, self.ASIC_ROUTE_ENTRY) self.nhops = get_exist_entries(dvs, self.ASIC_NEXT_HOP) self.nhgs = get_exist_entries(dvs, self.ASIC_NEXT_HOP_GROUP) + self.bfd_sessions = get_exist_entries(dvs, self.ASIC_BFD_SESSION) global loopback_id, def_vr_id, switch_mac if not loopback_id: @@ -863,6 +928,7 @@ def test_vnet_orch_1(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet_2000', '10.10.10.1') vnet_obj.check_vnet_routes(dvs, 'Vnet_2000', '10.10.10.1', tunnel_name) + check_state_db_routes(dvs, 'Vnet_2000', "100.100.1.1/32", ['10.10.10.1']) create_vnet_local_routes(dvs, "100.100.3.0/24", 'Vnet_2000', 'Vlan100') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_2000') @@ -883,6 +949,7 @@ def test_vnet_orch_1(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet_2001', '10.10.10.2', "00:12:34:56:78:9A") vnet_obj.check_vnet_routes(dvs, 'Vnet_2001', '10.10.10.2', tunnel_name, "00:12:34:56:78:9A") + check_state_db_routes(dvs, 'Vnet_2001', "100.100.2.1/32", ['10.10.10.2']) create_vnet_local_routes(dvs, "100.102.1.0/24", 'Vnet_2001', 'Ethernet4') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_2001') @@ -900,9 +967,11 @@ def test_vnet_orch_1(self, dvs, testlog): delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet_2001') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2001') + check_remove_state_db_routes(dvs, 'Vnet_2001', "100.100.2.1/32") delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet_2000') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2000') + check_remove_state_db_routes(dvs, 'Vnet_2000', "100.100.1.1/32") delete_phy_interface(dvs, "Ethernet4", "100.102.1.1/24") vnet_obj.check_del_router_interface(dvs, "Ethernet4") @@ -943,18 +1012,22 @@ def test_vnet_orch_2(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.10/32", 'Vnet_1', '100.1.1.10') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '100.1.1.10', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.10/32", ['100.1.1.10']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.11/32", 'Vnet_1', '100.1.1.10') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '100.1.1.10', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.11/32", ['100.1.1.10']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.12/32", 'Vnet_1', '200.200.1.200') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '200.200.1.200', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.12/32", ['200.200.1.200']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.14/32", 'Vnet_1', '200.200.1.201') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '200.200.1.201', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.14/32", ['200.200.1.201']) create_vnet_local_routes(dvs, "1.1.10.0/24", 'Vnet_1', 'Vlan1001') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_1') @@ -970,10 +1043,12 @@ def test_vnet_orch_2(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "2.2.2.10/32", 'Vnet_2', '100.1.1.20') vnet_obj.check_vnet_routes(dvs, 'Vnet_2', '100.1.1.20', tunnel_name) + check_state_db_routes(dvs, 'Vnet_2', "2.2.2.10/32", ['100.1.1.20']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "2.2.2.11/32", 'Vnet_2', '100.1.1.20') vnet_obj.check_vnet_routes(dvs, 'Vnet_2', '100.1.1.20', tunnel_name) + check_state_db_routes(dvs, 'Vnet_2', "2.2.2.11/32", ['100.1.1.20']) create_vnet_local_routes(dvs, "2.2.10.0/24", 'Vnet_2', 'Vlan1002') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_2') @@ -988,21 +1063,27 @@ def test_vnet_orch_2(self, dvs, testlog): delete_vnet_routes(dvs, "2.2.2.11/32", 'Vnet_2') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2') + check_remove_state_db_routes(dvs, 'Vnet_2', "2.2.2.11/32") delete_vnet_routes(dvs, "2.2.2.10/32", 'Vnet_2') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2') + check_remove_state_db_routes(dvs, 'Vnet_2', "2.2.2.10/32") delete_vnet_routes(dvs, "1.1.1.14/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.14/32") delete_vnet_routes(dvs, "1.1.1.12/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.12/32") delete_vnet_routes(dvs, "1.1.1.11/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.11/32") delete_vnet_routes(dvs, "1.1.1.10/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.10/32") delete_vlan_interface(dvs, "Vlan1002", "2.2.10.1/24") vnet_obj.check_del_router_interface(dvs, "Vlan1002") @@ -1049,10 +1130,12 @@ def test_vnet_orch_3(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "5.5.5.10/32", 'Vnet_10', '50.1.1.10') vnet_obj.check_vnet_routes(dvs, 'Vnet_10', '50.1.1.10', tunnel_name) + check_state_db_routes(dvs, 'Vnet_10', "5.5.5.10/32", ['50.1.1.10']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "8.8.8.10/32", 'Vnet_20', '80.1.1.20') vnet_obj.check_vnet_routes(dvs, 'Vnet_10', '80.1.1.20', tunnel_name) + check_state_db_routes(dvs, 'Vnet_20', "8.8.8.10/32", ['80.1.1.20']) create_vnet_local_routes(dvs, "5.5.10.0/24", 'Vnet_10', 'Vlan2001') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_10') @@ -1070,9 +1153,11 @@ def test_vnet_orch_3(self, dvs, testlog): delete_vnet_routes(dvs, "5.5.5.10/32", 'Vnet_10') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_10') + check_remove_state_db_routes(dvs, 'Vnet_10', "5.5.5.10/32") delete_vnet_routes(dvs, "8.8.8.10/32", 'Vnet_20') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_20') + check_remove_state_db_routes(dvs, 'Vnet_20', "8.8.8.10/32") delete_vlan_interface(dvs, "Vlan2001", "5.5.10.1/24") vnet_obj.check_del_router_interface(dvs, "Vlan2001") @@ -1112,9 +1197,11 @@ def test_vnet_orch_4(self, dvs, testlog): create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000') vnet_obj.check_vnet_routes(dvs, 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000', tunnel_name) + check_state_db_routes(dvs, 'Vnet3001', "100.100.1.1/32", ['2000:1000:2000:3000:4000:5000:6000:7000']) create_vnet_routes(dvs, "100.100.1.2/32", 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000') vnet_obj.check_vnet_routes(dvs, 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000', tunnel_name) + check_state_db_routes(dvs, 'Vnet3001', "100.100.1.2/32", ['2000:1000:2000:3000:4000:5000:6000:7000']) create_vnet_local_routes(dvs, "100.100.3.0/24", 'Vnet3001', 'Vlan300') vnet_obj.check_vnet_local_routes(dvs, 'Vnet3001') @@ -1134,6 +1221,7 @@ def test_vnet_orch_4(self, dvs, testlog): create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet3002', 'fd:2::34', "00:12:34:56:78:9A") vnet_obj.check_vnet_routes(dvs, 'Vnet3002', 'fd:2::34', tunnel_name, "00:12:34:56:78:9A") + check_state_db_routes(dvs, 'Vnet3002', "100.100.2.1/32", ['fd:2::34']) create_vnet_local_routes(dvs, "100.102.1.0/24", 'Vnet3002', 'Ethernet60') vnet_obj.check_vnet_local_routes(dvs, 'Vnet3002') @@ -1151,17 +1239,21 @@ def test_vnet_orch_4(self, dvs, testlog): create_vnet_routes(dvs, "5.5.5.10/32", 'Vnet3003', 'fd:2::35') vnet_obj.check_vnet_routes(dvs, 'Vnet3004', 'fd:2::35', tunnel_name) + check_state_db_routes(dvs, 'Vnet3003', "5.5.5.10/32", ['fd:2::35']) create_vnet_routes(dvs, "8.8.8.10/32", 'Vnet3004', 'fd:2::36') vnet_obj.check_vnet_routes(dvs, 'Vnet3003', 'fd:2::36', tunnel_name) + check_state_db_routes(dvs, 'Vnet3004', "8.8.8.10/32", ['fd:2::36']) # Clean-up and verify remove flows delete_vnet_routes(dvs, "5.5.5.10/32", 'Vnet3003') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3003') + check_remove_state_db_routes(dvs, 'Vnet3003', "5.5.5.10/32") delete_vnet_routes(dvs, "8.8.8.10/32", 'Vnet3004') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3004') + check_remove_state_db_routes(dvs, 'Vnet3004', "8.8.8.10/32") delete_vnet_entry(dvs, 'Vnet3003') vnet_obj.check_del_vnet_entry(dvs, 'Vnet3003') @@ -1171,6 +1263,7 @@ def test_vnet_orch_4(self, dvs, testlog): delete_vnet_routes(dvs, "100.100.2.1/24", 'Vnet3002') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3002') + check_remove_state_db_routes(dvs, 'Vnet3002', "100.100.2.1/24") delete_vnet_local_routes(dvs, "100.102.1.0/24", 'Vnet3002') vnet_obj.check_del_vnet_local_routes(dvs, 'Vnet3002') @@ -1189,9 +1282,11 @@ def test_vnet_orch_4(self, dvs, testlog): delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet3001') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3001') + check_remove_state_db_routes(dvs, 'Vnet3001', "100.100.1.1/32") delete_vnet_routes(dvs, "100.100.1.2/32", 'Vnet3001') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3001') + check_remove_state_db_routes(dvs, 'Vnet3001', "100.100.1.2/32") delete_vlan_interface(dvs, "Vlan300", "100.100.3.1/24") vnet_obj.check_del_router_interface(dvs, "Vlan300") @@ -1259,10 +1354,12 @@ def test_vnet_orch_7(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet7', '7.0.0.1,7.0.0.2,7.0.0.3') route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet7', ['7.0.0.1', '7.0.0.2', '7.0.0.3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet7', "100.100.1.1/32", ['7.0.0.1', '7.0.0.2', '7.0.0.3']) # Set the tunnel route to another nexthop group set_vnet_routes(dvs, "100.100.1.1/32", 'Vnet7', '7.0.0.1,7.0.0.2,7.0.0.3,7.0.0.4') route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet7', ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet7', "100.100.1.1/32", ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4']) # Check the previous nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1271,12 +1368,14 @@ def test_vnet_orch_7(self, dvs, testlog): # Create another tunnel route to the same set of endpoints create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet7', '7.0.0.1,7.0.0.2,7.0.0.3,7.0.0.4') route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet7', ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4'], tunnel_name) + check_state_db_routes(dvs, 'Vnet7', "100.100.2.1/32", ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4']) assert nhg2_1 == nhg1_2 # Remove one of the tunnel routes delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet7') vnet_obj.check_del_vnet_routes(dvs, 'Vnet7', ["100.100.1.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet7', "100.100.1.1/32") # Check the nexthop group still exists vnet_obj.fetch_exist_entries(dvs) @@ -1285,6 +1384,7 @@ def test_vnet_orch_7(self, dvs, testlog): # Remove the other tunnel route delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet7') vnet_obj.check_del_vnet_routes(dvs, 'Vnet7', ["100.100.2.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet7', "100.100.2.1/32") # Check the nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1315,10 +1415,12 @@ def test_vnet_orch_8(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "fd:8:10::32/128", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3') route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet8', "fd:8:10::32/128", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3']) # Set the tunnel route to another nexthop group set_vnet_routes(dvs, "fd:8:10::32/128", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3,fd:8:1::4') route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet8', "fd:8:10::32/128", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4']) # Check the previous nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1327,18 +1429,21 @@ def test_vnet_orch_8(self, dvs, testlog): # Create another tunnel route to the same set of endpoints create_vnet_routes(dvs, "fd:8:20::32/128", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3,fd:8:1::4') route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4'], tunnel_name) + check_state_db_routes(dvs, 'Vnet8', "fd:8:20::32/128", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4']) assert nhg2_1 == nhg1_2 # Create another tunnel route with ipv4 prefix to the same set of endpoints create_vnet_routes(dvs, "8.0.0.0/24", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3,fd:8:1::4') route3, nhg3_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4'], tunnel_name) + check_state_db_routes(dvs, 'Vnet8', "8.0.0.0/24", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4']) assert nhg3_1 == nhg1_2 # Remove one of the tunnel routes delete_vnet_routes(dvs, "fd:8:10::32/128", 'Vnet8') vnet_obj.check_del_vnet_routes(dvs, 'Vnet8', ["fd:8:10::32/128"]) + check_remove_state_db_routes(dvs, 'Vnet8', "fd:8:10::32/128") # Check the nexthop group still exists vnet_obj.fetch_exist_entries(dvs) @@ -1347,10 +1452,12 @@ def test_vnet_orch_8(self, dvs, testlog): # Remove tunnel route 2 delete_vnet_routes(dvs, "fd:8:20::32/128", 'Vnet8') vnet_obj.check_del_vnet_routes(dvs, 'Vnet8', ["fd:8:20::32/128"]) + check_remove_state_db_routes(dvs, 'Vnet8', "fd:8:20::32/128") # Remove tunnel route 3 delete_vnet_routes(dvs, "8.0.0.0/24", 'Vnet8') vnet_obj.check_del_vnet_routes(dvs, 'Vnet8', ["8.0.0.0/24"]) + check_remove_state_db_routes(dvs, 'Vnet8', "8.0.0.0/24") # Check the nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1358,6 +1465,337 @@ def test_vnet_orch_8(self, dvs, testlog): delete_vnet_entry(dvs, 'Vnet8') vnet_obj.check_del_vnet_entry(dvs, 'Vnet8') + + + ''' + Test 9 - Test for vnet tunnel routes with ECMP nexthop group with endpoint health monitor + ''' + def test_vnet_orch_9(self, dvs, testlog): + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_9' + + vnet_obj.fetch_exist_entries(dvs) + + create_vxlan_tunnel(dvs, tunnel_name, '9.9.9.9') + create_vnet_entry(dvs, 'Vnet9', tunnel_name, '10009', "") + + vnet_obj.check_vnet_entry(dvs, 'Vnet9') + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, 'Vnet9', '10009') + + vnet_obj.check_vxlan_tunnel(dvs, tunnel_name, '9.9.9.9') + + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet9', '9.0.0.1,9.0.0.2,9.0.0.3', ep_monitor='9.1.0.1,9.1.0.2,9.1.0.3') + + # default bfd status is down, route should not be programmed in this status + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.1.1/32"]) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", []) + + # Route should be properly configured when all bfd session states go up + update_bfd_session_state(dvs, '9.1.0.1', 'Up') + update_bfd_session_state(dvs, '9.1.0.2', 'Up') + update_bfd_session_state(dvs, '9.1.0.3', 'Up') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.2', '9.0.0.3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.2', '9.0.0.3']) + + # Remove endpoint from group if it goes down + update_bfd_session_state(dvs, '9.1.0.2', 'Down') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.3'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.3']) + + # Create another tunnel route with endpoint group overlapped with route1 + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet9', '9.0.0.1,9.0.0.2,9.0.0.5', ep_monitor='9.1.0.1,9.1.0.2,9.1.0.5') + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1'], tunnel_name) + check_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32", ['9.0.0.1']) + + # Update BFD session state and verify route change + update_bfd_session_state(dvs, '9.1.0.5', 'Up') + time.sleep(2) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32", ['9.0.0.1', '9.0.0.5']) + + # Update BFD state and check route nexthop + update_bfd_session_state(dvs, '9.1.0.3', 'Down') + time.sleep(2) + + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1']) + + # Set the route1 to a new group + set_vnet_routes(dvs, "100.100.1.1/32", 'Vnet9', '9.0.0.1,9.0.0.2,9.0.0.3,9.0.0.4', ep_monitor='9.1.0.1,9.1.0.2,9.1.0.3,9.1.0.4') + update_bfd_session_state(dvs, '9.1.0.4', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.4']) + + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_1 not in vnet_obj.nhgs + + # Set BFD session state for a down endpoint to up + update_bfd_session_state(dvs, '9.1.0.2', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.2', '9.0.0.4'], tunnel_name, route_ids=route1, nhg=nhg1_2) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.2', '9.0.0.4']) + + # Set all endpoint to down state + update_bfd_session_state(dvs, '9.1.0.1', 'Down') + update_bfd_session_state(dvs, '9.1.0.2', 'Down') + update_bfd_session_state(dvs, '9.1.0.3', 'Down') + update_bfd_session_state(dvs, '9.1.0.4', 'Down') + time.sleep(2) + + # Confirm the tunnel route is updated in ASIC + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.1.1/32"]) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32", ['9.0.0.5']) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", []) + + # Remove tunnel route2 + delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet9') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.2.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32") + + # Check the corresponding nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg2_1 not in vnet_obj.nhgs + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['9.1.0.5']) + check_bfd_session(dvs, ['9.1.0.1', '9.1.0.2', '9.1.0.3', '9.1.0.4']) + + # Remove tunnel route 1 + delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet9') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.1.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32") + + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_2 not in vnet_obj.nhgs + + # Confirm the BFD sessions are removed + check_del_bfd_session(dvs, ['9.1.0.1', '9.1.0.2', '9.1.0.3', '9.1.0.4', '9.1.0.5']) + + delete_vnet_entry(dvs, 'Vnet9') + vnet_obj.check_del_vnet_entry(dvs, 'Vnet9') + + + ''' + Test 10 - Test for ipv6 vnet tunnel routes with ECMP nexthop group with endpoint health monitor + ''' + def test_vnet_orch_10(self, dvs, testlog): + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_10' + + vnet_obj.fetch_exist_entries(dvs) + + create_vxlan_tunnel(dvs, tunnel_name, 'fd:10::32') + create_vnet_entry(dvs, 'Vnet10', tunnel_name, '10010', "") + + vnet_obj.check_vnet_entry(dvs, 'Vnet10') + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, 'Vnet10', '10010') + + vnet_obj.check_vxlan_tunnel(dvs, tunnel_name, 'fd:10::32') + + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "fd:10:10::1/128", 'Vnet10', 'fd:10:1::1,fd:10:1::2,fd:10:1::3', ep_monitor='fd:10:2::1,fd:10:2::2,fd:10:2::3') + + # default bfd status is down, route should not be programmed in this status + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:10::1/128"]) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", []) + + # Route should be properly configured when all bfd session states go up + update_bfd_session_state(dvs, 'fd:10:2::1', 'Up') + update_bfd_session_state(dvs, 'fd:10:2::2', 'Up') + update_bfd_session_state(dvs, 'fd:10:2::3', 'Up') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3']) + + # Remove endpoint from group if it goes down + update_bfd_session_state(dvs, 'fd:10:2::2', 'Down') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::3'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::3']) + + # Create another tunnel route with endpoint group overlapped with route1 + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "fd:10:20::1/128", 'Vnet10', 'fd:10:1::1,fd:10:1::2,fd:10:1::5', ep_monitor='fd:10:2::1,fd:10:2::2,fd:10:2::5') + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1'], tunnel_name) + check_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128", ['fd:10:1::1']) + + # Update BFD session state and verify route change + update_bfd_session_state(dvs, 'fd:10:2::5', 'Up') + time.sleep(2) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128", ['fd:10:1::1', 'fd:10:1::5']) + + # Update BFD state and check route nexthop + update_bfd_session_state(dvs, 'fd:10:2::3', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::2', 'Up') + time.sleep(2) + + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2']) + + # Set the route to a new group + set_vnet_routes(dvs, "fd:10:10::1/128", 'Vnet10', 'fd:10:1::1,fd:10:1::2,fd:10:1::3,fd:10:1::4', ep_monitor='fd:10:2::1,fd:10:2::2,fd:10:2::3,fd:10:2::4') + update_bfd_session_state(dvs, 'fd:10:2::4', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::4']) + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_1 not in vnet_obj.nhgs + + # Set BFD session state for a down endpoint to up + update_bfd_session_state(dvs, 'fd:10:2::3', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3', 'fd:10:1::4'], tunnel_name, route_ids=route1, nhg=nhg1_2) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3', 'fd:10:1::4']) + + # Set all endpoint to down state + update_bfd_session_state(dvs, 'fd:10:2::1', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::2', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::3', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::4', 'Down') + time.sleep(2) + + # Confirm the tunnel route is updated in ASIC + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:10::1/128"]) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128", ['fd:10:1::5']) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", []) + + # Remove tunnel route2 + delete_vnet_routes(dvs, "fd:10:20::1/128", 'Vnet10') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:20::1/128"]) + check_remove_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128") + + # Check the corresponding nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg2_1 not in vnet_obj.nhgs + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['fd:10:2::5']) + check_bfd_session(dvs, ['fd:10:2::1', 'fd:10:2::2', 'fd:10:2::3', 'fd:10:2::4']) + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['fd:10:2::5']) + check_bfd_session(dvs, ['fd:10:2::1', 'fd:10:2::2', 'fd:10:2::3', 'fd:10:2::4']) + + # Remove tunnel route 1 + delete_vnet_routes(dvs, "fd:10:10::1/128", 'Vnet10') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:10::1/128"]) + check_remove_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128") + + # Confirm the BFD sessions are removed + check_del_bfd_session(dvs, ['fd:10:2::1', 'fd:10:2::2', 'fd:10:2::3', 'fd:10:2::4', 'fd:10:2::5']) + + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_2 not in vnet_obj.nhgs + + delete_vnet_entry(dvs, 'Vnet10') + vnet_obj.check_del_vnet_entry(dvs, 'Vnet10') + + + ''' + Test 11 - Test for vnet tunnel routes with both single endpoint and ECMP group with endpoint health monitor + ''' + def test_vnet_orch_11(self, dvs, testlog): + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_11' + + vnet_obj.fetch_exist_entries(dvs) + + create_vxlan_tunnel(dvs, tunnel_name, '11.11.11.11') + create_vnet_entry(dvs, 'Vnet11', tunnel_name, '100011', "") + + vnet_obj.check_vnet_entry(dvs, 'Vnet11') + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, 'Vnet11', '100011') + + vnet_obj.check_vxlan_tunnel(dvs, tunnel_name, '11.11.11.11') + + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet11', '11.0.0.1', ep_monitor='11.1.0.1') + + # default bfd status is down, route should not be programmed in this status + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.1.1/32"]) + check_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32", []) + + # Route should be properly configured when bfd session state goes up + update_bfd_session_state(dvs, '11.1.0.1', 'Up') + time.sleep(2) + vnet_obj.check_vnet_routes(dvs, 'Vnet11', '11.0.0.1', tunnel_name) + check_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32", ['11.0.0.1']) + + # Create another tunnel route with endpoint group overlapped with route1 + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet11', '11.0.0.1,11.0.0.2', ep_monitor='11.1.0.1,11.1.0.2') + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet11', ['11.0.0.1'], tunnel_name) + check_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32", ['11.0.0.1']) + + # Create a third tunnel route with another endpoint + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.3.1/32", 'Vnet11', '11.0.0.2', ep_monitor='11.1.0.2') + + # Update BFD session state and verify route change + update_bfd_session_state(dvs, '11.1.0.2', 'Up') + time.sleep(2) + vnet_obj.check_vnet_routes(dvs, 'Vnet11', '11.0.0.2', tunnel_name) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet11', ['11.0.0.1', '11.0.0.2'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet11', "100.100.3.1/32", ['11.0.0.2']) + check_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32", ['11.0.0.1', '11.0.0.2']) + + update_bfd_session_state(dvs, '11.1.0.1', 'Down') + time.sleep(2) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet11', ['11.0.0.2'], tunnel_name, route_ids=route2, nhg=nhg2_1) + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.1.1/32"]) + check_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32", ['11.0.0.2']) + check_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32", []) + + # Set the route1 to a new endpoint + vnet_obj.fetch_exist_entries(dvs) + set_vnet_routes(dvs, "100.100.1.1/32", 'Vnet11', '11.0.0.2', ep_monitor='11.1.0.2') + vnet_obj.check_vnet_routes(dvs, 'Vnet11', '11.0.0.2', tunnel_name) + check_state_db_routes(dvs, 'Vnet11', "100.100.3.1/32", ['11.0.0.2']) + + # Remove tunnel route2 + delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet11') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.2.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32") + + # Check the corresponding nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg2_1 not in vnet_obj.nhgs + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['11.1.0.1']) + check_bfd_session(dvs, ['11.1.0.2']) + + # Remove tunnel route 1 + delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet11') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.1.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32") + + # Remove tunnel route 3 + delete_vnet_routes(dvs, "100.100.3.1/32", 'Vnet11') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.3.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet11', "100.100.3.1/32") + + # Confirm the BFD sessions are removed + check_del_bfd_session(dvs, ['11.1.0.1', '11.1.0.2']) + + delete_vnet_entry(dvs, 'Vnet11') + vnet_obj.check_del_vnet_entry(dvs, 'Vnet11') + # Add Dummy always-pass test at end as workaroud