/proc/self/cwd/test/integration/base_integration_test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "test/integration/base_integration_test.h" |
2 | | |
3 | | #include <chrono> |
4 | | #include <cstdint> |
5 | | #include <functional> |
6 | | #include <memory> |
7 | | #include <string> |
8 | | #include <vector> |
9 | | |
10 | | #include "envoy/admin/v3/config_dump.pb.h" |
11 | | #include "envoy/buffer/buffer.h" |
12 | | #include "envoy/config/bootstrap/v3/bootstrap.pb.h" |
13 | | #include "envoy/config/endpoint/v3/endpoint_components.pb.h" |
14 | | #include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" |
15 | | #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" |
16 | | #include "envoy/service/discovery/v3/discovery.pb.h" |
17 | | |
18 | | #include "source/common/common/assert.h" |
19 | | #include "source/common/event/libevent.h" |
20 | | #include "source/common/network/utility.h" |
21 | | #include "source/extensions/transport_sockets/tls/context_config_impl.h" |
22 | | #include "source/extensions/transport_sockets/tls/ssl_socket.h" |
23 | | #include "source/server/proto_descriptors.h" |
24 | | |
25 | | #include "test/integration/utility.h" |
26 | | #include "test/test_common/environment.h" |
27 | | #include "test/test_common/network_utility.h" |
28 | | |
29 | | #include "gtest/gtest.h" |
30 | | |
31 | | namespace Envoy { |
32 | 2.64k | envoy::config::bootstrap::v3::Bootstrap configToBootstrap(const std::string& config) { |
33 | 2.64k | #ifdef ENVOY_ENABLE_YAML |
34 | 2.64k | envoy::config::bootstrap::v3::Bootstrap bootstrap; |
35 | 2.64k | TestUtility::loadFromYaml(config, bootstrap); |
36 | 2.64k | return bootstrap; |
37 | | #else |
38 | | UNREFERENCED_PARAMETER(config); |
39 | | PANIC("YAML support compiled out: can't parse YAML"); |
40 | | #endif |
41 | 2.64k | } |
42 | | |
43 | | using ::testing::_; |
44 | | using ::testing::AssertionFailure; |
45 | | using ::testing::AssertionResult; |
46 | | using ::testing::AssertionSuccess; |
47 | | using ::testing::Invoke; |
48 | | using ::testing::IsSubstring; |
49 | | using ::testing::NiceMock; |
50 | | using ::testing::ReturnRef; |
51 | | |
52 | | BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstream_address_fn, |
53 | | Network::Address::IpVersion version, |
54 | | const envoy::config::bootstrap::v3::Bootstrap& bootstrap) |
55 | | : api_(Api::createApiForTest(stats_store_, time_system_)), |
56 | | mock_buffer_factory_(new NiceMock<MockBufferFactory>), |
57 | | dispatcher_(api_->allocateDispatcher("test_thread", |
58 | | Buffer::WatermarkFactoryPtr{mock_buffer_factory_})), |
59 | | version_(version), upstream_address_fn_(upstream_address_fn), |
60 | | config_helper_(version, bootstrap), |
61 | 2.64k | default_log_level_(TestEnvironment::getOptions().logLevel()) { |
62 | 2.64k | Envoy::Server::validateProtoDescriptors(); |
63 | | // This is a hack, but there are situations where we disconnect fake upstream connections and |
64 | | // then we expect the server connection pool to get the disconnect before the next test starts. |
65 | | // This does not always happen. This pause should allow the server to pick up the disconnect |
66 | | // notification and clear the pool connection if necessary. A real fix would require adding fairly |
67 | | // complex test hooks to the server and/or spin waiting on stats, neither of which I think are |
68 | | // necessary right now. |
69 | 2.64k | timeSystem().realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(10)); |
70 | 2.64k | ON_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) |
71 | 2.64k | .WillByDefault(Invoke([](std::function<void()> below_low, std::function<void()> above_high, |
72 | 4.42k | std::function<void()> above_overflow) -> Buffer::Instance* { |
73 | 4.42k | return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); |
74 | 4.42k | })); |
75 | 2.64k | ON_CALL(factory_context_.server_context_, api()).WillByDefault(ReturnRef(*api_)); |
76 | 2.64k | ON_CALL(factory_context_, statsScope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); |
77 | | |
78 | | #ifndef ENVOY_ADMIN_FUNCTIONALITY |
79 | | config_helper_.addConfigModifier( |
80 | | [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { bootstrap.clear_admin(); }); |
81 | | #endif |
82 | 2.64k | } |
83 | | |
84 | | BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstream_address_fn, |
85 | | Network::Address::IpVersion version, |
86 | | const std::string& config) |
87 | 2.64k | : BaseIntegrationTest(upstream_address_fn, version, configToBootstrap(config)) {} |
88 | | |
89 | | const BaseIntegrationTest::InstanceConstSharedPtrFn |
90 | 0 | BaseIntegrationTest::defaultAddressFunction(Network::Address::IpVersion version) { |
91 | 0 | return [version](int) { |
92 | 0 | return Network::Utility::parseInternetAddress(Network::Test::getLoopbackAddressString(version), |
93 | 0 | 0); |
94 | 0 | }; |
95 | 0 | } |
96 | | |
97 | | BaseIntegrationTest::BaseIntegrationTest(Network::Address::IpVersion version, |
98 | | const std::string& config) |
99 | 0 | : BaseIntegrationTest(defaultAddressFunction(version), version, config) {} |
100 | | |
101 | 1.10k | Network::ClientConnectionPtr BaseIntegrationTest::makeClientConnection(uint32_t port) { |
102 | 1.10k | return makeClientConnectionWithOptions(port, nullptr); |
103 | 1.10k | } |
104 | | |
105 | | Network::ClientConnectionPtr BaseIntegrationTest::makeClientConnectionWithOptions( |
106 | 1.10k | uint32_t port, const Network::ConnectionSocket::OptionsSharedPtr& options) { |
107 | 1.10k | Network::ClientConnectionPtr connection(dispatcher_->createClientConnection( |
108 | 1.10k | Network::Utility::resolveUrl( |
109 | 1.10k | fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)), |
110 | 1.10k | Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), options, |
111 | 1.10k | nullptr)); |
112 | | |
113 | 1.10k | connection->enableHalfClose(enableHalfClose()); |
114 | 1.10k | return connection; |
115 | 1.10k | } |
116 | | |
117 | 2.64k | void BaseIntegrationTest::initialize() { |
118 | 2.64k | RELEASE_ASSERT(!initialized_, ""); |
119 | 2.64k | RELEASE_ASSERT(Event::Libevent::Global::initialized(), ""); |
120 | 2.64k | initialized_ = true; |
121 | | |
122 | 2.64k | createUpstreams(); |
123 | 2.64k | createXdsUpstream(); |
124 | 2.64k | createEnvoy(); |
125 | | |
126 | 2.64k | #ifdef ENVOY_ADMIN_FUNCTIONALITY |
127 | 2.64k | if (!skip_tag_extraction_rule_check_) { |
128 | 2.64k | checkForMissingTagExtractionRules(); |
129 | 2.64k | } |
130 | 2.64k | #endif |
131 | 2.64k | } |
132 | | |
133 | | Network::DownstreamTransportSocketFactoryPtr |
134 | 0 | BaseIntegrationTest::createUpstreamTlsContext(const FakeUpstreamConfig& upstream_config) { |
135 | 0 | envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; |
136 | 0 | const std::string rundir = TestEnvironment::runfilesDirectory(); |
137 | 0 | tls_context.mutable_common_tls_context() |
138 | 0 | ->mutable_validation_context() |
139 | 0 | ->mutable_trusted_ca() |
140 | 0 | ->set_filename(rundir + "/test/config/integration/certs/cacert.pem"); |
141 | 0 | auto* certs = tls_context.mutable_common_tls_context()->add_tls_certificates(); |
142 | 0 | certs->mutable_certificate_chain()->set_filename( |
143 | 0 | rundir + "/test/config/integration/certs/upstreamcert.pem"); |
144 | 0 | certs->mutable_private_key()->set_filename(rundir + |
145 | 0 | "/test/config/integration/certs/upstreamkey.pem"); |
146 | |
|
147 | 0 | if (upstream_config.upstream_protocol_ == Http::CodecType::HTTP2) { |
148 | 0 | tls_context.mutable_common_tls_context()->add_alpn_protocols("h2"); |
149 | 0 | } else if (upstream_config.upstream_protocol_ == Http::CodecType::HTTP1) { |
150 | 0 | tls_context.mutable_common_tls_context()->add_alpn_protocols("http/1.1"); |
151 | 0 | } |
152 | 0 | if (upstream_config.upstream_protocol_ != Http::CodecType::HTTP3) { |
153 | 0 | auto cfg = std::make_unique<Extensions::TransportSockets::Tls::ServerContextConfigImpl>( |
154 | 0 | tls_context, factory_context_); |
155 | 0 | static auto* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); |
156 | 0 | return std::make_unique<Extensions::TransportSockets::Tls::ServerSslSocketFactory>( |
157 | 0 | std::move(cfg), context_manager_, *upstream_stats_store->rootScope(), |
158 | 0 | std::vector<std::string>{}); |
159 | 0 | } else { |
160 | 0 | envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport quic_config; |
161 | 0 | quic_config.mutable_downstream_tls_context()->MergeFrom(tls_context); |
162 | |
|
163 | 0 | std::vector<std::string> server_names; |
164 | 0 | auto& config_factory = Config::Utility::getAndCheckFactoryByName< |
165 | 0 | Server::Configuration::DownstreamTransportSocketConfigFactory>( |
166 | 0 | "envoy.transport_sockets.quic"); |
167 | 0 | return config_factory.createTransportSocketFactory(quic_config, factory_context_, server_names); |
168 | 0 | } |
169 | 0 | } |
170 | | |
171 | 2.64k | void BaseIntegrationTest::createUpstreams() { |
172 | 5.29k | for (uint32_t i = 0; i < fake_upstreams_count_; ++i) { |
173 | 2.64k | auto endpoint = upstream_address_fn_(i); |
174 | 2.64k | createUpstream(endpoint, upstreamConfig()); |
175 | 2.64k | } |
176 | 2.64k | } |
177 | | void BaseIntegrationTest::createUpstream(Network::Address::InstanceConstSharedPtr endpoint, |
178 | 2.64k | FakeUpstreamConfig& config) { |
179 | 2.64k | Network::DownstreamTransportSocketFactoryPtr factory = |
180 | 2.64k | upstream_tls_ ? createUpstreamTlsContext(config) |
181 | 2.64k | : Network::Test::createRawBufferDownstreamSocketFactory(); |
182 | 2.64k | if (autonomous_upstream_) { |
183 | 591 | fake_upstreams_.emplace_back(new AutonomousUpstream(std::move(factory), endpoint, config, |
184 | 591 | autonomous_allow_incomplete_streams_)); |
185 | 2.05k | } else { |
186 | 2.05k | fake_upstreams_.emplace_back(new FakeUpstream(std::move(factory), endpoint, config)); |
187 | 2.05k | } |
188 | 2.64k | } |
189 | | |
190 | | std::string BaseIntegrationTest::finalizeConfigWithPorts(ConfigHelper& config_helper, |
191 | | std::vector<uint32_t>& ports, |
192 | 2.64k | bool use_lds) { |
193 | 2.64k | if (use_lds) { |
194 | 2.63k | ENVOY_LOG_MISC(debug, "Setting up file-based LDS"); |
195 | | // Before finalization, set up a real lds path, replacing the default /dev/null |
196 | 2.63k | std::string lds_path = TestEnvironment::temporaryPath(TestUtility::uniqueFilename()); |
197 | 2.63k | config_helper.addConfigModifier( |
198 | 2.63k | [lds_path](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { |
199 | 2.63k | bootstrap.mutable_dynamic_resources()->mutable_lds_config()->set_resource_api_version( |
200 | 2.63k | envoy::config::core::v3::V3); |
201 | 2.63k | bootstrap.mutable_dynamic_resources() |
202 | 2.63k | ->mutable_lds_config() |
203 | 2.63k | ->mutable_path_config_source() |
204 | 2.63k | ->set_path(lds_path); |
205 | 2.63k | }); |
206 | 2.63k | } |
207 | | |
208 | | // Note that finalize assumes that every fake_upstream_ must correspond to a bootstrap config |
209 | | // static entry. So, if you want to manually create a fake upstream without specifying it in the |
210 | | // config, you will need to do so *after* initialize() (which calls this function) is done. |
211 | 2.64k | config_helper.finalize(ports); |
212 | | |
213 | 2.64k | envoy::config::bootstrap::v3::Bootstrap bootstrap = config_helper.bootstrap(); |
214 | 2.64k | if (use_lds) { |
215 | | // After the config has been finalized, write the final listener config to the lds file. |
216 | 2.63k | const std::string lds_path = |
217 | 2.63k | config_helper.bootstrap().dynamic_resources().lds_config().path_config_source().path(); |
218 | 2.63k | envoy::service::discovery::v3::DiscoveryResponse lds; |
219 | 2.63k | lds.set_version_info("0"); |
220 | 2.63k | for (auto& listener : config_helper.bootstrap().static_resources().listeners()) { |
221 | 2.63k | ProtobufWkt::Any* resource = lds.add_resources(); |
222 | 2.63k | resource->PackFrom(listener); |
223 | 2.63k | } |
224 | 2.63k | #ifdef ENVOY_ENABLE_YAML |
225 | 2.63k | TestEnvironment::writeStringToFileForTest( |
226 | 2.63k | lds_path, MessageUtil::getJsonStringFromMessageOrError(lds), true); |
227 | | #else |
228 | | PANIC("YAML support compiled out: can't parse YAML"); |
229 | | #endif |
230 | | // Now that the listeners have been written to the lds file, remove them from static resources |
231 | | // or they will not be reloadable. |
232 | 2.63k | bootstrap.mutable_static_resources()->mutable_listeners()->Clear(); |
233 | 2.63k | } |
234 | 2.64k | #ifdef ENVOY_ENABLE_YAML |
235 | 2.64k | ENVOY_LOG_MISC(debug, "Running Envoy with configuration:\n{}", |
236 | 2.64k | MessageUtil::getYamlStringFromMessage(bootstrap)); |
237 | | #else |
238 | | ENVOY_LOG_MISC(debug, "Running Envoy with configuration:\n{}", bootstrap.DebugString()); |
239 | | #endif |
240 | | |
241 | 2.64k | const std::string bootstrap_path = TestEnvironment::writeStringToFileForTest( |
242 | 2.64k | "bootstrap.pb", TestUtility::getProtobufBinaryStringFromMessage(bootstrap)); |
243 | 2.64k | return bootstrap_path; |
244 | 2.64k | } |
245 | | |
246 | 2.64k | void BaseIntegrationTest::createEnvoy() { |
247 | 2.64k | std::vector<uint32_t> ports; |
248 | 2.66k | for (auto& upstream : fake_upstreams_) { |
249 | 2.66k | if (upstream->localAddress()->ip()) { |
250 | 2.66k | ports.push_back(upstream->localAddress()->ip()->port()); |
251 | 2.66k | } |
252 | 2.66k | } |
253 | | |
254 | 2.64k | const std::string bootstrap_path = finalizeConfigWithPorts(config_helper_, ports, use_lds_); |
255 | | |
256 | 2.64k | std::vector<std::string> named_ports; |
257 | 2.64k | const auto& static_resources = config_helper_.bootstrap().static_resources(); |
258 | 2.64k | named_ports.reserve(static_resources.listeners_size()); |
259 | 5.28k | for (int i = 0; i < static_resources.listeners_size(); ++i) { |
260 | 2.63k | named_ports.push_back(static_resources.listeners(i).name()); |
261 | 2.63k | } |
262 | 2.64k | createGeneratedApiTestServer(bootstrap_path, named_ports, {false, true, false}, false); |
263 | 2.64k | } |
264 | | |
265 | 1.62k | void BaseIntegrationTest::setUpstreamProtocol(Http::CodecType protocol) { |
266 | 1.62k | upstream_config_.upstream_protocol_ = protocol; |
267 | 1.62k | if (upstream_config_.upstream_protocol_ == Http::CodecType::HTTP2) { |
268 | 1.62k | config_helper_.addConfigModifier( |
269 | 1.62k | [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { |
270 | 1.62k | RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); |
271 | 1.62k | ConfigHelper::HttpProtocolOptions protocol_options; |
272 | 1.62k | protocol_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); |
273 | 1.62k | ConfigHelper::setProtocolOptions( |
274 | 1.62k | *bootstrap.mutable_static_resources()->mutable_clusters(0), protocol_options); |
275 | 1.62k | }); |
276 | 1.62k | } else if (upstream_config_.upstream_protocol_ == Http::CodecType::HTTP1) { |
277 | 0 | config_helper_.addConfigModifier( |
278 | 0 | [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { |
279 | 0 | RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); |
280 | 0 | ConfigHelper::HttpProtocolOptions protocol_options; |
281 | 0 | protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options(); |
282 | 0 | ConfigHelper::setProtocolOptions( |
283 | 0 | *bootstrap.mutable_static_resources()->mutable_clusters(0), protocol_options); |
284 | 0 | }); |
285 | 0 | } else { |
286 | 0 | RELEASE_ASSERT(protocol == Http::CodecType::HTTP3, ""); |
287 | 0 | setUdpFakeUpstream(FakeUpstreamConfig::UdpConfig()); |
288 | 0 | upstream_tls_ = true; |
289 | 0 | config_helper_.configureUpstreamTls(false, true); |
290 | 0 | config_helper_.addConfigModifier( |
291 | 0 | [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { |
292 | | // Docker doesn't allow writing to the v6 address returned by |
293 | | // Network::Utility::getLocalAddress. |
294 | 0 | if (version_ == Network::Address::IpVersion::v6) { |
295 | 0 | auto* bind_config_address = bootstrap.mutable_static_resources() |
296 | 0 | ->mutable_clusters(0) |
297 | 0 | ->mutable_upstream_bind_config() |
298 | 0 | ->mutable_source_address(); |
299 | 0 | bind_config_address->set_address("::1"); |
300 | 0 | bind_config_address->set_port_value(0); |
301 | 0 | } |
302 | |
|
303 | 0 | RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); |
304 | 0 | ConfigHelper::HttpProtocolOptions protocol_options; |
305 | 0 | protocol_options.mutable_explicit_http_config()->mutable_http3_protocol_options(); |
306 | 0 | ConfigHelper::setProtocolOptions( |
307 | 0 | *bootstrap.mutable_static_resources()->mutable_clusters(0), protocol_options); |
308 | 0 | }); |
309 | 0 | } |
310 | 1.62k | } |
311 | | |
312 | | absl::optional<uint64_t> BaseIntegrationTest::waitForNextRawUpstreamConnection( |
313 | | const std::vector<uint64_t>& upstream_indices, FakeRawConnectionPtr& fake_upstream_connection, |
314 | 0 | std::chrono::milliseconds connection_wait_timeout) { |
315 | 0 | AssertionResult result = AssertionFailure(); |
316 | 0 | int upstream_index = 0; |
317 | 0 | Event::TestTimeSystem::RealTimeBound bound(connection_wait_timeout); |
318 | | // Loop over the upstreams until the call times out or an upstream request is |
319 | | // received. |
320 | 0 | while (!result) { |
321 | 0 | upstream_index = upstream_index % upstream_indices.size(); |
322 | 0 | result = fake_upstreams_[upstream_indices[upstream_index]]->waitForRawConnection( |
323 | 0 | fake_upstream_connection, std::chrono::milliseconds(5)); |
324 | 0 | if (result) { |
325 | 0 | return upstream_index; |
326 | 0 | } else if (!bound.withinBound()) { |
327 | 0 | RELEASE_ASSERT(0, "Timed out waiting for new connection."); |
328 | 0 | break; |
329 | 0 | } |
330 | 0 | ++upstream_index; |
331 | 0 | } |
332 | 0 | RELEASE_ASSERT(result, result.message()); |
333 | 0 | return {}; |
334 | 0 | } |
335 | | |
336 | | IntegrationTcpClientPtr |
337 | | BaseIntegrationTest::makeTcpConnection(uint32_t port, |
338 | | const Network::ConnectionSocket::OptionsSharedPtr& options, |
339 | | Network::Address::InstanceConstSharedPtr source_address, |
340 | 2.52k | absl::string_view destination_address) { |
341 | 2.52k | return std::make_unique<IntegrationTcpClient>(*dispatcher_, *mock_buffer_factory_, port, version_, |
342 | 2.52k | enableHalfClose(), options, source_address, |
343 | 2.52k | destination_address); |
344 | 2.52k | } |
345 | | |
346 | 5.26k | void BaseIntegrationTest::registerPort(const std::string& key, uint32_t port) { |
347 | 5.26k | port_map_[key] = port; |
348 | 5.26k | } |
349 | | |
350 | 3.62k | uint32_t BaseIntegrationTest::lookupPort(const std::string& key) { |
351 | 3.62k | auto it = port_map_.find(key); |
352 | 3.62k | if (it != port_map_.end()) { |
353 | 3.62k | return it->second; |
354 | 3.62k | } |
355 | 0 | RELEASE_ASSERT( |
356 | 0 | false, |
357 | 0 | fmt::format("lookupPort() called on service type '{}', which has not been added to port_map_", |
358 | 0 | key)); |
359 | 0 | } |
360 | | |
361 | | void BaseIntegrationTest::setUpstreamAddress( |
362 | 0 | uint32_t upstream_index, envoy::config::endpoint::v3::LbEndpoint& endpoint) const { |
363 | 0 | auto* socket_address = endpoint.mutable_endpoint()->mutable_address()->mutable_socket_address(); |
364 | 0 | socket_address->set_address(Network::Test::getLoopbackAddressString(version_)); |
365 | 0 | socket_address->set_port_value(fake_upstreams_[upstream_index]->localAddress()->ip()->port()); |
366 | 0 | } |
367 | | |
368 | | bool BaseIntegrationTest::getSocketOption(const std::string& listener_name, int level, int optname, |
369 | 0 | void* optval, socklen_t* optlen, int address_index) { |
370 | 0 | bool listeners_ready = false; |
371 | 0 | absl::Mutex l; |
372 | 0 | std::vector<std::reference_wrapper<Network::ListenerConfig>> listeners; |
373 | 0 | test_server_->server().dispatcher().post([&]() { |
374 | 0 | listeners = test_server_->server().listenerManager().listeners(); |
375 | 0 | l.Lock(); |
376 | 0 | listeners_ready = true; |
377 | 0 | l.Unlock(); |
378 | 0 | }); |
379 | 0 | l.LockWhen(absl::Condition(&listeners_ready)); |
380 | 0 | l.Unlock(); |
381 | |
|
382 | 0 | for (auto& listener : listeners) { |
383 | 0 | if (listener.get().name() == listener_name) { |
384 | 0 | auto& socket_factory = listener.get().listenSocketFactories()[address_index]; |
385 | 0 | auto socket = socket_factory->getListenSocket(0); |
386 | 0 | if (socket->getSocketOption(level, optname, optval, optlen).return_value_ != 0) { |
387 | 0 | return false; |
388 | 0 | } |
389 | 0 | return true; |
390 | 0 | } |
391 | 0 | } |
392 | 0 | return false; |
393 | 0 | } |
394 | | |
395 | | void BaseIntegrationTest::registerTestServerPorts(const std::vector<std::string>& port_names, |
396 | 2.63k | IntegrationTestServerPtr& test_server) { |
397 | 2.63k | bool listeners_ready = false; |
398 | 2.63k | absl::Mutex l; |
399 | 2.63k | std::vector<std::reference_wrapper<Network::ListenerConfig>> listeners; |
400 | 2.63k | test_server->server().dispatcher().post([&listeners, &listeners_ready, &l, &test_server]() { |
401 | 2.63k | listeners = test_server->server().listenerManager().listeners(); |
402 | 2.63k | l.Lock(); |
403 | 2.63k | listeners_ready = true; |
404 | 2.63k | l.Unlock(); |
405 | 2.63k | }); |
406 | 2.63k | l.LockWhen(absl::Condition(&listeners_ready)); |
407 | 2.63k | l.Unlock(); |
408 | | |
409 | 2.63k | auto listener_it = listeners.cbegin(); |
410 | 2.63k | auto port_it = port_names.cbegin(); |
411 | 5.26k | for (; port_it != port_names.end() && listener_it != listeners.end(); ++listener_it) { |
412 | 2.63k | auto socket_factory_it = listener_it->get().listenSocketFactories().begin(); |
413 | 5.26k | for (; socket_factory_it != listener_it->get().listenSocketFactories().end() && |
414 | 5.26k | port_it != port_names.end(); |
415 | 2.63k | ++socket_factory_it, ++port_it) { |
416 | 2.63k | const auto listen_addr = (*socket_factory_it)->localAddress(); |
417 | 2.63k | if (listen_addr->type() == Network::Address::Type::Ip) { |
418 | 2.63k | ENVOY_LOG(debug, "registered '{}' as port {}.", *port_it, listen_addr->ip()->port()); |
419 | 2.63k | registerPort(*port_it, listen_addr->ip()->port()); |
420 | 2.63k | } |
421 | 2.63k | } |
422 | 2.63k | } |
423 | 2.63k | if (test_server->server().admin().has_value()) { |
424 | 2.63k | const auto admin_addr = |
425 | 2.63k | test_server->server().admin()->socket().connectionInfoProvider().localAddress(); |
426 | 2.63k | if (admin_addr->type() == Network::Address::Type::Ip) { |
427 | 2.63k | registerPort("admin", admin_addr->ip()->port()); |
428 | 2.63k | } |
429 | 2.63k | } |
430 | 2.63k | } |
431 | | |
432 | 0 | std::string getListenerDetails(Envoy::Server::Instance& server) { |
433 | 0 | const auto& cbs_maps = server.admin()->getConfigTracker().getCallbacksMap(); |
434 | 0 | ProtobufTypes::MessagePtr details = cbs_maps.at("listeners")(Matchers::UniversalStringMatcher()); |
435 | 0 | auto listener_info = dynamic_cast<envoy::admin::v3::ListenersConfigDump&>(*details); |
436 | 0 | #ifdef ENVOY_ENABLE_YAML |
437 | 0 | return MessageUtil::getYamlStringFromMessage(listener_info.dynamic_listeners(0).error_state()); |
438 | | #else |
439 | | return listener_info.dynamic_listeners(0).error_state().DebugString(); |
440 | | #endif |
441 | 0 | } |
442 | | |
443 | | void BaseIntegrationTest::createGeneratedApiTestServer( |
444 | | const std::string& bootstrap_path, const std::vector<std::string>& port_names, |
445 | 2.64k | Server::FieldValidationConfig validator_config, bool allow_lds_rejection) { |
446 | 2.64k | createGeneratedApiTestServer(bootstrap_path, port_names, validator_config, allow_lds_rejection, |
447 | 2.64k | test_server_); |
448 | 2.64k | } |
449 | | |
450 | | void BaseIntegrationTest::createGeneratedApiTestServer( |
451 | | const std::string& bootstrap_path, const std::vector<std::string>& port_names, |
452 | | Server::FieldValidationConfig validator_config, bool allow_lds_rejection, |
453 | 2.64k | IntegrationTestServerPtr& test_server) { |
454 | 2.64k | test_server = IntegrationTestServer::create( |
455 | 2.64k | bootstrap_path, version_, on_server_ready_function_, on_server_init_function_, |
456 | 2.64k | deterministic_value_, timeSystem(), *api_, defer_listener_finalization_, process_object_, |
457 | 2.64k | validator_config, concurrency_, drain_time_, drain_strategy_, proxy_buffer_factory_, |
458 | 2.64k | use_real_stats_, use_bootstrap_node_metadata_); |
459 | 2.64k | if (config_helper_.bootstrap().static_resources().listeners_size() > 0 && |
460 | 2.64k | !defer_listener_finalization_) { |
461 | | |
462 | 2.63k | Event::TestTimeSystem::RealTimeBound bound(listeners_bound_timeout_ms_); |
463 | 2.63k | const char* success = "listener_manager.listener_create_success"; |
464 | 2.63k | const char* rejected = "listener_manager.lds.update_rejected"; |
465 | 2.63k | for (Stats::CounterSharedPtr success_counter = test_server->counter(success), |
466 | 2.63k | rejected_counter = test_server->counter(rejected); |
467 | 8.34k | (success_counter == nullptr || |
468 | 8.34k | success_counter->value() < |
469 | 8.34k | concurrency_ * config_helper_.bootstrap().static_resources().listeners_size()) && |
470 | 8.34k | (!allow_lds_rejection || rejected_counter == nullptr || rejected_counter->value() == 0); |
471 | 5.70k | success_counter = test_server->counter(success), |
472 | 5.70k | rejected_counter = test_server->counter(rejected)) { |
473 | 5.70k | if (!bound.withinBound()) { |
474 | 0 | RELEASE_ASSERT(0, "Timed out waiting for listeners."); |
475 | 0 | } |
476 | 5.70k | if (!allow_lds_rejection) { |
477 | 5.70k | RELEASE_ASSERT(rejected_counter == nullptr || rejected_counter->value() == 0, |
478 | 5.70k | absl::StrCat("Lds update failed. Details\n", |
479 | 5.70k | getListenerDetails(test_server->server()))); |
480 | 5.70k | } |
481 | | // TODO(mattklein123): Switch to events and waitFor(). |
482 | 5.70k | time_system_.realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(10)); |
483 | 5.70k | } |
484 | | |
485 | 2.63k | registerTestServerPorts(port_names, test_server); |
486 | 2.63k | } |
487 | 2.64k | } |
488 | | |
489 | | void BaseIntegrationTest::createApiTestServer(const ApiFilesystemConfig& api_filesystem_config, |
490 | | const std::vector<std::string>& port_names, |
491 | | Server::FieldValidationConfig validator_config, |
492 | 0 | bool allow_lds_rejection) { |
493 | 0 | const std::string eds_path = TestEnvironment::temporaryFileSubstitute( |
494 | 0 | api_filesystem_config.eds_path_, port_map_, version_); |
495 | 0 | const std::string cds_path = TestEnvironment::temporaryFileSubstitute( |
496 | 0 | api_filesystem_config.cds_path_, {{"eds_json_path", eds_path}}, port_map_, version_); |
497 | 0 | const std::string rds_path = TestEnvironment::temporaryFileSubstitute( |
498 | 0 | api_filesystem_config.rds_path_, port_map_, version_); |
499 | 0 | const std::string lds_path = TestEnvironment::temporaryFileSubstitute( |
500 | 0 | api_filesystem_config.lds_path_, {{"rds_json_path", rds_path}}, port_map_, version_); |
501 | 0 | createGeneratedApiTestServer(TestEnvironment::temporaryFileSubstitute( |
502 | 0 | api_filesystem_config.bootstrap_path_, |
503 | 0 | {{"cds_json_path", cds_path}, {"lds_json_path", lds_path}}, |
504 | 0 | port_map_, version_), |
505 | 0 | port_names, validator_config, allow_lds_rejection); |
506 | 0 | } |
507 | | |
508 | | void BaseIntegrationTest::sendRawHttpAndWaitForResponse( |
509 | | int port, const char* raw_http, std::string* response, bool disconnect_after_headers_complete, |
510 | 0 | Network::TransportSocketPtr transport_socket) { |
511 | 0 | auto connection = createConnectionDriver( |
512 | 0 | port, raw_http, |
513 | 0 | [response, disconnect_after_headers_complete](Network::ClientConnection& client, |
514 | 0 | const Buffer::Instance& data) -> void { |
515 | 0 | response->append(data.toString()); |
516 | 0 | if (disconnect_after_headers_complete && response->find("\r\n\r\n") != std::string::npos) { |
517 | 0 | client.close(Network::ConnectionCloseType::NoFlush); |
518 | 0 | } |
519 | 0 | }, |
520 | 0 | std::move(transport_socket)); |
521 | |
|
522 | 0 | if (connection->run() != testing::AssertionSuccess()) { |
523 | 0 | FAIL() << "Failed to get expected response within the time bound\n" |
524 | 0 | << "received " << *response << "\n"; |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | 0 | void BaseIntegrationTest::useListenerAccessLog(absl::string_view format) { |
529 | 0 | listener_access_log_name_ = TestEnvironment::temporaryPath(TestUtility::uniqueFilename()); |
530 | 0 | ASSERT_TRUE(config_helper_.setListenerAccessLog(listener_access_log_name_, format)); |
531 | 0 | } |
532 | | |
533 | | std::string BaseIntegrationTest::waitForAccessLog(const std::string& filename, uint32_t entry, |
534 | | bool allow_excess_entries, |
535 | 0 | Network::ClientConnection* client_connection) { |
536 | | |
537 | | // Wait a max of 1s for logs to flush to disk. |
538 | 0 | std::string contents; |
539 | 0 | const int num_iterations = TSAN_TIMEOUT_FACTOR * 1000; |
540 | 0 | for (int i = 0; i < num_iterations; ++i) { |
541 | 0 | contents = TestEnvironment::readFileToStringForTest(filename); |
542 | 0 | std::vector<std::string> entries = absl::StrSplit(contents, '\n', absl::SkipEmpty()); |
543 | 0 | if (entries.size() >= entry + 1) { |
544 | | // Often test authors will waitForAccessLog() for multiple requests, and |
545 | | // not increment the entry number for the second wait. Guard against that. |
546 | 0 | EXPECT_TRUE(allow_excess_entries || entries.size() == entry + 1) |
547 | 0 | << "Waiting for entry index " << entry << " but it was not the last entry as there were " |
548 | 0 | << entries.size() << "\n" |
549 | 0 | << contents; |
550 | 0 | return entries[entry]; |
551 | 0 | } |
552 | 0 | if (i % 25 == 0 && client_connection != nullptr) { |
553 | | // The QUIC default delayed ack timer is 25ms. Wait for any pending ack timers to expire, |
554 | | // then run dispatcher to send any pending acks. |
555 | 0 | client_connection->dispatcher().run(Envoy::Event::Dispatcher::RunType::NonBlock); |
556 | 0 | } |
557 | 0 | absl::SleepFor(absl::Milliseconds(1)); |
558 | 0 | } |
559 | 0 | RELEASE_ASSERT(0, absl::StrCat("Timed out waiting for access log. Found: '", contents, "'")); |
560 | 0 | return ""; |
561 | 0 | } |
562 | | |
563 | 2.64k | void BaseIntegrationTest::createXdsUpstream() { |
564 | 2.64k | if (create_xds_upstream_ == false) { |
565 | 2.63k | return; |
566 | 2.63k | } |
567 | 14 | if (tls_xds_upstream_ == false) { |
568 | 14 | addFakeUpstream(Http::CodecType::HTTP2); |
569 | 14 | } else { |
570 | 0 | envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; |
571 | 0 | auto* common_tls_context = tls_context.mutable_common_tls_context(); |
572 | 0 | common_tls_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http2); |
573 | 0 | auto* tls_cert = common_tls_context->add_tls_certificates(); |
574 | 0 | tls_cert->mutable_certificate_chain()->set_filename( |
575 | 0 | TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcert.pem")); |
576 | 0 | tls_cert->mutable_private_key()->set_filename( |
577 | 0 | TestEnvironment::runfilesPath("test/config/integration/certs/upstreamkey.pem")); |
578 | 0 | auto cfg = std::make_unique<Extensions::TransportSockets::Tls::ServerContextConfigImpl>( |
579 | 0 | tls_context, factory_context_); |
580 | |
|
581 | 0 | upstream_stats_store_ = std::make_unique<Stats::TestIsolatedStoreImpl>(); |
582 | 0 | auto context = std::make_unique<Extensions::TransportSockets::Tls::ServerSslSocketFactory>( |
583 | 0 | std::move(cfg), context_manager_, *upstream_stats_store_->rootScope(), |
584 | 0 | std::vector<std::string>{}); |
585 | 0 | addFakeUpstream(std::move(context), Http::CodecType::HTTP2, /*autonomous_upstream=*/false); |
586 | 0 | } |
587 | 14 | xds_upstream_ = fake_upstreams_.back().get(); |
588 | 14 | } |
589 | | |
590 | 14 | void BaseIntegrationTest::createXdsConnection() { |
591 | 14 | AssertionResult result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); |
592 | 14 | RELEASE_ASSERT(result, result.message()); |
593 | 14 | } |
594 | | |
595 | 14 | void BaseIntegrationTest::cleanUpXdsConnection() { |
596 | 14 | if (xds_connection_ != nullptr) { |
597 | 14 | AssertionResult result = xds_connection_->close(); |
598 | 14 | RELEASE_ASSERT(result, result.message()); |
599 | 14 | result = xds_connection_->waitForDisconnect(); |
600 | 14 | RELEASE_ASSERT(result, result.message()); |
601 | 14 | xds_connection_.reset(); |
602 | 14 | } |
603 | 14 | } |
604 | | |
605 | | AssertionResult BaseIntegrationTest::compareDiscoveryRequest( |
606 | | const std::string& expected_type_url, const std::string& expected_version, |
607 | | const std::vector<std::string>& expected_resource_names, |
608 | | const std::vector<std::string>& expected_resource_names_added, |
609 | | const std::vector<std::string>& expected_resource_names_removed, bool expect_node, |
610 | 28 | const Protobuf::int32 expected_error_code, const std::string& expected_error_substring) { |
611 | 28 | if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw || |
612 | 28 | sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedSotw) { |
613 | 20 | return compareSotwDiscoveryRequest(expected_type_url, expected_version, expected_resource_names, |
614 | 20 | expect_node, expected_error_code, expected_error_substring); |
615 | 20 | } else { |
616 | 8 | return compareDeltaDiscoveryRequest(expected_type_url, expected_resource_names_added, |
617 | 8 | expected_resource_names_removed, expected_error_code, |
618 | 8 | expected_error_substring, expect_node); |
619 | 8 | } |
620 | 28 | } |
621 | | |
622 | | AssertionResult compareSets(const std::set<std::string>& set1, const std::set<std::string>& set2, |
623 | 36 | absl::string_view name) { |
624 | 36 | if (set1 == set2) { |
625 | 36 | return AssertionSuccess(); |
626 | 36 | } |
627 | 0 | auto failure = AssertionFailure() << name << " field not as expected.\nExpected: {"; |
628 | 0 | for (const auto& x : set1) { |
629 | 0 | failure << x << ", "; |
630 | 0 | } |
631 | 0 | failure << "}\nActual: {"; |
632 | 0 | for (const auto& x : set2) { |
633 | 0 | failure << x << ", "; |
634 | 0 | } |
635 | 0 | return failure << "}"; |
636 | 36 | } |
637 | | |
638 | | AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( |
639 | | const std::string& expected_type_url, const std::string& expected_version, |
640 | | const std::vector<std::string>& expected_resource_names, bool expect_node, |
641 | | const Protobuf::int32 expected_error_code, const std::string& expected_error_substring, |
642 | 20 | FakeStream* stream) { |
643 | 20 | if (stream == nullptr) { |
644 | 20 | stream = xds_stream_.get(); |
645 | 20 | } |
646 | | |
647 | 20 | envoy::service::discovery::v3::DiscoveryRequest discovery_request; |
648 | 20 | VERIFY_ASSERTION(stream->waitForGrpcMessage(*dispatcher_, discovery_request)); |
649 | | |
650 | 20 | if (expect_node) { |
651 | 10 | EXPECT_TRUE(discovery_request.has_node()); |
652 | 10 | EXPECT_FALSE(discovery_request.node().id().empty()); |
653 | 10 | EXPECT_FALSE(discovery_request.node().cluster().empty()); |
654 | 10 | } else { |
655 | 10 | EXPECT_FALSE(discovery_request.has_node()); |
656 | 10 | } |
657 | 20 | last_node_.CopyFrom(discovery_request.node()); |
658 | | |
659 | 20 | if (expected_type_url != discovery_request.type_url()) { |
660 | 0 | return AssertionFailure() << fmt::format("type_url {} does not match expected {}", |
661 | 0 | discovery_request.type_url(), expected_type_url); |
662 | 0 | } |
663 | 20 | if (!(expected_error_code == discovery_request.error_detail().code())) { |
664 | 0 | return AssertionFailure() << fmt::format("error_code {} does not match expected {}", |
665 | 0 | discovery_request.error_detail().code(), |
666 | 0 | expected_error_code); |
667 | 0 | } |
668 | 20 | EXPECT_TRUE( |
669 | 20 | IsSubstring("", "", expected_error_substring, discovery_request.error_detail().message())); |
670 | 20 | const std::set<std::string> resource_names_in_request(discovery_request.resource_names().cbegin(), |
671 | 20 | discovery_request.resource_names().cend()); |
672 | 20 | if (auto resource_name_result = compareSets( |
673 | 20 | std::set<std::string>(expected_resource_names.cbegin(), expected_resource_names.cend()), |
674 | 20 | resource_names_in_request, "Sotw resource names")) { |
675 | 20 | return resource_name_result; |
676 | 20 | } |
677 | 0 | if (expected_version != discovery_request.version_info()) { |
678 | 0 | return AssertionFailure() << fmt::format("version {} does not match expected {} in {}", |
679 | 0 | discovery_request.version_info(), expected_version, |
680 | 0 | discovery_request.DebugString()); |
681 | 0 | } |
682 | 0 | return AssertionSuccess(); |
683 | 0 | } |
684 | | |
685 | | AssertionResult BaseIntegrationTest::waitForPortAvailable(uint32_t port, |
686 | 0 | std::chrono::milliseconds timeout) { |
687 | 0 | Event::TestTimeSystem::RealTimeBound bound(timeout); |
688 | 0 | while (bound.withinBound()) { |
689 | 0 | try { |
690 | 0 | Network::TcpListenSocket give_me_a_name( |
691 | 0 | Network::Utility::getAddressWithPort( |
692 | 0 | *Network::Test::getCanonicalLoopbackAddress(version_), port), |
693 | 0 | nullptr, true); |
694 | 0 | return AssertionSuccess(); |
695 | 0 | } catch (const EnvoyException&) { |
696 | | // The nature of this function requires using a real sleep here. |
697 | 0 | timeSystem().realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(100)); |
698 | 0 | } |
699 | 0 | } |
700 | | |
701 | 0 | return AssertionFailure() << "Timeout waiting for port availability"; |
702 | 0 | } |
703 | | |
704 | | envoy::service::discovery::v3::DeltaDiscoveryResponse |
705 | | BaseIntegrationTest::createExplicitResourcesDeltaDiscoveryResponse( |
706 | | const std::string& type_url, |
707 | | const std::vector<envoy::service::discovery::v3::Resource>& added_or_updated, |
708 | 38 | const std::vector<std::string>& removed) { |
709 | 38 | envoy::service::discovery::v3::DeltaDiscoveryResponse response; |
710 | 38 | response.set_system_version_info("system_version_info_this_is_a_test"); |
711 | 38 | response.set_type_url(type_url); |
712 | 38 | *response.mutable_resources() = {added_or_updated.begin(), added_or_updated.end()}; |
713 | 38 | *response.mutable_removed_resources() = {removed.begin(), removed.end()}; |
714 | 38 | static int next_nonce_counter = 0; |
715 | 38 | response.set_nonce(absl::StrCat("nonce", next_nonce_counter++)); |
716 | 38 | return response; |
717 | 38 | } |
718 | | |
719 | | AssertionResult BaseIntegrationTest::compareDeltaDiscoveryRequest( |
720 | | const std::string& expected_type_url, |
721 | | const std::vector<std::string>& expected_resource_subscriptions, |
722 | | const std::vector<std::string>& expected_resource_unsubscriptions, FakeStreamPtr& xds_stream, |
723 | | const Protobuf::int32 expected_error_code, const std::string& expected_error_substring, |
724 | 8 | bool expect_node) { |
725 | 8 | envoy::service::discovery::v3::DeltaDiscoveryRequest request; |
726 | 8 | VERIFY_ASSERTION(xds_stream->waitForGrpcMessage(*dispatcher_, request)); |
727 | | |
728 | | // Verify all we care about node. |
729 | 8 | if (expect_node && |
730 | 8 | (!request.has_node() || request.node().id().empty() || request.node().cluster().empty())) { |
731 | 0 | return AssertionFailure() << "Weird node field"; |
732 | 0 | } |
733 | 8 | last_node_.CopyFrom(request.node()); |
734 | 8 | if (request.type_url() != expected_type_url) { |
735 | 0 | return AssertionFailure() << fmt::format("type_url {} does not match expected {}.", |
736 | 0 | request.type_url(), expected_type_url); |
737 | 0 | } |
738 | | // Sort to ignore ordering. |
739 | 8 | std::set<std::string> expected_sub{expected_resource_subscriptions.begin(), |
740 | 8 | expected_resource_subscriptions.end()}; |
741 | 8 | std::set<std::string> expected_unsub{expected_resource_unsubscriptions.begin(), |
742 | 8 | expected_resource_unsubscriptions.end()}; |
743 | 8 | std::set<std::string> actual_sub{request.resource_names_subscribe().begin(), |
744 | 8 | request.resource_names_subscribe().end()}; |
745 | 8 | std::set<std::string> actual_unsub{request.resource_names_unsubscribe().begin(), |
746 | 8 | request.resource_names_unsubscribe().end()}; |
747 | 8 | auto sub_result = compareSets(expected_sub, actual_sub, "expected_resource_subscriptions"); |
748 | 8 | if (!sub_result) { |
749 | 0 | return sub_result; |
750 | 0 | } |
751 | 8 | auto unsub_result = |
752 | 8 | compareSets(expected_unsub, actual_unsub, "expected_resource_unsubscriptions"); |
753 | 8 | if (!unsub_result) { |
754 | 0 | return unsub_result; |
755 | 0 | } |
756 | | // (We don't care about response_nonce or initial_resource_versions.) |
757 | | |
758 | 8 | if (request.error_detail().code() != expected_error_code) { |
759 | 0 | return AssertionFailure() << fmt::format( |
760 | 0 | "error code {} does not match expected {}. (Error message is {}).", |
761 | 0 | request.error_detail().code(), expected_error_code, |
762 | 0 | request.error_detail().message()); |
763 | 0 | } |
764 | 8 | if (expected_error_code != Grpc::Status::WellKnownGrpcStatus::Ok && |
765 | 8 | request.error_detail().message().find(expected_error_substring) == std::string::npos) { |
766 | 0 | return AssertionFailure() << "\"" << expected_error_substring |
767 | 0 | << "\" is not a substring of actual error message \"" |
768 | 0 | << request.error_detail().message() << "\""; |
769 | 0 | } |
770 | 8 | return AssertionSuccess(); |
771 | 8 | } |
772 | | |
773 | | // Attempt to heuristically discover missing tag-extraction rules when new stats are added. |
774 | | // This is done by looking through the entire config for fields named `stat_prefix`, and then |
775 | | // validating that those values do not appear in the tag-extracted name of any stat. The alternate |
776 | | // approach of looking for the prefix in the extracted tags was more difficult because in the |
777 | | // tests some prefix values are reused (leading to false negatives) and some tests have base |
778 | | // configuration that sets a stat_prefix but don't produce any stats at all with that |
779 | | // configuration (leading to false positives). |
780 | | // |
781 | | // To add a rule, see `source/common/config/well_known_names.cc`. |
782 | | // |
783 | | // This is done in all integration tests because it is testing new stats and scopes that are |
784 | | // created for which the author isn't aware that tag extraction rules need to be written, and thus |
785 | | // the author wouldn't think to write tests for that themselves. |
786 | 2.64k | void BaseIntegrationTest::checkForMissingTagExtractionRules() { |
787 | 2.64k | BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( |
788 | 2.64k | test_server_->adminAddress(), "GET", "/config_dump", "", Http::CodecType::HTTP1); |
789 | 2.64k | EXPECT_TRUE(response->complete()); |
790 | 2.64k | if (!response->complete()) { |
791 | | // Allow the rest of the test to complete for better diagnostic information about the failure. |
792 | 0 | return; |
793 | 0 | } |
794 | | |
795 | 2.64k | EXPECT_EQ("200", response->headers().getStatusValue()); |
796 | 2.64k | Json::ObjectSharedPtr json = Json::Factory::loadFromString(response->body()); |
797 | | |
798 | 2.64k | std::vector<std::string> stat_prefixes; |
799 | 2.64k | Json::ObjectCallback find_stat_prefix = [&](const std::string& name, |
800 | 1.59M | const Json::Object& root) -> bool { |
801 | | // Looking for `stat_prefix` is based on precedent for how this is usually named in the |
802 | | // config. If there are other names used for a similar purpose, this check could be expanded |
803 | | // to add them also. |
804 | 1.59M | if (name == "stat_prefix") { |
805 | 2.63k | auto prefix = root.asString(); |
806 | 2.63k | if (!prefix.empty()) { |
807 | 2.63k | stat_prefixes.push_back(prefix); |
808 | 2.63k | } |
809 | 1.59M | } else if (root.isObject()) { |
810 | 503k | root.iterate(find_stat_prefix); |
811 | 1.09M | } else if (root.isArray()) { |
812 | 203k | std::vector<Json::ObjectSharedPtr> elements = root.asObjectArray(); |
813 | 485k | for (const auto& element : elements) { |
814 | 485k | find_stat_prefix("", *element); |
815 | 485k | } |
816 | 203k | } |
817 | 1.59M | return true; |
818 | 1.59M | }; |
819 | 2.64k | find_stat_prefix("", *json); |
820 | 2.64k | ENVOY_LOG_MISC(debug, "discovered stat_prefixes {}", stat_prefixes); |
821 | | |
822 | 1.06M | auto check_metric = [&](auto& metric) { |
823 | | // Validate that the `stat_prefix` string doesn't appear in the tag-extracted name, indicating |
824 | | // that it wasn't extracted. |
825 | 1.06M | const std::string tag_extracted_name = metric.tagExtractedName(); |
826 | 1.06M | for (const std::string& stat_prefix : stat_prefixes) { |
827 | 2.12M | EXPECT_EQ(tag_extracted_name.find(stat_prefix), std::string::npos) |
828 | 2.12M | << "Missing stat tag-extraction rule for stat '" << tag_extracted_name |
829 | 2.12M | << "' and stat_prefix '" << stat_prefix << "'"; |
830 | 1.06M | } |
831 | 1.06M | }; base_integration_test.cc:auto Envoy::BaseIntegrationTest::checkForMissingTagExtractionRules()::$_0::operator()<Envoy::Stats::Counter>(Envoy::Stats::Counter&) const Line | Count | Source | 822 | 813k | auto check_metric = [&](auto& metric) { | 823 | | // Validate that the `stat_prefix` string doesn't appear in the tag-extracted name, indicating | 824 | | // that it wasn't extracted. | 825 | 813k | const std::string tag_extracted_name = metric.tagExtractedName(); | 826 | 813k | for (const std::string& stat_prefix : stat_prefixes) { | 827 | 1.61M | EXPECT_EQ(tag_extracted_name.find(stat_prefix), std::string::npos) | 828 | 1.61M | << "Missing stat tag-extraction rule for stat '" << tag_extracted_name | 829 | 1.61M | << "' and stat_prefix '" << stat_prefix << "'"; | 830 | 809k | } | 831 | 813k | }; |
base_integration_test.cc:auto Envoy::BaseIntegrationTest::checkForMissingTagExtractionRules()::$_0::operator()<Envoy::Stats::Gauge>(Envoy::Stats::Gauge&) const Line | Count | Source | 822 | 220k | auto check_metric = [&](auto& metric) { | 823 | | // Validate that the `stat_prefix` string doesn't appear in the tag-extracted name, indicating | 824 | | // that it wasn't extracted. | 825 | 220k | const std::string tag_extracted_name = metric.tagExtractedName(); | 826 | 220k | for (const std::string& stat_prefix : stat_prefixes) { | 827 | 438k | EXPECT_EQ(tag_extracted_name.find(stat_prefix), std::string::npos) | 828 | 438k | << "Missing stat tag-extraction rule for stat '" << tag_extracted_name | 829 | 438k | << "' and stat_prefix '" << stat_prefix << "'"; | 830 | 219k | } | 831 | 220k | }; |
base_integration_test.cc:auto Envoy::BaseIntegrationTest::checkForMissingTagExtractionRules()::$_0::operator()<Envoy::Stats::ParentHistogram>(Envoy::Stats::ParentHistogram&) const Line | Count | Source | 822 | 35.5k | auto check_metric = [&](auto& metric) { | 823 | | // Validate that the `stat_prefix` string doesn't appear in the tag-extracted name, indicating | 824 | | // that it wasn't extracted. | 825 | 35.5k | const std::string tag_extracted_name = metric.tagExtractedName(); | 826 | 35.5k | for (const std::string& stat_prefix : stat_prefixes) { | 827 | 70.8k | EXPECT_EQ(tag_extracted_name.find(stat_prefix), std::string::npos) | 828 | 70.8k | << "Missing stat tag-extraction rule for stat '" << tag_extracted_name | 829 | 70.8k | << "' and stat_prefix '" << stat_prefix << "'"; | 830 | 35.4k | } | 831 | 35.5k | }; |
|
832 | 2.64k | test_server_->statStore().forEachCounter(nullptr, check_metric); |
833 | 2.64k | test_server_->statStore().forEachGauge(nullptr, check_metric); |
834 | 2.64k | test_server_->statStore().forEachHistogram(nullptr, check_metric); |
835 | 2.64k | } |
836 | | } // namespace Envoy |