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