diff --git a/portsyncd/portsyncd.cpp b/portsyncd/portsyncd.cpp index 8243f94f8b..c12e44e4f6 100644 --- a/portsyncd/portsyncd.cpp +++ b/portsyncd/portsyncd.cpp @@ -42,10 +42,7 @@ void usage() cout << " this program will exit if configDB does not contain that info" << endl; } -void handlePortConfigFile(ProducerStateTable &p, string file, bool warm); void handlePortConfigFromConfigDB(ProducerStateTable &p, DBConnector &cfgDb, bool warm); -void handleVlanIntfFile(string file); -void checkPortInitDone(DBConnector *appl_db); int main(int argc, char **argv) { diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 3546e6f934..6ee6faef46 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -2,13 +2,11 @@ FLEX_CTR_DIR = $(top_srcdir)/orchagent/flex_counter DEBUG_CTR_DIR = $(top_srcdir)/orchagent/debug_counter P4_ORCH_DIR = $(top_srcdir)/orchagent/p4orch -INCLUDES = -I $(FLEX_CTR_DIR) -I $(DEBUG_CTR_DIR) -I $(top_srcdir)/lib -I $(top_srcdir)/cfgmgr - CFLAGS_SAI = -I /usr/include/sai -TESTS = tests tests_intfmgrd +TESTS = tests tests_intfmgrd tests_portsyncd -noinst_PROGRAMS = tests tests_intfmgrd +noinst_PROGRAMS = tests tests_intfmgrd tests_portsyncd LDADD_SAI = -lsaimeta -lsaimetadata -lsaivs -lsairedis @@ -21,6 +19,10 @@ endif CFLAGS_GTEST = LDADD_GTEST = -L/usr/src/gtest +## Orchagent Unit Tests + +tests_INCLUDES = -I $(FLEX_CTR_DIR) -I $(DEBUG_CTR_DIR) -I $(top_srcdir)/lib -I$(top_srcdir)/cfgmgr -I$(top_srcdir)/orchagent + tests_SOURCES = aclorch_ut.cpp \ portsorch_ut.cpp \ routeorch_ut.cpp \ @@ -36,10 +38,10 @@ tests_SOURCES = aclorch_ut.cpp \ mock_orchagent_main.cpp \ mock_dbconnector.cpp \ mock_consumerstatetable.cpp \ + common/mock_shell_command.cpp \ mock_table.cpp \ mock_hiredis.cpp \ mock_redisreply.cpp \ - mock_shell_command.cpp \ bulker_ut.cpp \ portmgr_ut.cpp \ fake_response_publisher.cpp \ @@ -120,26 +122,45 @@ tests_SOURCES += $(P4_ORCH_DIR)/p4orch.cpp \ $(P4_ORCH_DIR)/gre_tunnel_manager.cpp \ $(P4_ORCH_DIR)/l3_admit_manager.cpp -tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I$(top_srcdir)/orchagent +tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) +tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) $(tests_INCLUDES) tests_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis -lpthread \ -lswsscommon -lswsscommon -lgtest -lgtest_main -lzmq -lnl-3 -lnl-route-3 +## portsyncd unit tests + +tests_portsyncd_SOURCES = portsyncd/portsyncd_ut.cpp \ + $(top_srcdir)/portsyncd/linksync.cpp \ + mock_dbconnector.cpp \ + common/mock_shell_command.cpp \ + mock_table.cpp \ + mock_hiredis.cpp \ + mock_redisreply.cpp + +tests_portsyncd_INCLUDES = -I $(top_srcdir)/portsyncd -I $(top_srcdir)/cfgmgr +tests_portsyncd_CXXFLAGS = -Wl,-wrap,if_nameindex -Wl,-wrap,if_freenameindex +tests_portsyncd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) +tests_portsyncd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(tests_portsyncd_INCLUDES) +tests_portsyncd_LDADD = $(LDADD_GTEST) -lnl-genl-3 -lhiredis -lhiredis \ + -lswsscommon -lswsscommon -lgtest -lgtest_main -lnl-3 -lnl-route-3 -lpthread + ## intfmgrd unit tests tests_intfmgrd_SOURCES = intfmgrd/add_ipv6_prefix_ut.cpp \ - $(top_srcdir)/cfgmgr/intfmgr.cpp \ - $(top_srcdir)/orchagent/orch.cpp \ - $(top_srcdir)/orchagent/request_parser.cpp \ - $(top_srcdir)/lib/subintf.cpp \ - mock_orchagent_main.cpp \ - mock_dbconnector.cpp \ - mock_table.cpp \ - mock_hiredis.cpp \ - fake_response_publisher.cpp \ - mock_redisreply.cpp + $(top_srcdir)/cfgmgr/intfmgr.cpp \ + $(top_srcdir)/lib/subintf.cpp \ + $(top_srcdir)/orchagent/orch.cpp \ + $(top_srcdir)/orchagent/request_parser.cpp \ + mock_orchagent_main.cpp \ + mock_dbconnector.cpp \ + mock_table.cpp \ + mock_hiredis.cpp \ + fake_response_publisher.cpp \ + mock_redisreply.cpp \ + common/mock_shell_command.cpp +tests_intfmgrd_INCLUDES = $(tests_INCLUDES) -I$(top_srcdir)/cfgmgr -I$(top_srcdir)/lib tests_intfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -tests_intfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I $(top_srcdir)/cfgmgr -I $(top_srcdir)/orchagent/ +tests_intfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) $(tests_intfmgrd_INCLUDES) tests_intfmgrd_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis \ -lswsscommon -lswsscommon -lgtest -lgtest_main -lzmq -lnl-3 -lnl-route-3 -lpthread diff --git a/tests/mock_tests/common/mock_shell_command.cpp b/tests/mock_tests/common/mock_shell_command.cpp new file mode 100644 index 0000000000..f56bf4ddd9 --- /dev/null +++ b/tests/mock_tests/common/mock_shell_command.cpp @@ -0,0 +1,25 @@ +#include +#include + +/* Override this pointer for custom behavior */ +int (*callback)(const std::string &cmd, std::string &stdout) = nullptr; + +int mockCmdReturn = 0; +std::string mockCmdStdcout = ""; +std::vector mockCallArgs; + +namespace swss { + int exec(const std::string &cmd, std::string &stdout) + { + if (callback != nullptr) + { + return callback(cmd, stdout); + } + else + { + mockCallArgs.push_back(cmd); + stdout = mockCmdStdcout; + return mockCmdReturn; + } + } +} diff --git a/tests/mock_tests/intfmgrd/add_ipv6_prefix_ut.cpp b/tests/mock_tests/intfmgrd/add_ipv6_prefix_ut.cpp index f07fa9ccbd..2ed1a1b6af 100644 --- a/tests/mock_tests/intfmgrd/add_ipv6_prefix_ut.cpp +++ b/tests/mock_tests/intfmgrd/add_ipv6_prefix_ut.cpp @@ -5,25 +5,17 @@ #include #include "../mock_table.h" #include "warm_restart.h" -#define private public +#define private public #include "intfmgr.h" #undef private -/* Override this pointer for custom behavior */ -int (*callback)(const std::string &cmd, std::string &stdout) = nullptr; -std::vector mockCallArgs; - -namespace swss { - int exec(const std::string &cmd, std::string &stdout) - { - mockCallArgs.push_back(cmd); - return callback(cmd, stdout); - } -} +extern int (*callback)(const std::string &cmd, std::string &stdout); +extern std::vector mockCallArgs; bool Ethernet0IPv6Set = false; int cb(const std::string &cmd, std::string &stdout){ + mockCallArgs.push_back(cmd); if (cmd == "sysctl -w net.ipv6.conf.\"Ethernet0\".disable_ipv6=0") Ethernet0IPv6Set = true; else if (cmd.find("/sbin/ip -6 address \"add\"") == 0) { return Ethernet0IPv6Set ? 0 : 2; diff --git a/tests/mock_tests/mock_table.cpp b/tests/mock_tests/mock_table.cpp index cd2ffbaa96..4d512a9835 100644 --- a/tests/mock_tests/mock_table.cpp +++ b/tests/mock_tests/mock_table.cpp @@ -73,8 +73,7 @@ namespace swss keys.push_back(it.first); } } - - + void Table::del(const std::string &key, const std::string& /* op */, const std::string& /*prefix*/) { auto table = gDB[m_pipe->getDbId()].find(getTableName()); @@ -82,7 +81,7 @@ namespace swss table->second.erase(key); } } - + void ProducerStateTable::set(const std::string &key, const std::vector &values, const std::string &op, diff --git a/tests/mock_tests/portsyncd/portsyncd_ut.cpp b/tests/mock_tests/portsyncd/portsyncd_ut.cpp new file mode 100644 index 0000000000..a7aaf0f9f8 --- /dev/null +++ b/tests/mock_tests/portsyncd/portsyncd_ut.cpp @@ -0,0 +1,342 @@ +#include "gtest/gtest.h" +#include +#include +#include "mock_table.h" +#define private public +#include "linksync.h" +#undef private + +struct if_nameindex *if_ni_mock = NULL; + +/* Mock if_nameindex() call */ +extern "C" { + struct if_nameindex *__wrap_if_nameindex() + { + return if_ni_mock; + } +} + +/* Mock if_freenameindex() call */ +extern "C" { + void __wrap_if_freenameindex(struct if_nameindex *ptr) + { + return ; + } +} + +extern std::string mockCmdStdcout; +extern std::vector mockCallArgs; +std::set g_portSet; +bool g_init = false; + +void writeToApplDB(swss::ProducerStateTable &p, swss::DBConnector &cfgDb) +{ + swss::Table table(&cfgDb, CFG_PORT_TABLE_NAME); + std::vector ovalues; + std::vector keys; + table.getKeys(keys); + + for ( auto &k : keys ) + { + table.get(k, ovalues); + std::vector attrs; + for ( auto &v : ovalues ) + { + swss::FieldValueTuple attr(v.first, v.second); + attrs.push_back(attr); + } + p.set(k, attrs); + g_portSet.insert(k); + } +} + +/* +Test Fixture +*/ +namespace portsyncd_ut +{ + struct PortSyncdTest : public ::testing::Test + { + std::shared_ptr m_config_db; + std::shared_ptr m_app_db; + std::shared_ptr m_state_db; + std::shared_ptr m_portCfgTable; + std::shared_ptr m_portAppTable; + + virtual void SetUp() override + { + testing_db::reset(); + m_config_db = std::make_shared("CONFIG_DB", 0); + m_app_db = std::make_shared("APPL_DB", 0); + m_state_db = std::make_shared("STATE_DB", 0); + m_portCfgTable = std::make_shared(m_config_db.get(), CFG_PORT_TABLE_NAME); + m_portAppTable = std::make_shared(m_app_db.get(), APP_PORT_TABLE_NAME); + } + + virtual void TearDown() override { + if (if_ni_mock != NULL) free(if_ni_mock); + if_ni_mock = NULL; + } + }; + + /* Helper Methods */ + void populateCfgDb(swss::Table* tbl){ + /* populate config db with Eth0 and Eth4 objects */ + std::vector vec; + vec.emplace_back("admin_status", "down"); + vec.emplace_back("index", "2"); + vec.emplace_back("lanes", "4,5,6,7"); + vec.emplace_back("mtu", "9100"); + vec.emplace_back("speed", "10000"); + vec.emplace_back("alias", "etp1"); + tbl->set("Ethernet0", vec); + vec.pop_back(); + vec.emplace_back("alias", "etp1"); + tbl->set("Ethernet4", vec); + } + + /* Create internal ds holding netdev ifaces for eth0 & lo */ + inline struct if_nameindex * populateNetDev(){ + struct if_nameindex *if_ni_temp; + /* Construct a mock if_nameindex array */ + if_ni_temp = (struct if_nameindex*) calloc(3, sizeof(struct if_nameindex)); + + if_ni_temp[2].if_index = 0; + if_ni_temp[2].if_name = NULL; + + if_ni_temp[1].if_index = 16222; + if_ni_temp[1].if_name = "eth0"; + + if_ni_temp[0].if_index = 1; + if_ni_temp[0].if_name = "lo"; + + return if_ni_temp; + } + + /* Create internal ds holding netdev ifaces for lo & Ethernet0 */ + inline struct if_nameindex * populateNetDevAdvanced(){ + struct if_nameindex *if_ni_temp; + /* Construct a mock if_nameindex array */ + if_ni_temp = (struct if_nameindex*) calloc(3, sizeof(struct if_nameindex)); + + if_ni_temp[2].if_index = 0; + if_ni_temp[2].if_name = NULL; + + if_ni_temp[1].if_index = 142; + if_ni_temp[1].if_name = "Ethernet0"; + + if_ni_temp[0].if_index = 1; + if_ni_temp[0].if_name = "lo"; + + return if_ni_temp; + } + + /* Draft a rtnl_link msg */ + struct nl_object* draft_nlmsg(const std::string& name, + std::vector flags, + const std::string& type, + const std::string& ll_add, + int ifindex, + unsigned int mtu, + int master_ifindex = 0){ + + struct rtnl_link* nl_obj = rtnl_link_alloc(); + if (!nl_obj){ + throw std::runtime_error("netlink: rtnl_link object allocation failed"); + } + /* Set name for rtnl link object */ + rtnl_link_set_name(nl_obj, name.c_str()); + + /* Set flags */ + for (auto nlflag : flags){ + rtnl_link_set_flags(nl_obj, nlflag); + } + + /* Set type */ + if (!type.empty()){ + rtnl_link_set_type(nl_obj, type.c_str()); + } + + /* Set Link layer Address */ + struct nl_addr * ll_addr; + int result = nl_addr_parse(ll_add.c_str(), AF_LLC, &ll_addr); + if (result < 0){ + throw std::runtime_error("netlink: Link layer address allocation failed"); + } + rtnl_link_set_addr(nl_obj, ll_addr); + + /* Set ifindex */ + rtnl_link_set_ifindex(nl_obj, ifindex); + + /* Set mtu */ + rtnl_link_set_mtu(nl_obj, mtu); + + /* Set master_ifindex if any */ + if (master_ifindex){ + rtnl_link_set_master(nl_obj, master_ifindex); + } + + return (struct nl_object*)nl_obj; + } + + inline void free_nlobj(struct nl_object* msg){ + nl_object_free(msg); + } +} + +namespace portsyncd_ut +{ + TEST_F(PortSyncdTest, test_linkSyncInit) + { + if_ni_mock = populateNetDev(); + mockCmdStdcout = "up\n"; + swss::LinkSync sync(m_app_db.get(), m_state_db.get()); + std::vector keys; + sync.m_stateMgmtPortTable.getKeys(keys); + ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.back(), "eth0"); + ASSERT_EQ(mockCallArgs.back(), "cat /sys/class/net/\"eth0\"/operstate"); + } + + TEST_F(PortSyncdTest, test_cacheOldIfaces) + { + if_ni_mock = populateNetDevAdvanced(); + swss::LinkSync sync(m_app_db.get(), m_state_db.get()); + ASSERT_EQ(mockCallArgs.back(), "ip link set \"Ethernet0\" down"); + ASSERT_NE(sync.m_ifindexOldNameMap.find(142), sync.m_ifindexOldNameMap.end()); + ASSERT_EQ(sync.m_ifindexOldNameMap[142], "Ethernet0"); + } + + TEST_F(PortSyncdTest, test_onMsgNewLink) + { + swss::LinkSync sync(m_app_db.get(), m_state_db.get()); + /* Write config to Config DB */ + populateCfgDb(m_portCfgTable.get()); + swss::DBConnector cfg_db_conn("CONFIG_DB", 0); + + /* Handle CFG DB notifs and Write them to APPL_DB */ + swss::ProducerStateTable p(m_app_db.get(), APP_PORT_TABLE_NAME); + writeToApplDB(p, cfg_db_conn); + + /* Generate a netlink notification about the netdev iface */ + std::vector flags = {IFF_UP, IFF_RUNNING}; + struct nl_object* msg = draft_nlmsg("Ethernet0", + flags, + "sx_netdev", + "1c:34:da:1c:9f:00", + 142, + 9100, + 0); + sync.onMsg(RTM_NEWLINK, msg); + + /* Verify if the update has been written to State DB */ + std::vector ovalues; + ASSERT_EQ(sync.m_statePortTable.get("Ethernet0", ovalues), true); + for (auto value : ovalues){ + if (fvField(value) == "state") {ASSERT_EQ(fvValue(value), "ok");} + if (fvField(value) == "mtu") {ASSERT_EQ(fvValue(value), "9100");} + if (fvField(value) == "netdev_oper_status") {ASSERT_EQ(fvValue(value), "up");} + if (fvField(value) == "admin_status") {ASSERT_EQ(fvValue(value), "up");} + if (fvField(value) == "speed") {ASSERT_EQ(fvValue(value), "10000");} + } + + /* Verify if the internal strctures are updated as expected */ + ASSERT_NE(sync.m_ifindexNameMap.find(142), sync.m_ifindexNameMap.end()); + ASSERT_EQ(sync.m_ifindexNameMap[142], "Ethernet0"); + + /* Free Nl_object */ + free_nlobj(msg); + } + + TEST_F(PortSyncdTest, test_onMsgDelLink){ + + swss::LinkSync sync(m_app_db.get(), m_state_db.get()); + + /* Write config to Config DB */ + populateCfgDb(m_portCfgTable.get()); + swss::DBConnector cfg_db_conn("CONFIG_DB", 0); + + /* Handle CFG DB notifs and Write them to APPL_DB */ + swss::ProducerStateTable p(m_app_db.get(), APP_PORT_TABLE_NAME); + writeToApplDB(p, cfg_db_conn);; + + /* Generate a netlink notification about the netdev iface */ + std::vector flags = {IFF_UP, IFF_RUNNING}; + struct nl_object* msg = draft_nlmsg("Ethernet0", + flags, + "sx_netdev", + "1c:34:da:1c:9f:00", + 142, + 9100, + 0); + sync.onMsg(RTM_NEWLINK, msg); + + /* Verify if the update has been written to State DB */ + std::vector ovalues; + ASSERT_EQ(sync.m_statePortTable.get("Ethernet0", ovalues), true); + + /* Free Nl_object */ + free_nlobj(msg); + + /* Generate a DELLINK Notif */ + msg = draft_nlmsg("Ethernet0", + flags, + "sx_netdev", + "1c:34:da:1c:9f:00", + 142, + 9100, + 0); + + sync.onMsg(RTM_DELLINK, msg); + ovalues.clear(); + + /* Verify if the state_db entry is cleared */ + ASSERT_EQ(sync.m_statePortTable.get("Ethernet0", ovalues), false); + } + + TEST_F(PortSyncdTest, test_onMsgMgmtIface){ + swss::LinkSync sync(m_app_db.get(), m_state_db.get()); + + /* Generate a netlink notification about the eth0 netdev iface */ + std::vector flags = {IFF_UP}; + struct nl_object* msg = draft_nlmsg("eth0", + flags, + "", + "00:50:56:28:0e:4a", + 16222, + 9100, + 0); + sync.onMsg(RTM_NEWLINK, msg); + + /* Verify if the update has been written to State DB */ + std::string oper_status; + ASSERT_EQ(sync.m_stateMgmtPortTable.hget("eth0", "oper_status", oper_status), true); + ASSERT_EQ(oper_status, "down"); + + /* Free Nl_object */ + free_nlobj(msg); + } + + TEST_F(PortSyncdTest, test_onMsgIgnoreOldNetDev){ + if_ni_mock = populateNetDevAdvanced(); + swss::LinkSync sync(m_app_db.get(), m_state_db.get()); + ASSERT_EQ(mockCallArgs.back(), "ip link set \"Ethernet0\" down"); + ASSERT_NE(sync.m_ifindexOldNameMap.find(142), sync.m_ifindexOldNameMap.end()); + ASSERT_EQ(sync.m_ifindexOldNameMap[142], "Ethernet0"); + + /* Generate a netlink notification about the netdev iface */ + std::vector flags; + struct nl_object* msg = draft_nlmsg("Ethernet0", + flags, + "sx_netdev", + "1c:34:da:1c:9f:00", + 142, + 9100, + 0); + sync.onMsg(RTM_NEWLINK, msg); + + /* Verify if nothing is written to state_db */ + std::vector ovalues; + ASSERT_EQ(sync.m_statePortTable.get("Ethernet0", ovalues), false); + } +}