forked from envoyproxy/envoy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxds_verifier.cc
329 lines (296 loc) · 11.7 KB
/
xds_verifier.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#include "test/server/config_validation/xds_verifier.h"
#include "common/common/logger.h"
namespace Envoy {
XdsVerifier::XdsVerifier(test::server::config_validation::Config::SotwOrDelta sotw_or_delta)
: num_warming_(0), num_active_(0), num_draining_(0), num_added_(0), num_modified_(0),
num_removed_(0) {
if (sotw_or_delta == test::server::config_validation::Config::SOTW) {
sotw_or_delta_ = SOTW;
} else {
sotw_or_delta_ = DELTA;
}
ENVOY_LOG_MISC(debug, "sotw_or_delta_ = {}", sotw_or_delta_);
}
/**
* get the route referenced by a listener
*/
std::string XdsVerifier::getRoute(const envoy::config::listener::v3::Listener& listener) {
envoy::config::listener::v3::Filter filter0 = listener.filter_chains()[0].filters()[0];
envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager conn_man;
filter0.typed_config().UnpackTo(&conn_man);
return conn_man.rds().route_config_name();
}
/**
* @return true iff the route listener refers to is in all_routes_
*/
bool XdsVerifier::hasRoute(const envoy::config::listener::v3::Listener& listener) {
return hasRoute(getRoute(listener));
}
bool XdsVerifier::hasRoute(const std::string& name) { return all_routes_.contains(name); }
bool XdsVerifier::hasActiveRoute(const envoy::config::listener::v3::Listener& listener) {
return hasActiveRoute(getRoute(listener));
}
bool XdsVerifier::hasActiveRoute(const std::string& name) { return active_routes_.contains(name); }
bool XdsVerifier::hasListener(const std::string& name, ListenerState state) {
return std::any_of(listeners_.begin(), listeners_.end(), [&](const auto& rep) {
return rep.listener.name() == name && state == rep.state;
});
}
/**
* prints the currently stored listeners and their states
*/
void XdsVerifier::dumpState() {
ENVOY_LOG_MISC(debug, "Listener Dump:");
for (const auto& rep : listeners_) {
ENVOY_LOG_MISC(debug, "Name: {}, Route {}, State: {}", rep.listener.name(),
getRoute(rep.listener), rep.state);
}
}
/*
* if a listener is added for the first time, it will be added as active/warming depending on if
* envoy knows about its route config
*
* if a listener is updated (i.e. there is a already a listener by this name), there are 3 cases:
* 1. the old listener is active and the new is warming:
* - old will remain active
* - new will be added as warming, to replace the old when it gets its route
* 2. the old listener is active and new is active:
* - old is drained (seemingly instantaneously)
* - new is added as active
* 3. the old listener is warming and new is active/warming:
* - old is completely removed
* - new is added as warming/active as normal
*/
/**
* update a listener when its route is changed, draining/removing the old listener and adding the
* updated listener
*/
void XdsVerifier::listenerUpdated(const envoy::config::listener::v3::Listener& listener) {
ENVOY_LOG_MISC(debug, "About to update listener {} to {}", listener.name(), getRoute(listener));
dumpState();
if (std::any_of(listeners_.begin(), listeners_.end(), [&](auto& rep) {
return rep.listener.name() == listener.name() &&
getRoute(listener) == getRoute(rep.listener) && rep.state != DRAINING;
})) {
ENVOY_LOG_MISC(debug, "Ignoring duplicate add of {}", listener.name());
return;
}
bool found = false;
for (auto it = listeners_.begin(); it != listeners_.end();) {
const auto& rep = *it;
ENVOY_LOG_MISC(debug, "checking {} for update", rep.listener.name());
if (rep.listener.name() == listener.name()) {
// if we're updating a warming/active listener, num_modified_ must be incremented
if (rep.state != DRAINING && !found) {
num_modified_++;
found = true;
}
if (rep.state == ACTIVE) {
if (hasActiveRoute(listener)) {
// if the new listener is ready to take traffic, the old listener will be removed
// it seems to be directly removed without being added to the config dump as draining
ENVOY_LOG_MISC(debug, "Removing {} after update", listener.name());
num_active_--;
it = listeners_.erase(it);
continue;
} else {
// if the new listener has not gotten its route yet, the old listener will remain active
// until that happens
ENVOY_LOG_MISC(debug, "Keeping {} as ACTIVE", listener.name());
}
} else if (rep.state == WARMING) {
// if the old listener is warming, it will be removed and replaced with the new
ENVOY_LOG_MISC(debug, "Removed warming listener {}", listener.name());
num_warming_--;
it = listeners_.erase(it);
// don't increment it
continue;
}
}
++it;
}
dumpState();
listenerAdded(listener, true);
}
/**
* add a new listener to listeners_ in either an active or warming state
* @param listener the listener to be added
* @param from_update whether this function was called from listenerUpdated, in which case
* num_added_ should not be incremented
*/
void XdsVerifier::listenerAdded(const envoy::config::listener::v3::Listener& listener,
bool from_update) {
if (!from_update) {
num_added_++;
}
if (hasActiveRoute(listener)) {
ENVOY_LOG_MISC(debug, "Adding {} to listeners_ as ACTIVE", listener.name());
listeners_.push_back({listener, ACTIVE});
num_active_++;
} else {
num_warming_++;
ENVOY_LOG_MISC(debug, "Adding {} to listeners_ as WARMING", listener.name());
listeners_.push_back({listener, WARMING});
}
ENVOY_LOG_MISC(debug, "listenerAdded({})", listener.name());
dumpState();
}
/**
* remove a listener and drain it if it was active
* @param name the name of the listener to be removed
*/
void XdsVerifier::listenerRemoved(const std::string& name) {
bool found = false;
for (auto it = listeners_.begin(); it != listeners_.end();) {
auto& rep = *it;
if (rep.listener.name() == name) {
if (rep.state == ACTIVE) {
// the listener will be drained before being removed
ENVOY_LOG_MISC(debug, "Changing {} to DRAINING", name);
found = true;
num_active_--;
num_draining_++;
rep.state = DRAINING;
} else if (rep.state == WARMING) {
// the listener will be removed immediately
ENVOY_LOG_MISC(debug, "Removed warming listener {}", name);
found = true;
num_warming_--;
it = listeners_.erase(it);
// don't increment it
continue;
}
}
++it;
}
if (found) {
num_removed_++;
}
}
/**
* after a SOTW update, see if any listeners that are currently warming can become active
*/
void XdsVerifier::updateSotwListeners() {
ASSERT(sotw_or_delta_ == SOTW);
for (auto& rep : listeners_) {
// check all_routes_, not active_routes_ since this is SOTW, so any inactive routes will become
// active if this listener refers to them
if (hasRoute(rep.listener) && rep.state == WARMING) {
// it should successfully warm now
ENVOY_LOG_MISC(debug, "Moving {} to ACTIVE state", rep.listener.name());
// if the route was not originally added as active, change it now
if (!hasActiveRoute(rep.listener)) {
std::string route_name = getRoute(rep.listener);
auto it = all_routes_.find(route_name);
// all added routes should be in all_routes_ in SOTW
ASSERT(it != all_routes_.end());
active_routes_.insert({route_name, it->second});
}
// if there were any active listeners that were waiting to be updated, they will now be
// removed and the warming listener will take their place
markForRemoval(rep);
num_warming_--;
num_active_++;
rep.state = ACTIVE;
}
}
listeners_.erase(std::remove_if(listeners_.begin(), listeners_.end(),
[&](auto& listener) { return listener.state == REMOVED; }),
listeners_.end());
}
/**
* after a delta update, update any listeners that refer to the added route
*/
void XdsVerifier::updateDeltaListeners(const envoy::config::route::v3::RouteConfiguration& route) {
for (auto& rep : listeners_) {
if (getRoute(rep.listener) == route.name() && rep.state == WARMING) {
// it should successfully warm now
ENVOY_LOG_MISC(debug, "Moving {} to ACTIVE state", rep.listener.name());
// if there were any active listeners that were waiting to be updated, they will now be
// removed and the warming listener will take their place
markForRemoval(rep);
num_warming_--;
num_active_++;
rep.state = ACTIVE;
}
}
// erase any active listeners that were replaced
listeners_.erase(std::remove_if(listeners_.begin(), listeners_.end(),
[&](auto& listener) { return listener.state == REMOVED; }),
listeners_.end());
}
/**
* @param listener a warming listener that has a corresponding active listener of the same name
* called after listener receives its route, so it will be moved to active and the old listener will
* be removed
*/
void XdsVerifier::markForRemoval(ListenerRepresentation& rep) {
ASSERT(rep.state == WARMING);
// find the old listener and mark it for removal
for (auto& old_rep : listeners_) {
if (old_rep.listener.name() == rep.listener.name() &&
getRoute(old_rep.listener) != getRoute(rep.listener) && old_rep.state == ACTIVE) {
// mark it as removed to remove it after the loop so as not to invalidate the iterator in
// the caller function
old_rep.state = REMOVED;
num_active_--;
}
}
}
/**
* called when a route that was previously added is re-added
* the original route might have been ignored if no resources refer to it, so we can add it here
*/
void XdsVerifier::routeUpdated(const envoy::config::route::v3::RouteConfiguration& route) {
if (!all_routes_.contains(route.name()) &&
std::any_of(listeners_.begin(), listeners_.end(),
[&](auto& rep) { return getRoute(rep.listener) == route.name(); })) {
all_routes_.insert({route.name(), route});
active_routes_.insert({route.name(), route});
}
ENVOY_LOG_MISC(debug, "Updating {}", route.name());
if (sotw_or_delta_ == DELTA) {
updateDeltaListeners(route);
} else {
updateSotwListeners();
}
}
/**
* add a new route and update any listeners that refer to this route
*/
void XdsVerifier::routeAdded(const envoy::config::route::v3::RouteConfiguration& route) {
// routes that are not referenced by any resource are ignored, so this creates a distinction
// between SOTW and delta
// if an unreferenced route is sent in delta, it is ignored forever as it will not be sent in
// future RDS updates, whereas in SOTW it will be present in all future RDS updates, so if a
// listener that refers to it is added in the meantime, it will become active
if (!hasRoute(route.name())) {
all_routes_.insert({route.name(), route});
}
if (sotw_or_delta_ == DELTA && std::any_of(listeners_.begin(), listeners_.end(), [&](auto& rep) {
return getRoute(rep.listener) == route.name();
})) {
if (!hasActiveRoute(route.name())) {
active_routes_.insert({route.name(), route});
updateDeltaListeners(route);
}
updateDeltaListeners(route);
} else if (sotw_or_delta_ == SOTW) {
updateSotwListeners();
}
}
/**
* called after draining a listener, will remove it from listeners_
*/
void XdsVerifier::drainedListener(const std::string& name) {
for (auto it = listeners_.begin(); it != listeners_.end(); ++it) {
if (it->listener.name() == name && it->state == DRAINING) {
ENVOY_LOG_MISC(debug, "Drained and removed {}", name);
num_draining_--;
listeners_.erase(it);
return;
}
}
throw EnvoyException(fmt::format("Tried to drain {} which is not draining", name));
}
} // namespace Envoy