Coverage Report

Created: 2024-09-19 09:45

/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