Coverage Report

Created: 2023-11-12 09:30

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