Skip to content

Commit cecec8e

Browse files
meghtonystepanblyschak
authored andcommitted
Add mock test for IPv4/IPv6 route add/delete (#3839)
* Add mock test for IPv4 route add/delete What I did I added a single GoogleTest RouteOrch_AddRemoveIPv4_And_DefaultRoute_State that covers both adding/removing 2.2.2.0/24 and the default-route. I also added small helpers to probe STATE_DB for the route state field and to wait for ok/na when available. Why I did it This mirrors the pytest intent while fitting the mock unit test harness that pre-seeds ports, interfaces, neighbors, and a default route in SetUp(). It keeps the test fast and deterministic by verifying behavior via SAI mocks/counters rather than relying on DVS/ASIC_DB.
1 parent b27d9a7 commit cecec8e

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed

tests/mock_tests/routeorch_ut.cpp

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,38 @@ namespace routeorch_test
2626
using ::testing::Return;
2727
using ::testing::DoAll;
2828

29+
static bool stateRouteStateFieldExists(swss::DBConnector* state_db,
30+
const std::string& prefix)
31+
{
32+
Table stateRoute(state_db, "ROUTE_TABLE");
33+
std::vector<FieldValueTuple> fvs;
34+
stateRoute.get(prefix, fvs);
35+
for (const auto &fv : fvs)
36+
if (fvField(fv) == "state")
37+
return true;
38+
return false;
39+
}
40+
41+
static bool waitStateRouteState(swss::DBConnector* state_db,
42+
const std::string& prefix,
43+
const std::string& want,
44+
int attempts = 30)
45+
{
46+
Table stateRoute(state_db, "ROUTE_TABLE");
47+
for (int i = 0; i < attempts; ++i)
48+
{
49+
std::vector<FieldValueTuple> fvs;
50+
stateRoute.get(prefix, fvs);
51+
for (const auto &fv : fvs)
52+
if (fvField(fv) == "state" && fvValue(fv) == want)
53+
return true;
54+
55+
// Let orch process any pending work again
56+
static_cast<Orch *>(gRouteOrch)->doTask();
57+
}
58+
return false;
59+
}
60+
2961
DEFINE_SAI_API_MOCK_SPECIFY_ENTRY_WITH_SET(route, route);
3062

3163
shared_ptr<swss::DBConnector> m_app_db;
@@ -397,6 +429,209 @@ namespace routeorch_test
397429
}
398430
};
399431

