Skip to content

Commit 35385ad

Browse files
[RouteOrch] Record ROUTE_TABLE entry programming status to APPL_STATE_DB (sonic-net#2512)
* [RouteOrch] Record ROUTE_TABLE entry programming status to APPL_STATE_DB * Support route supression in BGP. Orchagent has to sends route programming feedback back to fpmsyncd which communicates with zebra.
1 parent 0704f78 commit 35385ad

File tree

5 files changed

+132
-7
lines changed

5 files changed

+132
-7
lines changed

orchagent/routeorch.cpp

+40-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ RouteOrch::RouteOrch(DBConnector *db, vector<table_name_with_pri_t> &tableNames,
4747
{
4848
SWSS_LOG_ENTER();
4949

50+
m_publisher.setBuffered(true);
51+
5052
sai_attribute_t attr;
5153
attr.id = SAI_SWITCH_ATTR_NUMBER_OF_ECMP_GROUPS;
5254

@@ -499,7 +501,7 @@ void RouteOrch::doTask(Consumer& consumer)
499501

500502
auto rc = toBulk.emplace(std::piecewise_construct,
501503
std::forward_as_tuple(key, op),
502-
std::forward_as_tuple());
504+
std::forward_as_tuple(key, (op == SET_COMMAND)));
503505

504506
bool inserted = rc.second;
505507
auto& ctx = rc.first->second;
@@ -630,6 +632,11 @@ void RouteOrch::doTask(Consumer& consumer)
630632

631633
if (fvField(i) == "seg_src")
632634
srv6_source = fvValue(i);
635+
636+
if (fvField(i) == "protocol")
637+
{
638+
ctx.protocol = fvValue(i);
639+
}
633640
}
634641

635642
/*
@@ -831,6 +838,10 @@ void RouteOrch::doTask(Consumer& consumer)
831838
/* fullmask subnet route is same as ip2me route */
832839
else if (ip_prefix.isFullMask() && m_intfsOrch->isPrefixSubnet(ip_prefix, alsv[0]))
833840
{
841+
/* The prefix is full mask (/32 or /128) and it is an interface subnet route, so IntfOrch has already
842+
* created an IP2ME route for it and we skip programming such route here as it already exists.
843+
* However, to keep APPL_DB and APPL_STATE_DB consistent we have to publish it. */
844+
publishRouteState(ctx);
834845
it = consumer.m_toSync.erase(it);
835846
}
836847
/* subnet route, vrf leaked route, etc */
@@ -860,7 +871,9 @@ void RouteOrch::doTask(Consumer& consumer)
860871
}
861872
else
862873
{
863-
/* Duplicate entry */
874+
/* Duplicate entry. Publish route state anyway since there could be multiple DEL, SET operations
875+
* consolidated by ConsumerStateTable leading to orchagent receiving only the last SET update. */
876+
publishRouteState(ctx);
864877
it = consumer.m_toSync.erase(it);
865878
}
866879

@@ -2226,6 +2239,9 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey
22262239

22272240
notifyNextHopChangeObservers(vrf_id, ipPrefix, nextHops, true);
22282241

2242+
/* Publish and update APPL STATE DB route entry programming status */
2243+
publishRouteState(ctx);
2244+
22292245
/*
22302246
* If the route uses a temporary synced NHG owned by NhgOrch, return false
22312247
* in order to keep trying to update the route in case the NHG is updated,
@@ -2419,6 +2435,9 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx)
24192435

24202436
SWSS_LOG_INFO("Remove route %s with next hop(s) %s",
24212437
ipPrefix.to_string().c_str(), it_route->second.nhg_key.to_string().c_str());
2438+
2439+
/* Publish removal status, removes route entry from APPL STATE DB */
2440+
publishRouteState(ctx);
24222441

24232442
if (ipPrefix.isDefaultRoute() && vrf_id == gVirtualRouterId)
24242443
{
@@ -2574,3 +2593,22 @@ void RouteOrch::decNhgRefCount(const std::string &nhg_index)
25742593
gCbfNhgOrch->decNhgRefCount(nhg_index);
25752594
}
25762595
}
2596+
2597+
void RouteOrch::publishRouteState(const RouteBulkContext& ctx, const ReturnCode& status)
2598+
{
2599+
SWSS_LOG_ENTER();
2600+
2601+
std::vector<FieldValueTuple> fvs;
2602+
2603+
/* Leave the fvs empty if the operation type is "DEL".
2604+
* An empty fvs makes ResponsePublisher::publish() remove the state entry from APPL_STATE_DB
2605+
*/
2606+
if (ctx.is_set)
2607+
{
2608+
fvs.emplace_back("protocol", ctx.protocol);
2609+
}
2610+
2611+
const bool replace = false;
2612+
2613+
m_publisher.publish(APP_ROUTE_TABLE_NAME, ctx.key, fvs, status, replace);
2614+
}

