Skip to content

Commit 2e939b6

Browse files
authored
fuzz: add unit tests to xDS verifier (envoyproxy#12246)
Commit Message: Add unit tests to xDS verifier * add new file xds_verifier_test.cc with unit tests * add tests for main paths through verifier Signed-off-by: Sam Flattery <[email protected]>
1 parent 97d9829 commit 2e939b6

File tree

6 files changed

+312
-43
lines changed

6 files changed

+312
-43
lines changed

test/server/config_validation/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ envoy_cc_test_library(
129129
],
130130
)
131131

132+
envoy_cc_test(
133+
name = "xds_verifier_test",
134+
srcs = ["xds_verifier_test.cc"],
135+
deps = [
136+
":xds_verifier_lib",
137+
"//test/config:utility_lib",
138+
],
139+
)
140+
132141
envoy_cc_test_library(
133142
name = "xds_fuzz_lib",
134143
srcs = ["xds_fuzz.cc"],

test/server/config_validation/xds_fuzz.cc

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ XdsFuzzTest::XdsFuzzTest(const test::server::config_validation::XdsTestCase& inp
6969
create_xds_upstream_ = true;
7070
tls_xds_upstream_ = false;
7171

72+
// avoid listeners draining during the test
73+
drain_time_ = std::chrono::seconds(60);
74+
7275
if (input.config().sotw_or_delta() == test::server::config_validation::Config::SOTW) {
7376
sotw_or_delta_ = Grpc::SotwOrDelta::Sotw;
7477
} else {
@@ -130,6 +133,7 @@ bool XdsFuzzTest::hasRoute(const std::string& route_name) {
130133
*/
131134
void XdsFuzzTest::addListener(const std::string& listener_name, const std::string& route_name) {
132135
ENVOY_LOG_MISC(debug, "Adding {} with reference to {}", listener_name, route_name);
136+
lds_update_success_++;
133137
bool removed = eraseListener(listener_name);
134138
auto listener = buildListener(listener_name, route_name);
135139
listeners_.push_back(listener);
@@ -154,6 +158,7 @@ void XdsFuzzTest::removeListener(const std::string& listener_name) {
154158
bool removed = eraseListener(listener_name);
155159

156160
if (removed) {
161+
lds_update_success_++;
157162
updateListener(listeners_, {}, {listener_name});
158163
EXPECT_TRUE(waitForAck(Config::TypeUrl::get().Listener, std::to_string(version_)));
159164
verifier_.listenerRemoved(listener_name);
@@ -165,19 +170,15 @@ void XdsFuzzTest::removeListener(const std::string& listener_name) {
165170
*/
166171
void XdsFuzzTest::addRoute(const std::string& route_name) {
167172
ENVOY_LOG_MISC(debug, "Adding {}", route_name);
168-
bool has_route = hasRoute(route_name);
169173
auto route = buildRouteConfig(route_name);
170-
routes_.push_back(route);
171174

172-
if (has_route) {
173-
// if the route was already in routes_, don't send a duplicate add in delta request
174-
updateRoute(routes_, {}, {});
175-
verifier_.routeUpdated(route);
176-
} else {
177-
updateRoute(routes_, {route}, {});
178-
verifier_.routeAdded(route);
175+
if (!hasRoute(route_name)) {
176+
routes_.push_back(route);
179177
}
180178

179+
updateRoute(routes_, {route}, {});
180+
verifier_.routeAdded(route);
181+
181182
EXPECT_TRUE(waitForAck(Config::TypeUrl::get().RouteConfiguration, std::to_string(version_)));
182183
}
183184

@@ -277,6 +278,7 @@ void XdsFuzzTest::replay() {
277278
test_server_->waitForCounterEq("listener_manager.listener_modified", verifier_.numModified());
278279
test_server_->waitForCounterEq("listener_manager.listener_added", verifier_.numAdded());
279280
test_server_->waitForCounterEq("listener_manager.listener_removed", verifier_.numRemoved());
281+
test_server_->waitForCounterEq("listener_manager.lds.update_success", lds_update_success_);
280282
}
281283
ENVOY_LOG_MISC(debug, "warming {} ({}), active {} ({}), draining {} ({})",
282284
verifier_.numWarming(),

test/server/config_validation/xds_fuzz.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class XdsFuzzTest : public HttpIntegrationTest {
6363
std::vector<envoy::api::v2::RouteConfiguration> getRoutesConfigDump();
6464

6565
bool eraseListener(const std::string& listener_name);
66-
bool hasRoute(const std::string& route_num);
66+
bool hasRoute(const std::string& route_name);
6767
AssertionResult waitForAck(const std::string& expected_type_url,
6868
const std::string& expected_version);
6969

@@ -77,6 +77,8 @@ class XdsFuzzTest : public HttpIntegrationTest {
7777
envoy::config::core::v3::ApiVersion api_version_;
7878

7979
Network::Address::IpVersion ip_version_;
80+
81+
uint64_t lds_update_success_{0};
8082
};
8183

8284
} // namespace Envoy

test/server/config_validation/xds_verifier.cc

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,21 @@ std::string XdsVerifier::getRoute(const envoy::config::listener::v3::Listener& l
2929
* @return true iff the route listener refers to is in all_routes_
3030
*/
3131
bool XdsVerifier::hasRoute(const envoy::config::listener::v3::Listener& listener) {
32-
return all_routes_.contains(getRoute(listener));
32+
return hasRoute(getRoute(listener));
3333
}
3434

35+
bool XdsVerifier::hasRoute(const std::string& name) { return all_routes_.contains(name); }
36+
3537
bool XdsVerifier::hasActiveRoute(const envoy::config::listener::v3::Listener& listener) {
36-
return active_routes_.contains(getRoute(listener));
38+
return hasActiveRoute(getRoute(listener));
39+
}
40+
41+
bool XdsVerifier::hasActiveRoute(const std::string& name) { return active_routes_.contains(name); }
42+
43+
bool XdsVerifier::hasListener(const std::string& name, ListenerState state) {
44+
return std::any_of(listeners_.begin(), listeners_.end(), [&](const auto& rep) {
45+
return rep.listener.name() == name && state == rep.state;
46+
});
3747
}
3848

3949
/**
@@ -68,7 +78,7 @@ void XdsVerifier::dumpState() {
6878
* updated listener
6979
*/
7080
void XdsVerifier::listenerUpdated(const envoy::config::listener::v3::Listener& listener) {
71-
ENVOY_LOG_MISC(debug, "About to update listener {}", listener.name());
81+
ENVOY_LOG_MISC(debug, "About to update listener {} to {}", listener.name(), getRoute(listener));
7282
dumpState();
7383

7484
if (std::any_of(listeners_.begin(), listeners_.end(), [&](auto& rep) {
@@ -79,17 +89,25 @@ void XdsVerifier::listenerUpdated(const envoy::config::listener::v3::Listener& l
7989
return;
8090
}
8191

82-
for (unsigned long i = 0; i < listeners_.size(); ++i) {
83-
const auto& rep = listeners_[i];
92+
bool found = false;
93+
for (auto it = listeners_.begin(); it != listeners_.end();) {
94+
const auto& rep = *it;
95+
ENVOY_LOG_MISC(debug, "checking {} for update", rep.listener.name());
8496
if (rep.listener.name() == listener.name()) {
85-
if (rep.state == ACTIVE) {
97+
// if we're updating a warming/active listener, num_modified_ must be incremented
98+
if (rep.state != DRAINING && !found) {
8699
num_modified_++;
100+
found = true;
101+
}
102+
103+
if (rep.state == ACTIVE) {
87104
if (hasActiveRoute(listener)) {
88105
// if the new listener is ready to take traffic, the old listener will be removed
89106
// it seems to be directly removed without being added to the config dump as draining
90107
ENVOY_LOG_MISC(debug, "Removing {} after update", listener.name());
91108
num_active_--;
92-
listeners_.erase(listeners_.begin() + i);
109+
it = listeners_.erase(it);
110+
continue;
93111
} else {
94112
// if the new listener has not gotten its route yet, the old listener will remain active
95113
// until that happens
@@ -99,9 +117,12 @@ void XdsVerifier::listenerUpdated(const envoy::config::listener::v3::Listener& l
99117
// if the old listener is warming, it will be removed and replaced with the new
100118
ENVOY_LOG_MISC(debug, "Removed warming listener {}", listener.name());
101119
num_warming_--;
102-
listeners_.erase(listeners_.begin() + i);
120+
it = listeners_.erase(it);
121+
// don't increment it
122+
continue;
103123
}
104124
}
125+
++it;
105126
}
106127
dumpState();
107128
listenerAdded(listener, true);
@@ -139,25 +160,28 @@ void XdsVerifier::listenerAdded(const envoy::config::listener::v3::Listener& lis
139160
*/
140161
void XdsVerifier::listenerRemoved(const std::string& name) {
141162
bool found = false;
142-
for (unsigned long i = 0; i < listeners_.size(); ++i) {
143-
auto& rep = listeners_[i];
144-
if (rep.listener.name() != name) {
145-
continue;
146-
}
147163

148-
if (rep.state == ACTIVE) {
149-
// the listener will be drained before being removed
150-
ENVOY_LOG_MISC(debug, "Changing {} to DRAINING", name);
151-
num_removed_++;
152-
num_active_--;
153-
num_draining_++;
154-
rep.state = DRAINING;
155-
} else if (rep.state == WARMING) {
156-
// the listener will be removed immediately
157-
ENVOY_LOG_MISC(debug, "Removed warming listener {}", name);
158-
listeners_.erase(listeners_.begin() + i);
159-
num_warming_--;
164+
for (auto it = listeners_.begin(); it != listeners_.end();) {
165+
auto& rep = *it;
166+
if (rep.listener.name() == name) {
167+
if (rep.state == ACTIVE) {
168+
// the listener will be drained before being removed
169+
ENVOY_LOG_MISC(debug, "Changing {} to DRAINING", name);
170+
found = true;
171+
num_active_--;
172+
num_draining_++;
173+
rep.state = DRAINING;
174+
} else if (rep.state == WARMING) {
175+
// the listener will be removed immediately
176+
ENVOY_LOG_MISC(debug, "Removed warming listener {}", name);
177+
found = true;
178+
num_warming_--;
179+
it = listeners_.erase(it);
180+
// don't increment it
181+
continue;
182+
}
160183
}
184+
++it;
161185
}
162186

163187
if (found) {
@@ -236,7 +260,6 @@ void XdsVerifier::markForRemoval(ListenerRepresentation& rep) {
236260
// mark it as removed to remove it after the loop so as not to invalidate the iterator in
237261
// the caller function
238262
old_rep.state = REMOVED;
239-
/* num_modified_++; */
240263
num_active_--;
241264
}
242265
}
@@ -271,17 +294,19 @@ void XdsVerifier::routeAdded(const envoy::config::route::v3::RouteConfiguration&
271294
// if an unreferenced route is sent in delta, it is ignored forever as it will not be sent in
272295
// future RDS updates, whereas in SOTW it will be present in all future RDS updates, so if a
273296
// listener that refers to it is added in the meantime, it will become active
297+
if (!hasRoute(route.name())) {
298+
all_routes_.insert({route.name(), route});
299+
}
274300

275-
// in delta, active_routes_ and all_routes_ should be the same as we only send one route at a
276-
// time, so it either becomes active or not
277301
if (sotw_or_delta_ == DELTA && std::any_of(listeners_.begin(), listeners_.end(), [&](auto& rep) {
278302
return getRoute(rep.listener) == route.name();
279303
})) {
280-
active_routes_.insert({route.name(), route});
281-
all_routes_.insert({route.name(), route});
304+
if (!hasActiveRoute(route.name())) {
305+
active_routes_.insert({route.name(), route});
306+
updateDeltaListeners(route);
307+
}
282308
updateDeltaListeners(route);
283309
} else if (sotw_or_delta_ == SOTW) {
284-
all_routes_.insert({route.name(), route});
285310
updateSotwListeners();
286311
}
287312
}

test/server/config_validation/xds_verifier.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,16 @@ class XdsVerifier {
4949

5050
void dumpState();
5151

52+
bool hasListener(const std::string& name, ListenerState state);
53+
bool hasRoute(const envoy::config::listener::v3::Listener& listener);
54+
bool hasRoute(const std::string& name);
55+
bool hasActiveRoute(const envoy::config::listener::v3::Listener& listener);
56+
bool hasActiveRoute(const std::string& name);
57+
5258
private:
5359
enum SotwOrDelta { SOTW, DELTA };
5460

5561
std::string getRoute(const envoy::config::listener::v3::Listener& listener);
56-
bool hasRoute(const envoy::config::listener::v3::Listener& listener);
57-
bool hasActiveRoute(const envoy::config::listener::v3::Listener& listener);
5862
void updateSotwListeners();
5963
void updateDeltaListeners(const envoy::config::route::v3::RouteConfiguration& route);
6064
void markForRemoval(ListenerRepresentation& rep);

0 commit comments

Comments
 (0)