From 9d69dd5f8c90ccaa19c9c4dadd991a564b94a329 Mon Sep 17 00:00:00 2001 From: yehjunying Date: Thu, 6 Jun 2019 16:41:42 +0800 Subject: [PATCH] [aclorch] unittest by gtest (#924) --- debian/rules | 5 +- tests/.clang-format | 104 +++ tests/Makefile.am | 39 +- tests/aclorch_ut.cpp | 1121 +++++++++++++++++++++++++++++ tests/check.h | 75 ++ tests/mock_consumerstatetable.cpp | 10 + tests/mock_dbconnector.cpp | 45 ++ tests/mock_hiredis.cpp | 24 + tests/mock_orchagent_main.cpp | 27 + tests/mock_redisreply.cpp | 16 + tests/portal.h | 62 ++ tests/request_parser_ut.cpp | 1 - tests/saispy.h | 107 +++ tests/saispy_ut.cpp | 219 ++++++ tests/ut_helper.h | 11 + 15 files changed, 1862 insertions(+), 4 deletions(-) create mode 100644 tests/.clang-format create mode 100644 tests/aclorch_ut.cpp create mode 100644 tests/check.h create mode 100644 tests/mock_consumerstatetable.cpp create mode 100644 tests/mock_dbconnector.cpp create mode 100644 tests/mock_hiredis.cpp create mode 100644 tests/mock_orchagent_main.cpp create mode 100644 tests/mock_redisreply.cpp create mode 100644 tests/portal.h create mode 100644 tests/saispy.h create mode 100644 tests/saispy_ut.cpp create mode 100644 tests/ut_helper.h diff --git a/debian/rules b/debian/rules index 9e606567300d..519b45324242 100755 --- a/debian/rules +++ b/debian/rules @@ -28,10 +28,13 @@ include /usr/share/dpkg/default.mk # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) override_dh_auto_configure: - dh_auto_configure -- + dh_auto_configure -- --enable-gtest override_dh_auto_install: dh_auto_install --destdir=debian/swss override_dh_strip: dh_strip --dbg-package=swss-dbg + +override_dh_auto_test: + ./tests/tests \ No newline at end of file diff --git a/tests/.clang-format b/tests/.clang-format new file mode 100644 index 000000000000..a44a288e357b --- /dev/null +++ b/tests/.clang-format @@ -0,0 +1,104 @@ +#Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +#AlignOperands: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +#BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +#ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +#IncludeCategories: +# - Regex: '^"config\.h"' +# Priority: -1 +# # The main header for a source file automatically gets category 0 +# - Regex: '.*' +# Priority: 1 +# - Regex: '^<.*\.h>' +# Priority: 2 +#IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +#MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +#PenaltyBreakAssignment: 2 +#PenaltyBreakBeforeFirstCallParameter: 19 +#PenaltyBreakComment: 300 +#PenaltyBreakFirstLessLess: 120 +#PenaltyBreakString: 1000 +#PenaltyExcessCharacter: 1000000 +#PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +#Standard: Cpp11 +TabWidth: 4 +UseTab: Never \ No newline at end of file diff --git a/tests/Makefile.am b/tests/Makefile.am index b5e570c6b4d3..0ce37fba340d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,12 +9,47 @@ else DBGFLAGS = -g -DNDEBUG endif +LDADD_SAI = -lsaimeta -lsaimetadata -lsaivs -lsairedis + CFLAGS_GTEST = LDADD_GTEST = -L/usr/src/gtest -tests_SOURCES = swssnet_ut.cpp request_parser_ut.cpp +tests_SOURCES = swssnet_ut.cpp request_parser_ut.cpp aclorch_ut.cpp saispy_ut.cpp \ + mock_orchagent_main.cpp \ + mock_dbconnector.cpp \ + mock_consumerstatetable.cpp \ + mock_hiredis.cpp \ + mock_redisreply.cpp \ + ../orchagent/orchdaemon.cpp \ + ../orchagent/orch.cpp \ + ../orchagent/notifications.cpp \ + ../orchagent/routeorch.cpp \ + ../orchagent/neighorch.cpp \ + ../orchagent/intfsorch.cpp \ + ../orchagent/portsorch.cpp \ + ../orchagent/copporch.cpp \ + ../orchagent/tunneldecaporch.cpp \ + ../orchagent/qosorch.cpp \ + ../orchagent/bufferorch.cpp \ + ../orchagent/mirrororch.cpp \ + ../orchagent/fdborch.cpp \ + ../orchagent/aclorch.cpp \ + ../orchagent/saihelper.cpp \ + ../orchagent/switchorch.cpp \ + ../orchagent/pfcwdorch.cpp \ + ../orchagent/pfcactionhandler.cpp \ + ../orchagent/policerorch.cpp \ + ../orchagent/crmorch.cpp \ + ../orchagent/request_parser.cpp \ + ../orchagent/vrforch.cpp \ + ../orchagent/countercheckorch.cpp \ + ../orchagent/vxlanorch.cpp \ + ../orchagent/vnetorch.cpp \ + ../orchagent/dtelorch.cpp \ + ../orchagent/flexcounterorch.cpp \ + ../orchagent/watermarkorch.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -tests_LDADD = $(LDADD_GTEST) -lnl-genl-3 -lhiredis -lhiredis -lpthread \ +tests_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis -lpthread \ -lswsscommon -lswsscommon -lgtest -lgtest_main diff --git a/tests/aclorch_ut.cpp b/tests/aclorch_ut.cpp new file mode 100644 index 000000000000..e0bab4842807 --- /dev/null +++ b/tests/aclorch_ut.cpp @@ -0,0 +1,1121 @@ +#include "ut_helper.h" + +extern sai_object_id_t gSwitchId; + +extern CrmOrch *gCrmOrch; +extern PortsOrch *gPortsOrch; +extern RouteOrch *gRouteOrch; +extern IntfsOrch *gIntfsOrch; +extern NeighOrch *gNeighOrch; + +extern FdbOrch *gFdbOrch; +extern MirrorOrch *gMirrorOrch; +extern VRFOrch *gVrfOrch; + +extern sai_acl_api_t *sai_acl_api; +extern sai_switch_api_t *sai_switch_api; +extern sai_port_api_t *sai_port_api; +extern sai_vlan_api_t *sai_vlan_api; +extern sai_bridge_api_t *sai_bridge_api; +extern sai_route_api_t *sai_route_api; + +namespace aclorch_test +{ + using namespace std; + + size_t consumerAddToSync(Consumer *consumer, const deque &entries) + { + /* Nothing popped */ + if (entries.empty()) + { + return 0; + } + + for (auto &entry : entries) + { + string key = kfvKey(entry); + string op = kfvOp(entry); + + /* If a new task comes or if a DEL task comes, we directly put it into getConsumerTable().m_toSync map */ + if (consumer->m_toSync.find(key) == consumer->m_toSync.end() || op == DEL_COMMAND) + { + consumer->m_toSync[key] = entry; + } + /* If an old task is still there, we combine the old task with new task */ + else + { + KeyOpFieldsValuesTuple existing_data = consumer->m_toSync[key]; + + auto new_values = kfvFieldsValues(entry); + auto existing_values = kfvFieldsValues(existing_data); + + for (auto it : new_values) + { + string field = fvField(it); + string value = fvValue(it); + + auto iu = existing_values.begin(); + while (iu != existing_values.end()) + { + string ofield = fvField(*iu); + if (field == ofield) + iu = existing_values.erase(iu); + else + iu++; + } + existing_values.push_back(FieldValueTuple(field, value)); + } + consumer->m_toSync[key] = KeyOpFieldsValuesTuple(key, op, existing_values); + } + } + return entries.size(); + } + + struct AclTestBase : public ::testing::Test + { + vector m_s32list_pool; + + virtual ~AclTestBase() + { + for (auto p : m_s32list_pool) + { + free(p); + } + } + }; + + struct AclTest : public AclTestBase + { + + struct CreateAclResult + { + bool ret_val; + + vector attr_list; + }; + + shared_ptr m_config_db; + + AclTest() + { + m_config_db = make_shared(CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + } + + void SetUp() override + { + ASSERT_EQ(gCrmOrch, nullptr); + gCrmOrch = new CrmOrch(m_config_db.get(), CFG_CRM_TABLE_NAME); + + ASSERT_EQ(sai_acl_api, nullptr); + sai_acl_api = new sai_acl_api_t(); + } + + void TearDown() override + { + delete gCrmOrch; + gCrmOrch = nullptr; + + delete sai_acl_api; + sai_acl_api = nullptr; + } + + shared_ptr createAclTable(AclTable &acl) + { + auto ret = make_shared(); + + auto spy = SpyOn(&sai_acl_api->create_acl_table); + spy->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t attr_count, const sai_attribute_t *attr_list) -> sai_status_t { + for (uint32_t i = 0; i < attr_count; ++i) + { + ret->attr_list.emplace_back(attr_list[i]); + } + return SAI_STATUS_SUCCESS; + }); + + ret->ret_val = acl.create(); + return ret; + } + }; + + TEST_F(AclTest, Create_L3_Acl_Table) + { + AclTable acltable; + acltable.type = ACL_TABLE_L3; + auto res = createAclTable(acltable); + + ASSERT_TRUE(res->ret_val); + + auto v = 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_ETHER_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_DST_IP", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS", "true" }, + { "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" }, + { "SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_INGRESS" } }); + SaiAttributeList attr_list(SAI_OBJECT_TYPE_ACL_TABLE, v, false); + + ASSERT_TRUE(Check::AttrListEq(SAI_OBJECT_TYPE_ACL_TABLE, res->attr_list, attr_list)); + } + + struct MockAclOrch + { + AclOrch *m_aclOrch; + swss::DBConnector *config_db; + + MockAclOrch(swss::DBConnector *config_db, swss::DBConnector *state_db, + PortsOrch *portsOrch, MirrorOrch *mirrorOrch, NeighOrch *neighOrch, RouteOrch *routeOrch) : + config_db(config_db) + { + TableConnector confDbAclTable(config_db, CFG_ACL_TABLE_NAME); + TableConnector confDbAclRuleTable(config_db, CFG_ACL_RULE_TABLE_NAME); + + vector acl_table_connectors = { confDbAclTable, confDbAclRuleTable }; + + TableConnector stateDbSwitchTable(state_db, "SWITCH_CAPABILITY"); + + m_aclOrch = new AclOrch(acl_table_connectors, stateDbSwitchTable, portsOrch, mirrorOrch, + neighOrch, routeOrch); + } + + ~MockAclOrch() + { + delete m_aclOrch; + } + + operator const AclOrch *() const + { + return m_aclOrch; + } + + void doAclTableTask(const deque &entries) + { + auto consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(config_db, CFG_ACL_TABLE_NAME, 1, 1), m_aclOrch, CFG_ACL_TABLE_NAME)); + + consumerAddToSync(consumer.get(), entries); + + static_cast(m_aclOrch)->doTask(*consumer); + } + + void doAclRuleTask(const deque &entries) + { + auto consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(config_db, CFG_ACL_RULE_TABLE_NAME, 1, 1), m_aclOrch, CFG_ACL_RULE_TABLE_NAME)); + + consumerAddToSync(consumer.get(), entries); + + static_cast(m_aclOrch)->doTask(*consumer); + } + + sai_object_id_t getTableById(const string &table_id) + { + return m_aclOrch->getTableById(table_id); + } + + const map &getAclTables() const + { + return Portal::AclOrchInternal::getAclTables(m_aclOrch); + } + }; + + struct AclOrchTest : public AclTest + { + + shared_ptr m_app_db; + shared_ptr m_config_db; + shared_ptr m_state_db; + + AclOrchTest() + { + // FIXME: move out from constructor + m_app_db = make_shared(APPL_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_config_db = make_shared(CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_state_db = make_shared(STATE_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + } + + static map gProfileMap; + static map::iterator gProfileIter; + + static const char *profile_get_value( + sai_switch_profile_id_t profile_id, + const char *variable) + { + map::const_iterator it = gProfileMap.find(variable); + if (it == gProfileMap.end()) + { + return NULL; + } + + return it->second.c_str(); + } + + static int profile_get_next_value( + sai_switch_profile_id_t profile_id, + const char **variable, + const char **value) + { + if (value == NULL) + { + gProfileIter = gProfileMap.begin(); + return 0; + } + + if (variable == NULL) + { + return -1; + } + + if (gProfileIter == gProfileMap.end()) + { + return -1; + } + + *variable = gProfileIter->first.c_str(); + *value = gProfileIter->second.c_str(); + + gProfileIter++; + + return 0; + } + + void SetUp() override + { + AclTestBase::SetUp(); + + // Init switch and create dependencies + + gProfileMap.emplace("SAI_VS_SWITCH_TYPE", "SAI_VS_SWITCH_TYPE_BCM56850"); + gProfileMap.emplace("KV_DEVICE_MAC_ADDRESS", "20:03:04:05:06:00"); + + sai_service_method_table_t test_services = { + AclOrchTest::profile_get_value, + AclOrchTest::profile_get_next_value + }; + + auto status = sai_api_initialize(0, (sai_service_method_table_t *)&test_services); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + sai_api_query(SAI_API_SWITCH, (void **)&sai_switch_api); + sai_api_query(SAI_API_BRIDGE, (void **)&sai_bridge_api); + sai_api_query(SAI_API_PORT, (void **)&sai_port_api); + sai_api_query(SAI_API_VLAN, (void **)&sai_vlan_api); + sai_api_query(SAI_API_ROUTE, (void **)&sai_route_api); + sai_api_query(SAI_API_ACL, (void **)&sai_acl_api); + + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; + attr.value.booldata = true; + + status = sai_switch_api->create_switch(&gSwitchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // Get switch source MAC address + attr.id = SAI_SWITCH_ATTR_SRC_MAC_ADDRESS; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + gMacAddress = attr.value.mac; + + // Get the default virtual router ID + attr.id = SAI_SWITCH_ATTR_DEFAULT_VIRTUAL_ROUTER_ID; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + gVirtualRouterId = attr.value.oid; + + // Create dependencies ... + + const int portsorch_base_pri = 40; + + vector ports_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } + }; + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), ports_tables); + + ASSERT_EQ(gCrmOrch, nullptr); + gCrmOrch = new CrmOrch(m_config_db.get(), CFG_CRM_TABLE_NAME); + + ASSERT_EQ(gVrfOrch, nullptr); + gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME); + + ASSERT_EQ(gIntfsOrch, nullptr); + gIntfsOrch = new IntfsOrch(m_app_db.get(), APP_INTF_TABLE_NAME, gVrfOrch); + + ASSERT_EQ(gNeighOrch, nullptr); + gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch); + + ASSERT_EQ(gRouteOrch, nullptr); + gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gNeighOrch); + + TableConnector applDbFdb(m_app_db.get(), APP_FDB_TABLE_NAME); + TableConnector stateDbFdb(m_state_db.get(), STATE_FDB_TABLE_NAME); + + ASSERT_EQ(gFdbOrch, nullptr); + gFdbOrch = new FdbOrch(applDbFdb, stateDbFdb, gPortsOrch); + + PolicerOrch *policer_orch = new PolicerOrch(m_config_db.get(), "POLICER"); + + TableConnector stateDbMirrorSession(m_state_db.get(), STATE_MIRROR_SESSION_TABLE_NAME); + TableConnector confDbMirrorSession(m_config_db.get(), CFG_MIRROR_SESSION_TABLE_NAME); + + ASSERT_EQ(gMirrorOrch, nullptr); + gMirrorOrch = new MirrorOrch(stateDbMirrorSession, confDbMirrorSession, + gPortsOrch, gRouteOrch, gNeighOrch, gFdbOrch, policer_orch); + + auto consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(m_app_db.get(), APP_PORT_TABLE_NAME, 1, 1), gPortsOrch, APP_PORT_TABLE_NAME)); + + consumerAddToSync(consumer.get(), { { "PortInitDone", EMPTY_PREFIX, { { "", "" } } } }); + + static_cast(gPortsOrch)->doTask(*consumer.get()); + } + + void TearDown() override + { + AclTestBase::TearDown(); + + delete gFdbOrch; + gFdbOrch = nullptr; + delete gMirrorOrch; + gMirrorOrch = nullptr; + delete gRouteOrch; + gRouteOrch = nullptr; + delete gNeighOrch; + gNeighOrch = nullptr; + delete gIntfsOrch; + gIntfsOrch = nullptr; + delete gVrfOrch; + gVrfOrch = nullptr; + delete gCrmOrch; + gCrmOrch = nullptr; + delete gPortsOrch; + gPortsOrch = nullptr; + + auto status = sai_switch_api->remove_switch(gSwitchId); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + gSwitchId = 0; + + sai_api_uninitialize(); + + sai_switch_api = nullptr; + sai_acl_api = nullptr; + sai_port_api = nullptr; + sai_vlan_api = nullptr; + sai_bridge_api = nullptr; + sai_route_api = nullptr; + } + + shared_ptr createAclOrch() + { + return make_shared(m_config_db.get(), m_state_db.get(), gPortsOrch, gMirrorOrch, + gNeighOrch, gRouteOrch); + } + + shared_ptr getAclTableAttributeList(sai_object_type_t objecttype, const AclTable &acl_table) + { + vector fields; + + fields.push_back({ "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST", "2:SAI_ACL_BIND_POINT_TYPE_PORT,SAI_ACL_BIND_POINT_TYPE_LAG" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", "true" }); + + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT", "true" }); + 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) + { + case ACL_TABLE_L3: + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IP", "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" }); + break; + + default: + // We shouldn't get here. Will continue to add more test cases ...; + ; + } + + if (ACL_STAGE_INGRESS == acl_table.stage) + { + fields.push_back({ "SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_INGRESS" }); + } + else if (ACL_STAGE_EGRESS == acl_table.stage) + { + fields.push_back({ "SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_EGRESS" }); + } + + return shared_ptr(new SaiAttributeList(objecttype, fields, false)); + } + + shared_ptr getAclRuleAttributeList(sai_object_type_t objecttype, const AclRule &acl_rule, sai_object_id_t acl_table_oid, const AclTable &acl_table) + { + vector fields; + + auto table_id = sai_serialize_object_id(acl_table_oid); + auto counter_id = sai_serialize_object_id(const_cast(acl_rule).getCounterOid()); // FIXME: getcounterOid() should be const + + fields.push_back({ "SAI_ACL_ENTRY_ATTR_TABLE_ID", table_id }); + fields.push_back({ "SAI_ACL_ENTRY_ATTR_PRIORITY", "0" }); + 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) + { + 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 ... + ; + } + + return shared_ptr(new SaiAttributeList(objecttype, fields, false)); + } + + bool validateAclRule(const string acl_rule_sid, const AclRule &acl_rule, sai_object_id_t acl_table_oid, const AclTable &acl_table) + { + sai_object_type_t objecttype = SAI_OBJECT_TYPE_ACL_ENTRY; + auto exp_attrlist_2 = getAclRuleAttributeList(objecttype, acl_rule, acl_table_oid, acl_table); + + auto acl_rule_oid = Portal::AclRuleInternal::getRuleOid(&acl_rule); + + { + auto &exp_attrlist = *exp_attrlist_2; + + vector act_attr; + + for (uint32_t i = 0; i < exp_attrlist.get_attr_count(); ++i) + { + const auto attr = exp_attrlist.get_attr_list()[i]; + auto meta = sai_metadata_get_attr_metadata(objecttype, attr.id); + + if (meta == nullptr) + { + return false; + } + + sai_attribute_t new_attr; + memset(&new_attr, 0, sizeof(new_attr)); + + new_attr.id = attr.id; + + switch (meta->attrvaluetype) + { + case SAI_ATTR_VALUE_TYPE_INT32_LIST: + new_attr.value.s32list.list = (int32_t *)malloc(sizeof(int32_t) * attr.value.s32list.count); + new_attr.value.s32list.count = attr.value.s32list.count; + m_s32list_pool.emplace_back(new_attr.value.s32list.list); + break; + + default: + // do nothing + ; + } + + act_attr.emplace_back(new_attr); + } + + auto status = sai_acl_api->get_acl_entry_attribute(acl_rule_oid, (uint32_t)act_attr.size(), act_attr.data()); + if (status != SAI_STATUS_SUCCESS) + { + return false; + } + + auto b_attr_eq = Check::AttrListEq(objecttype, act_attr, exp_attrlist); + if (!b_attr_eq) + { + return false; + } + } + + return true; + } + + bool validateAclTable(sai_object_id_t acl_table_oid, const AclTable &acl_table) + { + const sai_object_type_t objecttype = SAI_OBJECT_TYPE_ACL_TABLE; + auto exp_attrlist_2 = getAclTableAttributeList(objecttype, acl_table); + + { + auto &exp_attrlist = *exp_attrlist_2; + + vector act_attr; + + for (uint32_t i = 0; i < exp_attrlist.get_attr_count(); ++i) + { + const auto attr = exp_attrlist.get_attr_list()[i]; + auto meta = sai_metadata_get_attr_metadata(objecttype, attr.id); + + if (meta == nullptr) + { + return false; + } + + sai_attribute_t new_attr; + memset(&new_attr, 0, sizeof(new_attr)); + + new_attr.id = attr.id; + + switch (meta->attrvaluetype) + { + case SAI_ATTR_VALUE_TYPE_INT32_LIST: + new_attr.value.s32list.list = (int32_t *)malloc(sizeof(int32_t) * attr.value.s32list.count); + new_attr.value.s32list.count = attr.value.s32list.count; + m_s32list_pool.emplace_back(new_attr.value.s32list.list); + break; + + default: + // do nothing + ; + } + + act_attr.emplace_back(new_attr); + } + + auto status = sai_acl_api->get_acl_table_attribute(acl_table_oid, (uint32_t)act_attr.size(), act_attr.data()); + if (status != SAI_STATUS_SUCCESS) + { + return false; + } + + auto b_attr_eq = Check::AttrListEq(objecttype, act_attr, exp_attrlist); + if (!b_attr_eq) + { + return false; + } + } + + for (const auto &sid_acl_rule : acl_table.rules) + { + auto b_valid = validateAclRule(sid_acl_rule.first, *sid_acl_rule.second, acl_table_oid, acl_table); + if (!b_valid) + { + return false; + } + } + + return true; + } + + // consistency validation with CRM + bool validateResourceCountWithCrm(const AclOrch *aclOrch, CrmOrch *crmOrch) + { + auto resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); + auto ifpPortKey = Portal::CrmOrchInternal::getCrmAclKey(crmOrch, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); + auto efpPortKey = Portal::CrmOrchInternal::getCrmAclKey(crmOrch, SAI_ACL_STAGE_EGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); + + auto ifpPortAclTableCount = resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap[ifpPortKey].usedCounter; + auto efpPortAclTableCount = resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap[efpPortKey].usedCounter; + + return ifpPortAclTableCount + efpPortAclTableCount == Portal::AclOrchInternal::getAclTables(aclOrch).size(); + + // TODO: add rule check + } + + // leakage check + bool validateResourceCountWithLowerLayerDb(const AclOrch *aclOrch) + { + // TODO: Using the need to include "sai_vs_state.h". That will need the include path from `configure` + // Do this later ... +#if WITH_SAI == LIBVS + // { + // auto& aclTableHash = g_switch_state_map.at(gSwitchId)->objectHash.at(SAI_OBJECT_TYPE_ACL_TABLE); + // + // return aclTableHash.size() == Portal::AclOrchInternal::getAclTables(aclOrch).size(); + // } + // + // TODO: add rule check +#endif + + return true; + } + + // validate consistency between aclOrch and mock data (via SAI) + bool validateLowerLayerDb(const MockAclOrch *orch) + { + assert(orch != nullptr); + + if (!validateResourceCountWithCrm(orch->m_aclOrch, gCrmOrch)) + { + return false; + } + + if (!validateResourceCountWithLowerLayerDb(orch->m_aclOrch)) + { + return false; + } + + const auto &acl_tables = orch->getAclTables(); + + for (const auto &id_acl_table : acl_tables) + { + if (!validateAclTable(id_acl_table.first, id_acl_table.second)) + { + return false; + } + } + + return true; + } + + bool validateAclTableByConfOp(const AclTable &acl_table, const vector &values) + { + for (const auto &fv : values) + { + if (fv.first == TABLE_DESCRIPTION) + { + } + else if (fv.first == 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 + { + return false; + } + } + else if (fv.first == TABLE_STAGE) + { + if (fv.second == TABLE_INGRESS) + { + if (acl_table.stage != ACL_STAGE_INGRESS) + { + return false; + } + } + else if (fv.second == TABLE_EGRESS) + { + if (acl_table.stage != ACL_STAGE_EGRESS) + { + return false; + } + } + else + { + return false; + } + } + else if (fv.first == TABLE_PORTS) + { + } + } + + return true; + } + + bool validateAclRuleAction(const AclRule &acl_rule, const string &attr_name, const string &attr_value) + { + const auto &rule_actions = Portal::AclRuleInternal::getActions(&acl_rule); + + if (attr_name == ACTION_PACKET_ACTION) + { + auto it = rule_actions.find(SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION); + if (it == rule_actions.end()) + { + return false; + } + + if (it->second.aclaction.enable != true) + { + return false; + } + + if (attr_value == PACKET_ACTION_FORWARD) + { + if (it->second.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) + { + return false; + } + } + else + { + // unkonw attr_value + return false; + } + } + else + { + // unknow attr_name + return false; + } + + return true; + } + + bool validateAclRuleMatch(const AclRule &acl_rule, const string &attr_name, const string &attr_value) + { + const auto &rule_matches = Portal::AclRuleInternal::getMatches(&acl_rule); + + if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP) + { + auto it_field = rule_matches.find(attr_name == MATCH_SRC_IP ? SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP : SAI_ACL_ENTRY_ATTR_FIELD_DST_IP); + if (it_field == rule_matches.end()) + { + return false; + } + + char addr[20]; + sai_serialize_ip4(addr, it_field->second.aclfield.data.ip4); + if (attr_value != addr) + { + return false; + } + + char mask[20]; + sai_serialize_ip4(mask, it_field->second.aclfield.mask.ip4); + if (string(mask) != "255.255.255.255") + { + return false; + } + } + else if (attr_name == MATCH_SRC_IPV6) + { + auto it_field = rule_matches.find(SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6); + if (it_field == rule_matches.end()) + { + return false; + } + + char addr[46]; + sai_serialize_ip6(addr, it_field->second.aclfield.data.ip6); + if (attr_value != addr) + { + return false; + } + + char mask[46]; + sai_serialize_ip6(mask, it_field->second.aclfield.mask.ip6); + if (string(mask) != "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + { + return false; + } + } + else + { + // unknow attr_name + return false; + } + + return true; + } + + bool validateAclRuleByConfOp(const AclRule &acl_rule, const vector &values) + { + for (const auto &fv : values) + { + auto attr_name = fv.first; + auto attr_value = fv.second; + + if (attr_name == ACTION_PACKET_ACTION) + { + if (!validateAclRuleAction(acl_rule, attr_name, attr_value)) + { + return false; + } + } + else if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP || attr_name == MATCH_SRC_IPV6) + { + if (!validateAclRuleMatch(acl_rule, attr_name, attr_value)) + { + return false; + } + } + else + { + // unknow attr_name + return false; + } + } + return true; + } + }; + + map AclOrchTest::gProfileMap; + map::iterator AclOrchTest::gProfileIter = AclOrchTest::gProfileMap.begin(); + + // When received ACL table SET_COMMAND, orchagent can create corresponding ACL. + // When received ACL table DEL_COMMAND, orchagent can delete corresponding ACL. + // + // Input by type = {L3, L3V6, PFCCMD ...}, stage = {INGRESS, EGRESS}. + // + // Using fixed ports = {"1,2"} for now. + // The bind operations will be another separately test cases. + TEST_F(AclOrchTest, ACL_Creation_and_Destorying) + { + auto orch = createAclOrch(); + + for (const auto &acl_table_type : { TABLE_TYPE_L3, TABLE_TYPE_L3V6 }) + { + for (const auto &acl_table_stage : { TABLE_INGRESS, TABLE_EGRESS }) + { + string acl_table_id = "acl_table_1"; + + auto kvfAclTable = deque( + { { acl_table_id, + SET_COMMAND, + { { TABLE_DESCRIPTION, "filter source IP" }, + { TABLE_TYPE, acl_table_type }, + { TABLE_STAGE, acl_table_stage }, + { TABLE_PORTS, "1,2" } } } }); + // FIXME: ^^^^^^^^^^^^^ fixed port + + orch->doAclTableTask(kvfAclTable); + + auto oid = orch->getTableById(acl_table_id); + ASSERT_NE(oid, SAI_NULL_OBJECT_ID); + + const auto &acl_tables = orch->getAclTables(); + + auto it = acl_tables.find(oid); + ASSERT_NE(it, acl_tables.end()); + + const auto &acl_table = it->second; + + ASSERT_TRUE(validateAclTableByConfOp(acl_table, kfvFieldsValues(kvfAclTable.front()))); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + + // delete acl table ... + + kvfAclTable = deque( + { { acl_table_id, + DEL_COMMAND, + {} } }); + + orch->doAclTableTask(kvfAclTable); + + oid = orch->getTableById(acl_table_id); + ASSERT_EQ(oid, SAI_NULL_OBJECT_ID); + + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + } + } + } + + // When received ACL rule SET_COMMAND, orchagent can create corresponding ACL rule. + // When received ACL rule DEL_COMMAND, orchagent can delete corresponding ACL rule. + // + // Verify ACL table type = { L3 }, stage = { INGRESS, ENGRESS } + // Input by matchs = { SIP, DIP ...}, pkg:actions = { FORWARD, DROP ... } + // + TEST_F(AclOrchTest, L3Acl_Matches_Actions) + { + 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, + { { TABLE_DESCRIPTION, "filter source IP" }, + { TABLE_TYPE, TABLE_TYPE_L3 }, + // ^^^^^^^^^^^^^ L3 ACL + { TABLE_STAGE, TABLE_INGRESS }, + // FIXME: ^^^^^^^^^^^^^ only support / test for ingress ? + { TABLE_PORTS, "1,2" } } } }); + // FIXME: ^^^^^^^^^^^^^ fixed port + + 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()); + + const auto &acl_table = it_table->second; + + ASSERT_TRUE(validateAclTableByConfOp(acl_table, kfvFieldsValues(kvfAclTable.front()))); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + + // add rule ... + for (const auto &acl_rule_pkg_action : { PACKET_ACTION_FORWARD, PACKET_ACTION_DROP }) + { + + auto kvfAclRule = deque({ { acl_table_id + "|" + acl_rule_id, + SET_COMMAND, + { { ACTION_PACKET_ACTION, acl_rule_pkg_action }, + + // if (attr_name == ACTION_PACKET_ACTION || attr_name == ACTION_MIRROR_ACTION || + // attr_name == ACTION_DTEL_FLOW_OP || attr_name == ACTION_DTEL_INT_SESSION || + // attr_name == ACTION_DTEL_DROP_REPORT_ENABLE || + // attr_name == ACTION_DTEL_TAIL_DROP_REPORT_ENABLE || + // attr_name == ACTION_DTEL_FLOW_SAMPLE_PERCENT || + // attr_name == ACTION_DTEL_REPORT_ALL_PACKETS) + // + // TODO: required field (add new test cases for that ....) + // + + { MATCH_SRC_IP, "1.2.3.4" }, + { MATCH_DST_IP, "4.3.2.1" } } } }); + + // TODO: RULE_PRIORITY (important field) + // TODO: MATCH_DSCP / MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6 + + orch->doAclRuleTask(kvfAclRule); + + // validate acl rule ... + + auto it_rule = acl_table.rules.find(acl_rule_id); + ASSERT_NE(it_rule, acl_table.rules.end()); + + ASSERT_TRUE(validateAclRuleByConfOp(*it_rule->second, kfvFieldsValues(kvfAclRule.front()))); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + + // delete acl rule ... + + kvfAclRule = deque({ { acl_table_id + "|" + acl_rule_id, + DEL_COMMAND, + {} } }); + + orch->doAclRuleTask(kvfAclRule); + + // validate acl rule ... + + it_rule = acl_table.rules.find(acl_rule_id); + ASSERT_EQ(it_rule, acl_table.rules.end()); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + } + } + + // When received ACL rule SET_COMMAND, orchagent can create corresponding ACL rule. + // When received ACL rule DEL_COMMAND, orchagent can delete corresponding ACL rule. + // + // Verify ACL table type = { L3V6 }, stage = { INGRESS, ENGRESS } + // Input by matchs = { SIP, DIP ...}, pkg:actions = { FORWARD, DROP ... } + // + TEST_F(AclOrchTest, L3V6Acl_Matches_Actions) + { + 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, + { { TABLE_DESCRIPTION, "filter source IP" }, + { TABLE_TYPE, TABLE_TYPE_L3V6 }, + // ^^^^^^^^^^^^^ L3V6 ACL + { TABLE_STAGE, TABLE_INGRESS }, + // FIXME: ^^^^^^^^^^^^^ only support / test for ingress ? + { TABLE_PORTS, "1,2" } } } }); + // FIXME: ^^^^^^^^^^^^^ fixed port + + 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()); + + const auto &acl_table = it_table->second; + + ASSERT_TRUE(validateAclTableByConfOp(acl_table, kfvFieldsValues(kvfAclTable.front()))); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + + // add rule ... + for (const auto &acl_rule_pkg_action : { PACKET_ACTION_FORWARD, PACKET_ACTION_DROP }) + { + + auto kvfAclRule = deque({ { acl_table_id + "|" + acl_rule_id, + SET_COMMAND, + { { ACTION_PACKET_ACTION, acl_rule_pkg_action }, + + // if (attr_name == ACTION_PACKET_ACTION || attr_name == ACTION_MIRROR_ACTION || + // attr_name == ACTION_DTEL_FLOW_OP || attr_name == ACTION_DTEL_INT_SESSION || + // attr_name == ACTION_DTEL_DROP_REPORT_ENABLE || + // attr_name == ACTION_DTEL_TAIL_DROP_REPORT_ENABLE || + // attr_name == ACTION_DTEL_FLOW_SAMPLE_PERCENT || + // attr_name == ACTION_DTEL_REPORT_ALL_PACKETS) + // + // TODO: required field (add new test cases for that ....) + // + + { MATCH_SRC_IPV6, "::1.2.3.4" }, + /*{ MATCH_DST_IP, "4.3.2.1" }*/ } } }); + + // TODO: RULE_PRIORITY (important field) + // TODO: MATCH_DSCP / MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6 + + orch->doAclRuleTask(kvfAclRule); + + // validate acl rule ... + + auto it_rule = acl_table.rules.find(acl_rule_id); + ASSERT_NE(it_rule, acl_table.rules.end()); + + ASSERT_TRUE(validateAclRuleByConfOp(*it_rule->second, kfvFieldsValues(kvfAclRule.front()))); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + + // delete acl rule ... + + kvfAclRule = deque({ { acl_table_id + "|" + acl_rule_id, + DEL_COMMAND, + {} } }); + + orch->doAclRuleTask(kvfAclRule); + + // validate acl rule ... + + it_rule = acl_table.rules.find(acl_rule_id); + ASSERT_EQ(it_rule, acl_table.rules.end()); + ASSERT_TRUE(validateLowerLayerDb(orch.get())); + } + } + +} // namespace nsAclOrchTest diff --git a/tests/check.h b/tests/check.h new file mode 100644 index 000000000000..3eddcecc2ca0 --- /dev/null +++ b/tests/check.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include + +#include "saiattributelist.h" + +struct Check +{ + static bool AttrListEq(sai_object_type_t objecttype, const std::vector &act_attr_list, SaiAttributeList &exp_attr_list) + { + if (act_attr_list.size() != exp_attr_list.get_attr_count()) + { + return false; + } + + for (uint32_t i = 0; i < exp_attr_list.get_attr_count(); ++i) + { + sai_attr_id_t id = exp_attr_list.get_attr_list()[i].id; + auto meta = sai_metadata_get_attr_metadata(objecttype, id); + + assert(meta != nullptr); + + // The following id can not serialize, check id only + if (id == SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE || id == SAI_ACL_BIND_POINT_TYPE_PORT || id == SAI_ACL_BIND_POINT_TYPE_LAG) + { + if (id != act_attr_list[i].id) + { + auto meta_act = sai_metadata_get_attr_metadata(objecttype, act_attr_list[i].id); + + if (meta_act) + { + std::cerr << "AttrListEq failed\n"; + std::cerr << "Actual: " << meta_act->attridname << "\n"; + std::cerr << "Expected: " << meta->attridname << "\n"; + } + } + + continue; + } + + const int MAX_BUF_SIZE = 0x4000; + std::string act_str; + std::string exp_str; + + act_str.reserve(MAX_BUF_SIZE); + exp_str.reserve(MAX_BUF_SIZE); + + auto act_len = sai_serialize_attribute_value(&act_str[0], meta, &act_attr_list[i].value); + auto exp_len = sai_serialize_attribute_value(&exp_str[0], meta, &exp_attr_list.get_attr_list()[i].value); + + assert(act_len < act_str.size()); + assert(act_len < exp_str.size()); + + if (act_len != exp_len) + { + std::cerr << "AttrListEq failed\n"; + std::cerr << "Actual: " << act_str << "\n"; + std::cerr << "Expected: " << exp_str << "\n"; + return false; + } + + if (act_str != exp_str) + { + std::cerr << "AttrListEq failed\n"; + std::cerr << "Actual: " << act_str << "\n"; + std::cerr << "Expected: " << exp_str << "\n"; + return false; + } + } + + return true; + } +}; diff --git a/tests/mock_consumerstatetable.cpp b/tests/mock_consumerstatetable.cpp new file mode 100644 index 000000000000..0af574a5f59b --- /dev/null +++ b/tests/mock_consumerstatetable.cpp @@ -0,0 +1,10 @@ +#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) + { + } +} \ No newline at end of file diff --git a/tests/mock_dbconnector.cpp b/tests/mock_dbconnector.cpp new file mode 100644 index 000000000000..14f955eb3eef --- /dev/null +++ b/tests/mock_dbconnector.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include + +#include "dbconnector.h" + +namespace swss +{ + DBConnector::~DBConnector() + { + close(m_conn->fd); + if (m_conn->connection_type == REDIS_CONN_TCP) + free(m_conn->tcp.host); + else + free(m_conn->unix_sock.path); + free(m_conn); + } + + DBConnector::DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout) : + m_dbId(dbId) + { + m_conn = (redisContext *)calloc(1, sizeof(redisContext)); + m_conn->connection_type = REDIS_CONN_TCP; + m_conn->tcp.host = strdup(hostname.c_str()); + m_conn->tcp.port = port; + m_conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + } + + DBConnector::DBConnector(int dbId, const std::string &unixPath, unsigned int timeout) : + m_dbId(dbId) + { + m_conn = (redisContext *)calloc(1, sizeof(redisContext)); + m_conn->connection_type = REDIS_CONN_UNIX; + m_conn->unix_sock.path = strdup(unixPath.c_str()); + m_conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + } + + int DBConnector::getDbId() const + { + return 12345; + } +} \ No newline at end of file diff --git a/tests/mock_hiredis.cpp b/tests/mock_hiredis.cpp new file mode 100644 index 000000000000..921447543331 --- /dev/null +++ b/tests/mock_hiredis.cpp @@ -0,0 +1,24 @@ +#include +#include + +int redisGetReply(redisContext *c, void **reply) +{ + *reply = calloc(sizeof(redisReply), 1); + ((redisReply *)*reply)->type = 3; + return 0; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) +{ + return 0; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) +{ + return 0; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) +{ + return 0; +} \ No newline at end of file diff --git a/tests/mock_orchagent_main.cpp b/tests/mock_orchagent_main.cpp new file mode 100644 index 000000000000..60b5f5e00f15 --- /dev/null +++ b/tests/mock_orchagent_main.cpp @@ -0,0 +1,27 @@ +extern "C" { +#include "sai.h" +#include "saistatus.h" +} + +#include "orchdaemon.h" + +/* Global variables */ +sai_object_id_t gVirtualRouterId; +sai_object_id_t gUnderlayIfId; +sai_object_id_t gSwitchId = SAI_NULL_OBJECT_ID; +MacAddress gMacAddress; +MacAddress gVxlanMacAddress; + +#define DEFAULT_BATCH_SIZE 128 +int gBatchSize = DEFAULT_BATCH_SIZE; + +bool gSairedisRecord = true; +bool gSwssRecord = true; +bool gLogRotate = false; +ofstream gRecordOfs; +string gRecordFile; + +MirrorOrch *gMirrorOrch; +VRFOrch *gVrfOrch; + +void syncd_apply_view() {} \ No newline at end of file diff --git a/tests/mock_redisreply.cpp b/tests/mock_redisreply.cpp new file mode 100644 index 000000000000..391b8531c596 --- /dev/null +++ b/tests/mock_redisreply.cpp @@ -0,0 +1,16 @@ +#include "redisreply.h" + +namespace swss +{ + void RedisReply::checkStatus(const char *status) + { + } + + void RedisReply::checkReply() + { + } + + void RedisReply::checkReplyType(int expectedType) + { + } +} \ No newline at end of file diff --git a/tests/portal.h b/tests/portal.h new file mode 100644 index 000000000000..7dab40036ba7 --- /dev/null +++ b/tests/portal.h @@ -0,0 +1,62 @@ +#pragma once + +#define private public +#define protected public + +#include "aclorch.h" +#include "crmorch.h" + +#undef protected +#undef private + +struct Portal +{ + struct AclRuleInternal + { + static sai_object_id_t getRuleOid(const AclRule *aclRule) + { + return aclRule->m_ruleOid; + } + + static const map &getMatches(const AclRule *aclRule) + { + return aclRule->m_matches; + } + + static const map &getActions(const AclRule *aclRule) + { + return aclRule->m_actions; + } + }; + + struct AclOrchInternal + { + static const map &getAclTables(const AclOrch *aclOrch) + { + return aclOrch->m_AclTables; + } + }; + + struct CrmOrchInternal + { + static const std::map &getResourceMap(const CrmOrch *crmOrch) + { + return crmOrch->m_resourcesMap; + } + + static std::string getCrmAclKey(CrmOrch *crmOrch, sai_acl_stage_t stage, sai_acl_bind_point_type_t bindPoint) + { + return crmOrch->getCrmAclKey(stage, bindPoint); + } + + static std::string getCrmAclTableKey(CrmOrch *crmOrch, sai_object_id_t id) + { + return crmOrch->getCrmAclTableKey(id); + } + + static void getResAvailableCounters(CrmOrch *crmOrch) + { + crmOrch->getResAvailableCounters(); + } + }; +}; diff --git a/tests/request_parser_ut.cpp b/tests/request_parser_ut.cpp index ba8a504578c4..b82fa9e2ccb9 100644 --- a/tests/request_parser_ut.cpp +++ b/tests/request_parser_ut.cpp @@ -7,7 +7,6 @@ #include "macaddress.h" #include "orch.h" #include "request_parser.h" -#include "request_parser.cpp" const request_description_t request_description1 = { { REQ_T_STRING }, diff --git a/tests/saispy.h b/tests/saispy.h new file mode 100644 index 000000000000..535ef130bbdb --- /dev/null +++ b/tests/saispy.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#include "saitypes.h" + +// Spy C functin pointer to std::function to access closure +// Internal using static `spy` function pointer to invoke std::function `fake` +// To make sure the convert work for multiple function in the same or different API table. +// The caller shall passing to create unique SaiSpyFunction class. +// +// Almost use cases will like the follow, pass n=0 and objecttype/offset to use. +// auto x = SpyOn<0, SAI_OBJECT_TYPE_ACL_TABLE>(&acl_api_1.get()->create_acl_table); +// auto y = SpyOn<0, SAI_OBJECT_TYPE_ACL_ENTRY>(&acl_api_1.get()->create_acl_entry); +// or +// auto x = SpyOn<0, offsetof(sai_acl_api_t, create_acl_table)>(&acl_api.get()->create_acl_table); +// auto x = SpyOn<0, offsetof(sai_acl_api_t, create_acl_entry)>(&acl_api.get()->create_acl_entry); +// +// or pass n=api_api for different API table +// auto x = SpyOn(&acl_api.get()->create_acl_table); +// auto y = SpyOn(&switch_api.get()->create_switch); +// +// The rest rare case is spy same function in different API table. Using differnt n value for that. +// auto x = SpyOn<0, SAI_OBJECT_TYPE_ACL_TABLE>(&acl_api_1.get()->create_acl_table); +// auto y = SpyOn<1, SAI_OBJECT_TYPE_ACL_TABLE>(&acl_api_2.get()->create_acl_table); +// +template +struct SaiSpyFunctor +{ + + using original_fn_t = R (*)(arglist...); + using original_fn_ptr_t = R (**)(arglist...); + + original_fn_t original_fn; + static std::function fake; + + SaiSpyFunctor(original_fn_ptr_t fn_ptr) : + original_fn(*fn_ptr) + { + *fn_ptr = spy; + } + + void callFake(std::function fn) + { + fake = fn; + } + + static sai_status_t spy(arglist... args) + { + // TODO: pass this into fake. Inside fake() it can call original_fn + return fake(args...); + } +}; + +template +std::function SaiSpyFunctor::fake; + +// create entry +template +std::shared_ptr> + SpyOn(sai_status_t (**fn_ptr)(sai_object_id_t *, sai_object_id_t, uint32_t, const sai_attribute_t *)) +{ + using SaiSpyCreateFunctor = SaiSpyFunctor; + + return std::make_shared(fn_ptr); +} + +// create entry without input oid +template +std::shared_ptr> + SpyOn(sai_status_t (**fn_ptr)(sai_object_id_t *, uint32_t, const sai_attribute_t *)) +{ + using SaiSpyCreateFunctor = SaiSpyFunctor; + + return std::make_shared(fn_ptr); +} + +// remove entry +template +std::shared_ptr> + SpyOn(sai_status_t (**fn_ptr)(sai_object_id_t)) +{ + using SaiSpyRemoveFunctor = SaiSpyFunctor; + + return std::make_shared(fn_ptr); +} + +// set entry attribute +template +std::shared_ptr> + SpyOn(sai_status_t (**fn_ptr)(sai_object_id_t, const sai_attribute_t *)) +{ + using SaiSpySetAttrFunctor = SaiSpyFunctor; + + return std::make_shared(fn_ptr); +} + +// get entry attribute +template +std::shared_ptr> + SpyOn(sai_status_t (**fn_ptr)(sai_object_id_t, uint32_t, sai_attribute_t *)) +{ + using SaiSpyGetAttrFunctor = SaiSpyFunctor; + + return std::make_shared(fn_ptr); +} diff --git a/tests/saispy_ut.cpp b/tests/saispy_ut.cpp new file mode 100644 index 000000000000..075db3c0abaf --- /dev/null +++ b/tests/saispy_ut.cpp @@ -0,0 +1,219 @@ +#include "gtest/gtest.h" +#include "saispy.h" + +#include "sai.h" + +TEST(SaiSpy, CURD) +{ + auto acl_api = std::make_shared(); + + acl_api->create_acl_table = [](sai_object_id_t *oid, sai_object_id_t, uint32_t, + const sai_attribute_t *) { + *oid = 1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + acl_api->remove_acl_table = [](sai_object_id_t oid) { + return (sai_status_t)(oid == 2 ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE); + }; + + acl_api->set_acl_table_attribute = [](sai_object_id_t oid, + const sai_attribute_t *) { + return (sai_status_t)(oid == 3 ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE); + }; + + acl_api->get_acl_table_attribute = [](sai_object_id_t oid, uint32_t, + sai_attribute_t *) { + return (sai_status_t)(oid == 4 ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE); + }; + + sai_object_id_t oid; + + auto status = acl_api->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, 1); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + status = acl_api->remove_acl_table(2); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + status = acl_api->set_acl_table_attribute(3, nullptr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + status = acl_api->get_acl_table_attribute(4, 0, nullptr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + sai_object_id_t exp_oid_1 = 100; + sai_object_id_t exp_oid_2 = 200; + + auto x = SpyOn<0, offsetof(sai_acl_api_t, create_acl_table)>(&acl_api.get()->create_acl_table); + x->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + auto y = SpyOn<0, offsetof(sai_acl_api_t, remove_acl_table)>(&acl_api.get()->remove_acl_table); + y->callFake([&](sai_object_id_t oid) -> sai_status_t { + return (sai_status_t)(oid == exp_oid_2 ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE); + }); + + auto w = SpyOn<0, offsetof(sai_acl_api_t, set_acl_table_attribute)>(&acl_api.get()->set_acl_table_attribute); + w->callFake([&](sai_object_id_t oid, const sai_attribute_t *) -> sai_status_t { + return (sai_status_t)(oid == exp_oid_2 ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE); + }); + + auto z = SpyOn<0, offsetof(sai_acl_api_t, get_acl_table_attribute)>(&acl_api.get()->get_acl_table_attribute); + z->callFake([&](sai_object_id_t oid, uint32_t, sai_attribute_t *) -> sai_status_t { + return (sai_status_t)(oid == exp_oid_2 ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE); + }); + + acl_api->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, exp_oid_1); + + status = acl_api->remove_acl_table(exp_oid_2); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + status = acl_api->set_acl_table_attribute(exp_oid_2, nullptr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + status = acl_api->get_acl_table_attribute(exp_oid_2, 0, nullptr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); +} + +TEST(SaiSpy, Same_Function_Signature_In_Same_API_Table) +{ + auto acl_api_1 = std::make_shared(); + acl_api_1->create_acl_table = [](sai_object_id_t *oid, sai_object_id_t, uint32_t, + const sai_attribute_t *) { + *oid = 1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + acl_api_1->create_acl_entry = [](sai_object_id_t *oid, sai_object_id_t, uint32_t, + const sai_attribute_t *) { + *oid = 2; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + sai_object_id_t oid; + + acl_api_1->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, 1); + + acl_api_1->create_acl_entry(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, 2); + + sai_object_id_t exp_oid_1 = 100; + sai_object_id_t exp_oid_2 = 200; + + auto x = SpyOn<0, SAI_OBJECT_TYPE_ACL_TABLE>(&acl_api_1.get()->create_acl_table); + x->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + auto y = SpyOn<0, SAI_OBJECT_TYPE_ACL_ENTRY>(&acl_api_1.get()->create_acl_entry); + y->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_2; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + acl_api_1->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, exp_oid_1); + + acl_api_1->create_acl_entry(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, exp_oid_2); +} + +TEST(SaiSpy, Same_Function_Signature_In_Different_API_Table) +{ + auto acl_api_1 = std::make_shared(); //std::shared_ptr(new sai_acl_api_t()); + auto acl_api_2 = std::make_shared(); + acl_api_1->create_acl_table = [](sai_object_id_t *oid, sai_object_id_t, uint32_t, + const sai_attribute_t *) { + *oid = 1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + acl_api_2->create_acl_table = [](sai_object_id_t *oid, sai_object_id_t, uint32_t, + const sai_attribute_t *) { + *oid = 2; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + sai_object_id_t oid; + + acl_api_1->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, 1); + + acl_api_2->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, 2); + + sai_object_id_t exp_oid_1 = 100; + sai_object_id_t exp_oid_2 = 200; + + auto x = SpyOn<0, SAI_OBJECT_TYPE_ACL_TABLE>(&acl_api_1.get()->create_acl_table); + // ^ using different number for same api table type with different instance + x->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + auto y = SpyOn<1, SAI_OBJECT_TYPE_ACL_TABLE>(&acl_api_2.get()->create_acl_table); + // ^ using different number for same api table type with different instance + y->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_2; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + acl_api_1->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, exp_oid_1); + + acl_api_2->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, exp_oid_2); +} + +TEST(SaiSpy, create_switch_and_acl_table) +{ + auto acl_api = std::make_shared(); + auto switch_api = std::make_shared(); + acl_api->create_acl_table = [](sai_object_id_t *oid, sai_object_id_t, uint32_t, + const sai_attribute_t *) { + *oid = 1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + switch_api->create_switch = [](sai_object_id_t *oid, uint32_t, + const sai_attribute_t *) { + *oid = 2; + return (sai_status_t)SAI_STATUS_SUCCESS; + }; + + sai_object_id_t oid; + + acl_api->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, 1); + + switch_api->create_switch(&oid, 0, nullptr); + ASSERT_EQ(oid, 2); + + sai_object_id_t exp_oid_1 = 100; + sai_object_id_t exp_oid_2 = 200; + + auto x = SpyOn(&acl_api.get()->create_acl_table); + x->callFake([&](sai_object_id_t *oid, sai_object_id_t, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_1; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + auto y = SpyOn(&switch_api.get()->create_switch); + y->callFake([&](sai_object_id_t *oid, uint32_t, const sai_attribute_t *) -> sai_status_t { + *oid = exp_oid_2; + return (sai_status_t)SAI_STATUS_SUCCESS; + }); + + acl_api->create_acl_table(&oid, 1, 0, nullptr); + ASSERT_EQ(oid, exp_oid_1); + + switch_api->create_switch(&oid, 0, nullptr); + ASSERT_EQ(oid, exp_oid_2); +} diff --git a/tests/ut_helper.h b/tests/ut_helper.h new file mode 100644 index 000000000000..ba1a649d8f3c --- /dev/null +++ b/tests/ut_helper.h @@ -0,0 +1,11 @@ +#pragma once + +#define LIBVS 1 +#define LIBSAIREDIS 2 +#define WITH_SAI LIBVS + +#include "gtest/gtest.h" +#include "portal.h" +#include "saispy.h" + +#include "check.h"