orchagent/routeorch.h

+10-2
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,12 @@ struct RouteBulkContext
122122
// using_temp_nhg will track if the NhgOrch's owned NHG is temporary or not
123123
bool using_temp_nhg;
124124

125-
RouteBulkContext()
126-
: excp_intfs_flag(false), using_temp_nhg(false)
125+
std::string key; // Key in database table
126+
std::string protocol; // Protocol string
127+
bool is_set; // True if set operation
128+
129+
RouteBulkContext(const std::string& key, bool is_set)
130+
: key(key), excp_intfs_flag(false), using_temp_nhg(false), is_set(is_set)
127131
{
128132
}
129133

@@ -139,6 +143,8 @@ struct RouteBulkContext
139143
excp_intfs_flag = false;
140144
vrf_id = SAI_NULL_OBJECT_ID;
141145
using_temp_nhg = false;
146+
key.clear();
147+
protocol.clear();
142148
}
143149
};
144150

@@ -269,6 +275,8 @@ class RouteOrch : public Orch, public Subject
269275
const NhgBase &getNhg(const std::string& nhg_index);
270276
void incNhgRefCount(const std::string& nhg_index);
271277
void decNhgRefCount(const std::string& nhg_index);
278+
279+
void publishRouteState(const RouteBulkContext& ctx, const ReturnCode& status = ReturnCode(SAI_STATUS_SUCCESS));
272280
};
273281

274282
#endif /* SWSS_ROUTEORCH_H */

tests/mock_tests/Makefile.am

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ tests_intfmgrd_INCLUDES = $(tests_INCLUDES) -I$(top_srcdir)/cfgmgr -I$(top_srcdi
171171
tests_intfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI)
172172
tests_intfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) $(tests_intfmgrd_INCLUDES)
173173
tests_intfmgrd_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis \
174-
-lswsscommon -lswsscommon -lgtest -lgtest_main -lzmq -lnl-3 -lnl-route-3 -lpthread
174+
-lswsscommon -lswsscommon -lgtest -lgtest_main -lzmq -lnl-3 -lnl-route-3 -lpthread -lgmock -lgmock_main
175175

176176
## fpmsyncd unit tests
177177

tests/mock_tests/fake_response_publisher.cpp

+19-2
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,36 @@
22
#include <vector>
33

44
#include "response_publisher.h"
5+
#include "mock_response_publisher.h"
6+
7+
/* This mock plugs into this fake response publisher implementation
8+
* when needed to test code that uses response publisher. */
9+
std::unique_ptr<MockResponsePublisher> gMockResponsePublisher;
510

611
ResponsePublisher::ResponsePublisher(bool buffered) : m_db(std::make_unique<swss::DBConnector>("APPL_STATE_DB", 0)), m_buffered(buffered) {}
712

813
void ResponsePublisher::publish(
914
const std::string& table, const std::string& key,
1015
const std::vector<swss::FieldValueTuple>& intent_attrs,
1116
const ReturnCode& status,
12-
const std::vector<swss::FieldValueTuple>& state_attrs, bool replace) {}
17+
const std::vector<swss::FieldValueTuple>& state_attrs, bool replace)
18+
{
19+
if (gMockResponsePublisher)
20+
{
21+
gMockResponsePublisher->publish(table, key, intent_attrs, status, state_attrs, replace);
22+
}
23+
}
1324

1425
void ResponsePublisher::publish(
1526
const std::string& table, const std::string& key,
1627
const std::vector<swss::FieldValueTuple>& intent_attrs,
17-
const ReturnCode& status, bool replace) {}
28+
const ReturnCode& status, bool replace)
29+
{
30+
if (gMockResponsePublisher)
31+
{
32+
gMockResponsePublisher->publish(table, key, intent_attrs, status, replace);
33+
}
34+
}
1835