432+
TEST_F(RouteOrchTest, RouteOrch_AddDeleteIPv6)
433+
{
434+
// Add IPv6 interface IPs (like the pytest does) and an IPv6 neighbor.
435+
{
436+
Table intfTable(m_app_db.get(), APP_INTF_TABLE_NAME);
437+
intfTable.set("Ethernet0:2000::1/64", { {"scope","global"}, {"family","IPv6"} });
438+
intfTable.set("Ethernet4:2001::1/64", { {"scope","global"}, {"family","IPv6"} });
439+
gIntfsOrch->addExistingData(&intfTable);
440+
static_cast<Orch *>(gIntfsOrch)->doTask();
441+
442+
Table neighborTable(m_app_db.get(), APP_NEIGH_TABLE_NAME);
443+
neighborTable.set("Ethernet0:2000::2", { {"neigh","00:00:00:00:00:22"}, {"family","IPv6"} });
444+
gNeighOrch->addExistingData(&neighborTable);
445+
static_cast<Orch *>(gNeighOrch)->doTask();
446+
}
447+
448+
auto *routeConsumer = dynamic_cast<Consumer *>(gRouteOrch->getExecutor(APP_ROUTE_TABLE_NAME));
449+
ASSERT_NE(routeConsumer, nullptr);
450+
451+
// PART A: Add/Remove IPv6 prefix 3000::/64 via 2000::2 on Ethernet0
452+
{
453+
std::deque<KeyOpFieldsValuesTuple> entries;
454+
entries.push_back({ "3000::/64", "SET",
455+
{ {"ifname","Ethernet0"}, {"nexthop","2000::2"} }});
456+
routeConsumer->addToSync(entries);
457+
458+
auto base_create = create_route_count;
459+
auto base_set = set_route_count;
460+
auto base_remove = remove_route_count;
461+
462+
static_cast<Orch *>(gRouteOrch)->doTask();
463+
464+
// Expect CREATE +1 for new route, no SET/REMOVE yet
465+
ASSERT_EQ(base_create + 1, create_route_count);
466+
ASSERT_EQ(base_set, set_route_count);
467+
ASSERT_EQ(base_remove, remove_route_count);
468+
469+
// Remove the route
470+
entries.clear();
471+
entries.push_back({ "3000::/64", "DEL", {} });
472+
routeConsumer->addToSync(entries);
473+
474+
base_create = create_route_count;
475+
base_set = set_route_count;
476+
base_remove = remove_route_count;
477+
478+
static_cast<Orch *>(gRouteOrch)->doTask();
479+
480+
// Expect REMOVE +1, create/set unchanged
481+
ASSERT_EQ(base_create, create_route_count);
482+
ASSERT_EQ(base_set, set_route_count);
483+
ASSERT_EQ(base_remove + 1, remove_route_count);
484+
}
485+
486+
// PART B: IPv6 default route (::/0): SET to add (state -> ok), DEL to remove (state -> na)
487+
{
488+
const std::string def6 = "::/0";
489+
const bool hasStateField = stateRouteStateFieldExists(m_state_db.get(), def6);
490+
491+
// Add default v6 route (::/0) via SET path
492+
std::deque<KeyOpFieldsValuesTuple> entries;
493+
entries.push_back({ def6, "SET", { {"ifname","Ethernet0"}, {"nexthop","2000::2"} }});
494+
routeConsumer->addToSync(entries);
495+
496+
auto base_create = create_route_count;
497+
auto base_set = set_route_count;
498+
auto base_remove = remove_route_count;
499+
500+
static_cast<Orch *>(gRouteOrch)->doTask();
501+
502+
// Default route typically programs via attribute SET (no create/remove)
503+
ASSERT_EQ(base_create, create_route_count);
504+
ASSERT_EQ(base_remove, remove_route_count);
505+
ASSERT_EQ(base_set + 1, set_route_count);
506+
ASSERT_EQ(sai_fail_count, 0);
507+
508+
if (hasStateField)
509+
{
510+
ASSERT_TRUE(waitStateRouteState(m_state_db.get(), def6, "ok"))
511+
<< "Expected IPv6 default-route state to become 'ok' after SET.";
512+
}
513+
514+
// Now delete the default v6 route
515+
entries.clear();
516+
entries.push_back({ def6, "DEL", {} });
517+
routeConsumer->addToSync(entries);
518+
519+
base_create = create_route_count;
520+
base_set = set_route_count;
521+
base_remove = remove_route_count;
522+
523+
static_cast<Orch *>(gRouteOrch)->doTask();
524+
525+
// Expect another SET (no create/remove), and no invalid SAI programming
526+
ASSERT_EQ(base_create, create_route_count);
527+
ASSERT_EQ(base_remove, remove_route_count);
528+
ASSERT_EQ(base_set + 1, set_route_count);
529+
ASSERT_EQ(sai_fail_count, 0);
530+
531+
if (hasStateField)
532+
{
533+
ASSERT_TRUE(waitStateRouteState(m_state_db.get(), def6, "na"))
534+
<< "Expected IPv6 default-route state to become 'na' after DEL.";
535+
}
536+
}
537+
}
538+
539+
TEST_F(RouteOrchTest, RouteOrch_AddDeleteIPv4)
540+
{
541+
auto *routeConsumer = dynamic_cast<Consumer *>(gRouteOrch->getExecutor(APP_ROUTE_TABLE_NAME));
542+
ASSERT_NE(routeConsumer, nullptr);
543+
544+
// PART A: Regular prefix add/remove (2.2.2.0/24)
545+
{
546+
std::deque<KeyOpFieldsValuesTuple> entries;
547+
entries.push_back({ "2.2.2.0/24", "SET",
548+
{ {"ifname","Ethernet0"}, {"nexthop","10.0.0.2"} }});
549+
routeConsumer->addToSync(entries);
550+
551+
auto base_create = create_route_count;
552+
auto base_set = set_route_count;
553+
auto base_remove = remove_route_count;
554+
555+
static_cast<Orch *>(gRouteOrch)->doTask();
556+
557+
// Expect create +1, set unchanged, remove unchanged
558+
ASSERT_EQ(base_create + 1, create_route_count);
559+
ASSERT_EQ(base_set, set_route_count);
560+
ASSERT_EQ(base_remove, remove_route_count);
561+
562+
// Now remove the route
563+
entries.clear();
564+
entries.push_back({ "2.2.2.0/24", "DEL", {} });
565+
routeConsumer->addToSync(entries);
566+
567+
base_create = create_route_count;
568+
base_set = set_route_count;
569+
base_remove = remove_route_count;
570+
571+
static_cast<Orch *>(gRouteOrch)->doTask();
572+
573+
// Expect remove +1, create/set unchanged
574+
ASSERT_EQ(base_create, create_route_count);
575+
ASSERT_EQ(base_set, set_route_count);
576+
ASSERT_EQ(base_remove + 1, remove_route_count);
577+
}
578+
579+
// PART B: Default route DEL -> state 'na' -> SET -> state 'ok'
580+
{
581+
const std::string def = "0.0.0.0/0";
582+
ASSERT_TRUE(stateRouteStateFieldExists(m_state_db.get(), def))
583+
<< "Expected STATE_DB:ROUTE_TABLE to expose 'state' for the default route.";
584+
585+
// SetUp() seeds a default route; if state is exposed, it should become 'ok'
586+
587+
ASSERT_TRUE(waitStateRouteState(m_state_db.get(), def, "ok"))
588+
<< "Expected initial default-route state to become 'ok'.";
589+
590+
591+
// DEL default route
592+
std::deque<KeyOpFieldsValuesTuple> entries;
593+
entries.push_back({ def, "DEL", {} });
594+
routeConsumer->addToSync(entries);
595+
596+
auto base_create = create_route_count;
597+
auto base_set = set_route_count;
598+
auto base_remove = remove_route_count;
599+
600+
static_cast<Orch *>(gRouteOrch)->doTask();
601+
602+
// For default route, expect attribute SET path (no create/remove), set +1
603+
ASSERT_EQ(base_create, create_route_count);
604+
ASSERT_EQ(base_remove, remove_route_count);
605+
ASSERT_EQ(base_set + 1, set_route_count);
606+
ASSERT_EQ(sai_fail_count, 0);
607+
608+
ASSERT_TRUE(waitStateRouteState(m_state_db.get(), def, "na"))
609+
<< "Expected default-route state to become 'na' after DEL.";
610+
611+
612+
// Re-SET default route
613+
entries.clear();
614+
entries.push_back({ def, "SET", { {"ifname","Ethernet0"}, {"nexthop","10.0.0.2"} }});
615+
routeConsumer->addToSync(entries);
616+
617+
base_create = create_route_count;
618+
base_set = set_route_count;
619+
base_remove = remove_route_count;
620+
621+
static_cast<Orch *>(gRouteOrch)->doTask();
622+
623+
// Expect another SET (no create/remove)
624+
ASSERT_EQ(base_create, create_route_count);
625+
ASSERT_EQ(base_remove, remove_route_count);
626+
ASSERT_EQ(base_set + 1, set_route_count);
627+
ASSERT_EQ(sai_fail_count, 0);
628+
629+
ASSERT_TRUE(waitStateRouteState(m_state_db.get(), def, "ok"))
630+
<< "Expected default-route state to return to 'ok' after re-SET.";
631+
632+
}
633+
}
634+
400635
TEST_F(RouteOrchTest, RouteOrchTestDelSetSameNexthop)
401636
{
402637
std::deque<KeyOpFieldsValuesTuple> entries;

tests/test_route.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ def clear_srv_config(self, dvs):
134134

135135
class TestRoute(TestRouteBase):
136136
""" Functionality tests for route """
137+
@pytest.mark.skip(reason="Covered by mock test: RouteOrch_AddRemoveIPv4_And_DefaultRoute_State (GTest)")
137138
def test_RouteAddRemoveIpv4Route(self, dvs, testlog):
138139
self.setup_db(dvs)
139140

@@ -213,6 +214,7 @@ def test_RouteAddRemoveIpv4Route(self, dvs, testlog):
213214
dvs.servers[1].runcmd("ip route del default dev eth0")
214215
dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0")
215216

217+
@pytest.mark.skip(reason="Covered by mock test: RouteOrch_AddRemoveIPv6_And_DefaultRoute_State (GTest)")
216218
def test_RouteAddRemoveIpv6Route(self, dvs, testlog):
217219
self.setup_db(dvs)
218220

0 commit comments

Comments
 (0)