1
#pragma once
2

            
3
#include "source/common/common/logger.h"
4
#include "source/extensions/config_subscription/grpc/grpc_stream.h"
5

            
6
namespace Envoy {
7
namespace Config {
8

            
9
/**
10
 * This class arbitrates between two config providers of the same GrpcMux -
11
 * the primary and the failover. Envoy always prefers fetching config from the
12
 * primary source, but if not available, it will fetch the config from the failover
13
 * source until the primary is again available.
14
 *
15
 * This class owns the state for the GrpcMux primary and failover streams, and
16
 * proxies the gRPC-stream functionality to either the primary or the failover config sources.
17
 * The failover source is optional and will only be used if given in the c'tor.
18
 *
19
 * Failover is supported in both
20
 * SotW (RequestType = envoy::service::discovery::v3::DiscoveryRequest,
21
 *   ResponseType = envoy::service::discovery::v3::DiscoveryResponse), and
22
 * Delta-xDS (RequestType = envoy::service::discovery::v3::DeltaDiscoveryRequest,
23
 *   ResponseType = envoy::service::discovery::v3::DeltaDiscoveryResponse).
24
 * Both the primary and failover streams are either SotW or Delta-xDS.
25
 *
26
 * The use of this class will be as follows: the GrpcMux object will own an instance of
27
 * the GrpcMuxFailover. The GrpcMuxFailover will own 2 GrpcStreams, primary and failover.
28
 * Each of the primary and secondary streams will invoke GrpcStreamCallbacks
29
 * on their corresponding objects (also owned by the GrpcMuxFailover). These invocations
30
 * will be followed by the GrpcMuxFailover calling the GrpcStreamCallbacks on the GrpcMux
31
 * object that initialized it.
32
 *
33
 * To simplify the state-machine, Envoy can be in one of the mutually exclusive states:
34
 *   ConnectingToPrimary - attempting to connect to the primary source.
35
 *   ConnectedToPrimary - after receiving a response from the primary source.
36
 *   ConnectingToFailover - attempting to connect to the failover source.
37
 *   ConnectedToFailover - after receiving a response from the failover source.
38
 *   None - not attempting to connect or connected to any source (e.g., upon  initialization).
39
 *
40
 * The GrpcMuxFailover attempts to establish a connection to the primary source. Once a response is
41
 * received from the primary source it will be considered available, and the failover will not be
42
 * used. Any future reconnection attempts will be to the primary source only.
43
 * However, if no response is received from the primary source, and accessing the primary has
44
 * failed 2 times in a row, the GrpcMuxFailover will attempt to establish a connection to the
45
 * failover source. Envoy will keep alternating between the primary and failover sources attempting
46
 * to connect to one of them. If a response from the failover source is received, it will be the
47
 * source of configuration until the connection is closed. In case the failover connection is
48
 * closed, Envoy will attempt to connect to the primary, before retrying to connect to the failover
49
 * source. If the failover source is unavailable or a connection to it is closed, the
50
 * GrpcMuxFailover will alternate between attempts to reconnect to the primary source and the
51
 * failover source.
52
 * TODO(adisuissa): The number of consecutive failures is currently statically
53
 * defined, and may be converted to a config field in the future.
54
 */
55
template <class RequestType, class ResponseType>
56
class GrpcMuxFailover : public GrpcStreamInterface<RequestType, ResponseType>,
57
                        public Logger::Loggable<Logger::Id::config> {
58
public:
59
  static constexpr uint32_t DefaultFailoverBackoffMilliseconds = 500;
60

            
61
  // A GrpcStream creator function that receives the stream callbacks and returns a
62
  // GrpcStream object. This is introduced to facilitate dependency injection for
63
  // testing and will be used to create the primary and failover streams.
64
  using GrpcStreamCreator = std::function<GrpcStreamInterfacePtr<RequestType, ResponseType>(
65
      GrpcStreamCallbacks<ResponseType>* stream_callbacks)>;
66

            
67
  GrpcMuxFailover(GrpcStreamCreator primary_stream_creator,
68
                  absl::optional<GrpcStreamCreator> failover_stream_creator,
69
                  GrpcStreamCallbacks<ResponseType>& grpc_mux_callbacks,
70
                  Event::Dispatcher& dispatcher)
71
221
      : grpc_mux_callbacks_(grpc_mux_callbacks), primary_callbacks_(*this),
72
221
        primary_grpc_stream_(std::move(primary_stream_creator(&primary_callbacks_))),
73
221
        connection_state_(ConnectionState::None), ever_connected_to_primary_(false),
74
221
        previously_connected_to_(ConnectedTo::None) {
75
221
    ASSERT(primary_grpc_stream_ != nullptr);
76
221
    if (failover_stream_creator.has_value()) {
77
90
      ENVOY_LOG(warn, "Using xDS-Failover. Note that the implementation is currently considered "
78
90
                      "experimental and may be modified in future Envoy versions!");
79
      // Only create the retry timer if failover is supported.
80
90
      complete_retry_timer_ = dispatcher.createTimer([this]() -> void { retryConnections(); });
81
90
      failover_callbacks_ = std::make_unique<FailoverGrpcStreamCallbacks>(*this);
82
90
      GrpcStreamCreator& failover_stream_creator_ref = failover_stream_creator.value();
83
90
      failover_grpc_stream_ = std::move(failover_stream_creator_ref(failover_callbacks_.get()));
84
90
      ASSERT(failover_grpc_stream_ != nullptr);
85
90
    }
86
221
  }
87

            
88
221
  virtual ~GrpcMuxFailover() = default;
89

            
90
  // Attempts to establish a new stream to the either the primary or failover source.
91
199
  void establishNewStream() override {
92
    // Attempt establishing a connection to the primary source.
93
    // This method may be called multiple times, even if the primary/failover stream
94
    // is already established or in the process of being established.
95
199
    if (complete_retry_timer_) {
96
95
      complete_retry_timer_->disableTimer();
97
95
    }
98
    // If already connected to one of the source, return.
99
199
    if (connection_state_ == ConnectionState::ConnectedToPrimary ||
100
199
        connection_state_ == ConnectionState::ConnectedToFailover) {
101
2
      ENVOY_LOG_MISC(trace,
102
2
                     "Already connected to an xDS server, skipping establishNewStream() call");
103
2
      return;
104
2
    }
105
197
    if (!Runtime::runtimeFeatureEnabled(
106
197
            "envoy.reloadable_features.xds_failover_to_primary_enabled")) {
107
      // Allow stickiness, so if Envoy was ever connected to the primary source only
108
      // retry to reconnect to the primary source, If Envoy was ever connected to the
109
      // failover source then only retry to reconnect to the failover source.
110
6
      if (previously_connected_to_ == ConnectedTo::Primary) {
111
        ENVOY_LOG_MISC(
112
            info, "Previously connected to the primary xDS source, attempting to reconnect to it");
113
        connection_state_ = ConnectionState::ConnectingToPrimary;
114
        primary_grpc_stream_->establishNewStream();
115
        return;
116
6
      } else if (previously_connected_to_ == ConnectedTo::Failover) {
117
1
        ENVOY_LOG_MISC(
118
1
            info, "Previously connected to the failover xDS source, attempting to reconnect to it");
119
1
        connection_state_ = ConnectionState::ConnectingToFailover;
120
1
        failover_grpc_stream_->establishNewStream();
121
1
        return;
122
1
      }
123
6
    }
124
    // connection_state_ is either None, ConnectingToPrimary or
125
    // ConnectingToFailover. In the first 2 cases try to connect to the primary
126
    // (preferring the primary in the case of None), and in the third case
127
    // try to connect to the failover.
128
    // Note that if a connection to the primary source was ever successful, the
129
    // failover manager will keep setting connection_state_ to either None or
130
    // ConnectingToPrimary, which ensures that only the primary stream will be
131
    // established.
132
196
    if (connection_state_ == ConnectionState::ConnectingToFailover) {
133
2
      ASSERT(!ever_connected_to_primary_);
134
2
      failover_grpc_stream_->establishNewStream();
135
194
    } else {
136
194
      ASSERT(connection_state_ == ConnectionState::None ||
137
194
             connection_state_ == ConnectionState::ConnectingToPrimary);
138
194
      connection_state_ = ConnectionState::ConnectingToPrimary;
139
194
      primary_grpc_stream_->establishNewStream();
140
194
    }
141
196
  }
142

            
143
  // Returns the availability of the underlying stream.
144
2582
  bool grpcStreamAvailable() const override {
145
2582
    if (connectingToOrConnectedToFailover()) {
146
386
      return failover_grpc_stream_->grpcStreamAvailable();
147
386
    }
148
    // Either connecting/connected to the primary, or no connection was attempted.
149
2196
    return primary_grpc_stream_->grpcStreamAvailable();
150
2582
  }
151

            
152
  // Sends a message using the underlying stream.
153
1877
  void sendMessage(const RequestType& request) override {
154
1877
    if (connectingToOrConnectedToFailover()) {
155
346
      ASSERT(!ever_connected_to_primary_);
156
346
      failover_grpc_stream_->sendMessage(request);
157
346
      return;
158
346
    }
159
    // Either connecting/connected to the primary, or no connection was attempted.
160
1531
    primary_grpc_stream_->sendMessage(request);
161
1531
  }
162

            
163
  // Updates the queue size of the underlying stream.
164
2608
  void maybeUpdateQueueSizeStat(uint64_t size) override {
165
2608
    if (connectingToOrConnectedToFailover()) {
166
654
      failover_grpc_stream_->maybeUpdateQueueSizeStat(size);
167
654
      return;
168
654
    }
169
    // Either connecting/connected to the primary, or no connection was attempted.
170
1954
    primary_grpc_stream_->maybeUpdateQueueSizeStat(size);
171
1954
  }
172

            
173
  // Returns true if the rate-limit allows draining.
174
1921
  bool checkRateLimitAllowsDrain() override {
175
1921
    if (connectingToOrConnectedToFailover()) {
176
346
      return failover_grpc_stream_->checkRateLimitAllowsDrain();
177
346
    }
178
    // Either connecting/connected to the primary, or no connection was attempted.
179
1575
    return primary_grpc_stream_->checkRateLimitAllowsDrain();
180
1921
  }
181

            
182
  // Returns the close status for testing purposes only.
183
  absl::optional<Grpc::Status::GrpcStatus> getCloseStatusForTest() {
184
    if (connectingToOrConnectedToFailover()) {
185
      return failover_grpc_stream_->getCloseStatusForTest();
186
    }
187
    ASSERT(connectingToOrConnectedToPrimary());
188
    return primary_grpc_stream_->getCloseStatusForTest();
189
  }
190

            
191
  // Returns the current stream for testing purposes only.
192
507
  GrpcStreamInterface<RequestType, ResponseType>& currentStreamForTest() {
193
507
    if (connectingToOrConnectedToFailover()) {
194
      return *failover_grpc_stream_;
195
    }
196
507
    ASSERT(connectingToOrConnectedToPrimary());
197
507
    return *primary_grpc_stream_;
198
507
  };
199

            
200
  // Retries to connect again to the primary and then (possibly) to the
201
  // failover. Assumes that no connection has been made or is being attempted.
202
21
  void retryConnections() {
203
21
    ASSERT(connection_state_ == ConnectionState::None);
204
21
    ENVOY_LOG(trace, "Expired timer, retrying to reconnect to the primary xDS server.");
205
21
    connection_state_ = ConnectionState::ConnectingToPrimary;
206
21
    primary_grpc_stream_->establishNewStream();
207
21
  }
208

            
209
private:
210
  friend class GrpcMuxFailoverTest;
211

            
212
  // A helper class that proxies the callbacks of GrpcStreamCallbacks for the primary service.
213
  class PrimaryGrpcStreamCallbacks : public GrpcStreamCallbacks<ResponseType> {
214
  public:
215
221
    PrimaryGrpcStreamCallbacks(GrpcMuxFailover& parent) : parent_(parent) {}
216

            
217
257
    void onStreamEstablished() override {
218
      // Although onStreamEstablished is invoked on the the primary stream, Envoy
219
      // needs to wait for the first response to be received from it before
220
      // considering the primary source as available.
221
      // Calling the onStreamEstablished() callback on the GrpcMux object will
222
      // trigger the GrpcMux to start sending requests.
223
257
      ASSERT(parent_.connection_state_ == ConnectionState::ConnectingToPrimary);
224
257
      parent_.grpc_mux_callbacks_.onStreamEstablished();
225
257
    }
226

            
227
200
    void onEstablishmentFailure(bool) override {
228
      // This will be called when the primary stream fails to establish a connection, or after the
229
      // connection was closed.
230
200
      ASSERT(parent_.connectingToOrConnectedToPrimary());
231
      // If there's no failover supported, this will just be a pass-through
232
      // callback.
233
200
      if (parent_.failover_grpc_stream_ != nullptr) {
234
182
        if (parent_.connection_state_ == ConnectionState::ConnectingToPrimary &&
235
182
            !parent_.ever_connected_to_primary_) {
236
          // If there are 2 consecutive failures to the primary, Envoy will try to connect to the
237
          // failover.
238
141
          primary_consecutive_failures_++;
239
141
          if (primary_consecutive_failures_ >= 2) {
240
            // The primary stream failed to establish a connection 2 times in a row.
241
            // Terminate the primary stream and establish a connection to the failover stream.
242
78
            ENVOY_LOG(info, "Primary xDS stream failed to establish a connection at least 2 times "
243
78
                            "in a row. Attempting to connect to the failover stream.");
244
            // This will close the stream and prevent the retry timer from
245
            // reconnecting to the primary source.
246
78
            parent_.primary_grpc_stream_->closeStream();
247
            // Next attempt will be to the failover, set the value that
248
            // determines whether to set initial_resource_versions or not.
249
78
            parent_.grpc_mux_callbacks_.onEstablishmentFailure(parent_.previously_connected_to_ ==
250
78
                                                               ConnectedTo::Failover);
251
78
            parent_.connection_state_ = ConnectionState::ConnectingToFailover;
252
78
            parent_.failover_grpc_stream_->establishNewStream();
253
78
            return;
254
78
          }
255
141
        }
256
182
      }
257
      // Pass along the failure to the GrpcMux object. Retry will be triggered
258
      // later by the underlying grpc stream.
259
122
      ENVOY_LOG_MISC(trace, "Not trying to connect to failover. Will try again to reconnect to the "
260
122
                            "primary (upon retry).");
261
122
      parent_.connection_state_ = ConnectionState::ConnectingToPrimary;
262
      // Next attempt will be to the primary, set the value that
263
      // determines whether to set initial_resource_versions or not.
264
122
      const bool next_attempt_may_send_initial_resource_version =
265
122
          parent_.previously_connected_to_ == ConnectedTo::Primary ||
266
122
          parent_.previously_connected_to_ == ConnectedTo::None;
267
122
      parent_.grpc_mux_callbacks_.onEstablishmentFailure(
268
122
          next_attempt_may_send_initial_resource_version);
269
122
    }
270

            
271
    void onDiscoveryResponse(ResponseProtoPtr<ResponseType>&& message,
272
556
                             ControlPlaneStats& control_plane_stats) override {
273
556
      ASSERT((parent_.connectingToOrConnectedToPrimary()) &&
274
556
             !parent_.connectingToOrConnectedToFailover());
275
      // Received a response from the primary. The primary is now considered available (no failover
276
      // will be attempted).
277
556
      parent_.ever_connected_to_primary_ = true;
278
556
      primary_consecutive_failures_ = 0;
279
556
      parent_.connection_state_ = ConnectionState::ConnectedToPrimary;
280
556
      parent_.previously_connected_to_ = ConnectedTo::Primary;
281
556
      parent_.grpc_mux_callbacks_.onDiscoveryResponse(std::move(message), control_plane_stats);
282
556
    }
283

            
284
4
    void onWriteable() override {
285
4
      if (parent_.connectingToOrConnectedToPrimary()) {
286
4
        parent_.grpc_mux_callbacks_.onWriteable();
287
4
      }
288
4
    }
289

            
290
  private:
291
    GrpcMuxFailover& parent_;
292
    uint32_t primary_consecutive_failures_{0};
293
  };
294

            
295
  // A helper class that proxies the callbacks of GrpcStreamCallbacks for the failover service.
296
  class FailoverGrpcStreamCallbacks : public GrpcStreamCallbacks<ResponseType> {
297
  public:
298
90
    FailoverGrpcStreamCallbacks(GrpcMuxFailover& parent) : parent_(parent) {}
299

            
300
89
    void onStreamEstablished() override {
301
      // Although the failover stream is considered established, need to wait for the
302
      // the first response to be received before considering the failover available.
303
      // Calling the onStreamEstablished() callback on the GrpcMux object will
304
      // trigger the GrpcMux to start sending requests.
305
89
      ASSERT(parent_.connection_state_ == ConnectionState::ConnectingToFailover);
306
89
      ASSERT(!parent_.ever_connected_to_primary_);
307
89
      parent_.grpc_mux_callbacks_.onStreamEstablished();
308
89
    }
309

            
310
91
    void onEstablishmentFailure(bool) override {
311
      // This will be called when the failover stream fails to establish a connection, or after the
312
      // connection was closed.
313
91
      ASSERT(parent_.connectingToOrConnectedToFailover());
314
91
      if (!Runtime::runtimeFeatureEnabled(
315
91
              "envoy.reloadable_features.xds_failover_to_primary_enabled")) {
316
        // If previously Envoy was connected to the failover, keep using that.
317
        // Otherwise let the retry mechanism try to access the primary (similar
318
        // to if the runtime flag was not set).
319
33
        if (parent_.previously_connected_to_ == ConnectedTo::Failover) {
320
33
          ENVOY_LOG(info,
321
33
                    "Failover xDS stream disconnected (either after establishing a connection or "
322
33
                    "before). Attempting to reconnect to Failover because Envoy successfully "
323
33
                    "connected to it previously.");
324
          // Not closing the failover stream, allows it to use its retry timer
325
          // to reconnect to the failover source.
326
          // Next attempt will be to the failover after Envoy was already
327
          // connected to it. Allowing to send the initial_resource_versions on reconnect.
328
33
          parent_.grpc_mux_callbacks_.onEstablishmentFailure(true);
329
33
          parent_.connection_state_ = ConnectionState::ConnectingToFailover;
330
33
          return;
331
33
        }
332
33
      }
333
      // Either this was an intentional disconnection from the failover source,
334
      // or unintentional. Either way, try to connect to the primary next.
335
58
      ENVOY_LOG(debug,
336
58
                "Failover xDS stream disconnected (either after establishing a connection or "
337
58
                "before). Attempting to connect to the primary stream.");
338

            
339
      // This will close the stream and prevent the retry timer from
340
      // reconnecting to the failover source. The next attempt will be to the
341
      // primary source.
342
58
      parent_.failover_grpc_stream_->closeStream();
343
      // Next attempt will be to the primary, set the value that
344
      // determines whether to set initial_resource_versions or not.
345
58
      const bool next_attempt_may_send_initial_resource_version =
346
58
          parent_.previously_connected_to_ == ConnectedTo::Primary ||
347
58
          parent_.previously_connected_to_ == ConnectedTo::None;
348
58
      parent_.grpc_mux_callbacks_.onEstablishmentFailure(
349
58
          next_attempt_may_send_initial_resource_version);
350
      // Setting the connection state to None, and when the retry timer will
351
      // expire, Envoy will try to connect to the primary source.
352
58
      parent_.connection_state_ = ConnectionState::None;
353
      // Wait for a short period of time before retrying to reconnect to the
354
      // primary, reducing strain on the network/servers in case of an issue.
355
      // TODO(adisuissa): need to use the primary source's retry timer here, to wait
356
      // for the next time to connect to the primary. This requires a refactor
357
      // of the retry timer and moving it from the grpc_stream to here.
358
58
      parent_.complete_retry_timer_->enableTimer(std::chrono::milliseconds(500));
359
58
    }
360

            
361
    void onDiscoveryResponse(ResponseProtoPtr<ResponseType>&& message,
362
117
                             ControlPlaneStats& control_plane_stats) override {
363
117
      ASSERT(parent_.connectingToOrConnectedToFailover());
364
117
      ASSERT(!parent_.ever_connected_to_primary_);
365
      // Received a response from the failover. The failover is now considered available (no going
366
      // back to the primary will be attempted).
367
117
      parent_.connection_state_ = ConnectionState::ConnectedToFailover;
368
117
      parent_.previously_connected_to_ = ConnectedTo::Failover;
369
117
      parent_.grpc_mux_callbacks_.onDiscoveryResponse(std::move(message), control_plane_stats);
370
117
    }
371

            
372
2
    void onWriteable() override {
373
2
      if (parent_.connectingToOrConnectedToFailover()) {
374
2
        parent_.grpc_mux_callbacks_.onWriteable();
375
2
      }
376
2
    }
377

            
378
  private:
379
    GrpcMuxFailover& parent_;
380
  };
381

            
382
  // Returns true iff the state is connecting to primary or connected to it.
383
18
  bool connectingToOrConnectedToPrimary() const {
384
18
    return connection_state_ == ConnectionState::ConnectingToPrimary ||
385
18
           connection_state_ == ConnectionState::ConnectedToPrimary;
386
18
  }
387

            
388
  // Returns true iff the state is connecting to failover or connected to it.
389
9498
  bool connectingToOrConnectedToFailover() const {
390
9498
    return connection_state_ == ConnectionState::ConnectingToFailover ||
391
9498
           connection_state_ == ConnectionState::ConnectedToFailover;
392
9498
  }
393

            
394
  // The following method overrides are to allow GrpcMuxFailover to extend the
395
  // GrpcStreamInterface. Once envoy.restart_features.xds_failover_support is deprecated,
396
  // the class will no longer need to extend the interface, and these can be removed.
397
  void onCreateInitialMetadata(Http::RequestHeaderMap&) override { PANIC("not implemented"); }
398
  void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override { PANIC("not implemented"); }
399
  void onReceiveMessage(std::unique_ptr<ResponseType>&&) override { PANIC("not implemented"); }
400
  void onReceiveTrailingMetadata(Http::ResponseTrailerMapPtr&&) override {
401
    PANIC("not implemented");
402
  }
403
  void onRemoteClose(Grpc::Status::GrpcStatus, const std::string&) override {
404
    PANIC("not implemented");
405
  }
406
14
  void closeStream() override {
407
14
    if (connectingToOrConnectedToPrimary()) {
408
13
      ENVOY_LOG_MISC(debug, "Intentionally closing the primary gRPC stream");
409
13
      primary_grpc_stream_->closeStream();
410
13
    } else if (connectingToOrConnectedToFailover()) {
411
1
      ENVOY_LOG_MISC(debug, "Intentionally closing the failover gRPC stream");
412
1
      failover_grpc_stream_->closeStream();
413
1
    }
414
14
  }
415

            
416
  // The stream callbacks that will be invoked on the GrpcMux object, to notify
417
  // about the state of the underlying primary/failover stream.
418
  GrpcStreamCallbacks<ResponseType>& grpc_mux_callbacks_;
419
  // The callbacks that will be invoked by the primary stream.
420
  PrimaryGrpcStreamCallbacks primary_callbacks_;
421
  // The stream to the primary source.
422
  GrpcStreamInterfacePtr<RequestType, ResponseType> primary_grpc_stream_;
423
  // The callbacks that will be invoked by the failover stream.
424
  std::unique_ptr<FailoverGrpcStreamCallbacks> failover_callbacks_;
425
  // The stream to the failover source.
426
  GrpcStreamInterfacePtr<RequestType, ResponseType> failover_grpc_stream_;
427

            
428
  // A timer that allows waiting for some period of time before trying to
429
  // connect again after both primary and failover attempts failed. Only
430
  // initialized when failover is supported.
431
  Event::TimerPtr complete_retry_timer_{nullptr};
432

            
433
  enum class ConnectionState {
434
    None,
435
    ConnectingToPrimary,
436
    ConnectedToPrimary,
437
    ConnectingToFailover,
438
    ConnectedToFailover
439
  };
440

            
441
  // Flags to keep track of the state of connections to primary/failover.
442
  // The object starts with all the connecting_to and connected_to flags set
443
  // to None.
444
  // Once a new stream is attempted, connecting_to_ will become Primary, until
445
  // a response will be received from the primary (connected_to_ will be set
446
  // to Primary), or a failure to establish a connection to the primary occurs.
447
  // In the latter case, if Envoy attempts to reconnect to the primary,
448
  // connecting_to_ will stay Primary, but if it attempts to connect to the failover,
449
  // connecting_to_ will be set to Failover.
450
  // If Envoy successfully connects to the failover, connected_to_ will be set
451
  // to Failover.
452
  // Note that while Envoy can only be connected to a single source (mutually
453
  // exclusive), it can attempt connecting to more than one source at a time.
454
  ConnectionState connection_state_;
455

            
456
  // A flag that keeps track of whether Envoy successfully connected to the
457
  // primary source. Envoy is considered successfully connected to a source
458
  // once it receives a response from it.
459
  bool ever_connected_to_primary_{false};
460

            
461
  enum class ConnectedTo { None, Primary, Failover };
462
  // Used to track the most recent source that Envoy was connected to.
463
  ConnectedTo previously_connected_to_;
464
};
465

            
466
} // namespace Config
467
} // namespace Envoy