1936
void ResponsePublisher::writeToDB(
2037
const std::string& table, const std::string& key,

tests/mock_tests/routeorch_ut.cpp

+62
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
#include "ut_helper.h"
88
#include "mock_orchagent_main.h"
99
#include "mock_table.h"
10+
#include "mock_response_publisher.h"
1011
#include "bulker.h"
1112

1213
extern string gMySwitchType;
1314

15+
extern std::unique_ptr<MockResponsePublisher> gMockResponsePublisher;
16+
17+
using ::testing::_;
1418

1519
namespace routeorch_test
1620
{
@@ -282,6 +286,10 @@ namespace routeorch_test
282286
{"mac_addr", "00:00:00:00:00:00" }});
283287
intfTable.set("Ethernet0:10.0.0.1/24", { { "scope", "global" },
284288
{ "family", "IPv4" }});
289+
intfTable.set("Ethernet4", { {"NULL", "NULL" },
290+
{"mac_addr", "00:00:00:00:00:00" }});
291+
intfTable.set("Ethernet4:11.0.0.1/32", { { "scope", "global" },
292+
{ "family", "IPv4" }});
285293
gIntfsOrch->addExistingData(&intfTable);
286294
static_cast<Orch *>(gIntfsOrch)->doTask();
287295

@@ -422,4 +430,58 @@ namespace routeorch_test
422430
ASSERT_EQ(current_set_count + 1, set_route_count);
423431
ASSERT_EQ(sai_fail_count, 0);
424432
}
433+
434+
TEST_F(RouteOrchTest, RouteOrchTestSetDelResponse)
435+
{
436+
gMockResponsePublisher = std::make_unique<MockResponsePublisher>();
437+
438+
std::deque<KeyOpFieldsValuesTuple> entries;
439+
std::string key = "2.2.2.0/24";
440+
std::vector<FieldValueTuple> fvs{{"ifname", "Ethernet0,Ethernet0"}, {"nexthop", "10.0.0.2,10.0.0.3"}, {"protocol", "bgp"}};
441+
entries.push_back({key, "SET", fvs});
442+
443+
auto consumer = dynamic_cast<Consumer *>(gRouteOrch->getExecutor(APP_ROUTE_TABLE_NAME));
444+
consumer->addToSync(entries);
445+
446+
EXPECT_CALL(*gMockResponsePublisher, publish(APP_ROUTE_TABLE_NAME, key, std::vector<FieldValueTuple>{{"protocol", "bgp"}}, ReturnCode(SAI_STATUS_SUCCESS), false)).Times(1);
447+
static_cast<Orch *>(gRouteOrch)->doTask();
448+
449+
// add entries again to the consumer queue (in case of rapid DEL/SET operations from fpmsyncd, routeorch just gets the last SET update)
450+
consumer->addToSync(entries);
451+
452+
EXPECT_CALL(*gMockResponsePublisher, publish(APP_ROUTE_TABLE_NAME, key, std::vector<FieldValueTuple>{{"protocol", "bgp"}}, ReturnCode(SAI_STATUS_SUCCESS), false)).Times(1);
453+
static_cast<Orch *>(gRouteOrch)->doTask();
454+
455+
entries.clear();
456+
457+
// Route deletion
458+
459+
entries.clear();
460+
entries.push_back({key, "DEL", {}});
461+
462+
consumer->addToSync(entries);
463+
464+
EXPECT_CALL(*gMockResponsePublisher, publish(APP_ROUTE_TABLE_NAME, key, std::vector<FieldValueTuple>{}, ReturnCode(SAI_STATUS_SUCCESS), false)).Times(1);
465+
static_cast<Orch *>(gRouteOrch)->doTask();
466+
467+
gMockResponsePublisher.reset();
468+
}
469+
470+
TEST_F(RouteOrchTest, RouteOrchSetFullMaskSubnetPrefix)
471+
{
472+
gMockResponsePublisher = std::make_unique<MockResponsePublisher>();
473+
474+
std::deque<KeyOpFieldsValuesTuple> entries;
475+
std::string key = "11.0.0.1/32";
476+
std::vector<FieldValueTuple> fvs{{"ifname", "Ethernet4"}, {"nexthop", "0.0.0.0"}, {"protocol", "bgp"}};
477+
entries.push_back({key, "SET", fvs});
478+
479+
auto consumer = dynamic_cast<Consumer *>(gRouteOrch->getExecutor(APP_ROUTE_TABLE_NAME));
480+
consumer->addToSync(entries);
481+
482+
EXPECT_CALL(*gMockResponsePublisher, publish(APP_ROUTE_TABLE_NAME, key, std::vector<FieldValueTuple>{{"protocol", "bgp"}}, ReturnCode(SAI_STATUS_SUCCESS), false)).Times(1);
483+
static_cast<Orch *>(gRouteOrch)->doTask();
484+
485+
gMockResponsePublisher.reset();
486+
}
425487
}

0 commit comments

Comments
 (0)