1
#include "source/server/admin/clusters_handler.h"
2

            
3
#include "envoy/admin/v3/clusters.pb.h"
4

            
5
#include "source/common/http/headers.h"
6
#include "source/common/http/utility.h"
7
#include "source/common/network/utility.h"
8
#include "source/common/upstream/host_utility.h"
9
#include "source/server/admin/utils.h"
10

            
11
namespace Envoy {
12
namespace Server {
13

            
14
namespace {
15

            
16
void addCircuitBreakerSettingsAsText(const std::string& cluster_name,
17
                                     const std::string& priority_str,
18
                                     Upstream::ResourceManager& resource_manager,
19
12
                                     Buffer::Instance& response) {
20
12
  response.add(fmt::format("{}::{}_priority::max_connections::{}\n", cluster_name, priority_str,
21
12
                           resource_manager.connections().max()));
22
12
  response.add(fmt::format("{}::{}_priority::max_pending_requests::{}\n", cluster_name,
23
12
                           priority_str, resource_manager.pendingRequests().max()));
24
12
  response.add(fmt::format("{}::{}_priority::max_requests::{}\n", cluster_name, priority_str,
25
12
                           resource_manager.requests().max()));
26
12
  response.add(fmt::format("{}::{}_priority::max_retries::{}\n", cluster_name, priority_str,
27
12
                           resource_manager.retries().max()));
28
12
}
29

            
30
void addCircuitBreakerSettingsAsJson(const envoy::config::core::v3::RoutingPriority& priority,
31
                                     Upstream::ResourceManager& resource_manager,
32
30
                                     envoy::admin::v3::ClusterStatus& cluster_status) {
33
30
  auto& thresholds = *cluster_status.mutable_circuit_breakers()->add_thresholds();
34
30
  thresholds.set_priority(priority);
35
30
  thresholds.mutable_max_connections()->set_value(resource_manager.connections().max());
36
30
  thresholds.mutable_max_pending_requests()->set_value(resource_manager.pendingRequests().max());
37
30
  thresholds.mutable_max_requests()->set_value(resource_manager.requests().max());
38
30
  thresholds.mutable_max_retries()->set_value(resource_manager.retries().max());
39
30
}
40

            
41
} // namespace
42

            
43
10735
ClustersHandler::ClustersHandler(Server::Instance& server) : HandlerContextBase(server) {}
44

            
45
Http::Code ClustersHandler::handlerClusters(Http::ResponseHeaderMap& response_headers,
46
14
                                            Buffer::Instance& response, AdminStream& admin_stream) {
47
14
  const auto format_value = Utility::formatParam(admin_stream.queryParams());
48
14
  const auto filter_value = admin_stream.queryParams().getFirstValue("filter");
49

            
50
14
  absl::optional<const re2::RE2> re2_filter;
51
14
  if (filter_value.has_value() && !filter_value.value().empty()) {
52
5
    re2::RE2::Options options;
53
5
    options.set_log_errors(false);
54
5
    re2_filter.emplace(filter_value.value(), options);
55
5
    if (!re2_filter->ok()) {
56
      response.add("Invalid re2 regex");
57
      return Http::Code::BadRequest;
58
    }
59
5
  }
60

            
61
14
  if (format_value.has_value() && format_value.value() == "json") {
62
9
    writeClustersAsJson(re2_filter, response);
63
9
    response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Json);
64
9
  } else {
65
5
    writeClustersAsText(re2_filter, response);
66
5
  }
67

            
68
14
  return Http::Code::OK;
69
14
}
70

            
71
// Helper method that ensures that we've setting flags based on all the health flag values on the
72
// host.
73
void setHealthFlag(Upstream::Host::HealthFlag flag, const Upstream::Host& host,
74
62
                   envoy::admin::v3::HostHealthStatus& health_status) {
75
62
  switch (flag) {
76
6
  case Upstream::Host::HealthFlag::FAILED_ACTIVE_HC:
77
6
    health_status.set_failed_active_health_check(
78
6
        host.healthFlagGet(Upstream::Host::HealthFlag::FAILED_ACTIVE_HC));
79
6
    break;
80
6
  case Upstream::Host::HealthFlag::FAILED_OUTLIER_CHECK:
81
6
    health_status.set_failed_outlier_check(
82
6
        host.healthFlagGet(Upstream::Host::HealthFlag::FAILED_OUTLIER_CHECK));
83
6
    break;
84
5
  case Upstream::Host::HealthFlag::FAILED_EDS_HEALTH:
85
10
  case Upstream::Host::HealthFlag::DEGRADED_EDS_HEALTH:
86
10
    if (host.healthFlagGet(Upstream::Host::HealthFlag::FAILED_EDS_HEALTH)) {
87
1
      health_status.set_eds_health_status(envoy::config::core::v3::UNHEALTHY);
88
9
    } else if (host.healthFlagGet(Upstream::Host::HealthFlag::DEGRADED_EDS_HEALTH)) {
89
3
      health_status.set_eds_health_status(envoy::config::core::v3::DEGRADED);
90
9
    } else {
91
6
      health_status.set_eds_health_status(envoy::config::core::v3::HEALTHY);
92
6
    }
93
10
    break;
94
6
  case Upstream::Host::HealthFlag::DEGRADED_ACTIVE_HC:
95
6
    health_status.set_failed_active_degraded_check(
96
6
        host.healthFlagGet(Upstream::Host::HealthFlag::DEGRADED_ACTIVE_HC));
97
6
    break;
98
6
  case Upstream::Host::HealthFlag::PENDING_DYNAMIC_REMOVAL:
99
6
    health_status.set_pending_dynamic_removal(
100
6
        host.healthFlagGet(Upstream::Host::HealthFlag::PENDING_DYNAMIC_REMOVAL));
101
6
    break;
102
6
  case Upstream::Host::HealthFlag::PENDING_ACTIVE_HC:
103
6
    health_status.set_pending_active_hc(
104
6
        host.healthFlagGet(Upstream::Host::HealthFlag::PENDING_ACTIVE_HC));
105
6
    break;
106
6
  case Upstream::Host::HealthFlag::EXCLUDED_VIA_IMMEDIATE_HC_FAIL:
107
6
    health_status.set_excluded_via_immediate_hc_fail(
108
6
        host.healthFlagGet(Upstream::Host::HealthFlag::EXCLUDED_VIA_IMMEDIATE_HC_FAIL));
109
6
    break;
110
6
  case Upstream::Host::HealthFlag::ACTIVE_HC_TIMEOUT:
111
6
    health_status.set_active_hc_timeout(
112
6
        host.healthFlagGet(Upstream::Host::HealthFlag::ACTIVE_HC_TIMEOUT));
113
6
    break;
114
6
  case Upstream::Host::HealthFlag::EDS_STATUS_DRAINING:
115
6
    if (host.healthFlagGet(Upstream::Host::HealthFlag::EDS_STATUS_DRAINING)) {
116
2
      health_status.set_eds_health_status(envoy::config::core::v3::DRAINING);
117
2
    }
118
6
    break;
119
4
  case Upstream::Host::HealthFlag::DEGRADED_OUTLIER_DETECTION:
120
4
    health_status.set_failed_degraded_outlier_detection(
121
4
        host.healthFlagGet(Upstream::Host::HealthFlag::DEGRADED_OUTLIER_DETECTION));
122
4
    break;
123
62
  }
124
62
}
125

            
126
// TODO(efimki): Add support of text readouts stats.
127
void ClustersHandler::writeClustersAsJson(const absl::optional<const re2::RE2>& filter,
128
9
                                          Buffer::Instance& response) {
129
9
  envoy::admin::v3::Clusters clusters;
130
  // TODO(mattklein123): Add ability to see warming clusters in admin output.
131
9
  auto all_clusters = server_.clusterManager().clusters();
132
24
  for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) {
133
24
    UNREFERENCED_PARAMETER(name);
134
24
    const Upstream::Cluster& cluster = cluster_ref.get();
135
24
    Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info();
136
24
    if (!shouldIncludeCluster(cluster_info->name(), filter)) {
137
9
      continue;
138
9
    }
139

            
140
15
    envoy::admin::v3::ClusterStatus& cluster_status = *clusters.add_cluster_statuses();
141
15
    cluster_status.set_name(cluster_info->name());
142
15
    cluster_status.set_observability_name(cluster_info->observabilityName());
143
15
    if (const auto& name = cluster_info->edsServiceName(); !name.empty()) {
144
1
      cluster_status.set_eds_service_name(name);
145
1
    }
146
15
    addCircuitBreakerSettingsAsJson(
147
15
        envoy::config::core::v3::RoutingPriority::DEFAULT,
148
15
        cluster.info()->resourceManager(Upstream::ResourcePriority::Default), cluster_status);
149
15
    addCircuitBreakerSettingsAsJson(
150
15
        envoy::config::core::v3::RoutingPriority::HIGH,
151
15
        cluster.info()->resourceManager(Upstream::ResourcePriority::High), cluster_status);
152

            
153
15
    const Upstream::Outlier::Detector* outlier_detector = cluster.outlierDetector();
154
15
    if (outlier_detector != nullptr &&
155
15
        outlier_detector->successRateEjectionThreshold(
156
1
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin) > 0.0) {
157
1
      cluster_status.mutable_success_rate_ejection_threshold()->set_value(
158
1
          outlier_detector->successRateEjectionThreshold(
159
1
              Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin));
160
1
    }
161
15
    if (outlier_detector != nullptr &&
162
15
        outlier_detector->successRateEjectionThreshold(
163
1
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin) > 0.0) {
164
1
      cluster_status.mutable_local_origin_success_rate_ejection_threshold()->set_value(
165
1
          outlier_detector->successRateEjectionThreshold(
166
1
              Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin));
167
1
    }
168

            
169
15
    cluster_status.set_added_via_api(cluster_info->addedViaApi());
170

            
171
15
    for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) {
172
15
      for (auto& host : host_set->hosts()) {
173
4
        envoy::admin::v3::HostStatus& host_status = *cluster_status.add_host_statuses();
174
4
        Network::Utility::addressToProtobufAddress(*host->address(),
175
4
                                                   *host_status.mutable_address());
176
4
        host_status.set_hostname(host->hostname());
177
4
        host_status.mutable_locality()->MergeFrom(host->locality());
178

            
179
21
        for (const auto& [counter_name, counter] : host->counters()) {
180
21
          auto& metric = *host_status.add_stats();
181
21
          metric.set_name(std::string(counter_name));
182
21
          metric.set_value(counter.get().value());
183
21
          metric.set_type(envoy::admin::v3::SimpleMetric::COUNTER);
184
21
        }
185

            
186
8
        for (const auto& [gauge_name, gauge] : host->gauges()) {
187
8
          auto& metric = *host_status.add_stats();
188
8
          metric.set_name(std::string(gauge_name));
189
8
          metric.set_value(gauge.get().value());
190
8
          metric.set_type(envoy::admin::v3::SimpleMetric::GAUGE);
191
8
        }
192

            
193
4
        envoy::admin::v3::HostHealthStatus& health_status = *host_status.mutable_health_status();
194

            
195
// Invokes setHealthFlag for each health flag.
196
4
#define SET_HEALTH_FLAG(name, notused)                                                             \
197
44
  setHealthFlag(Upstream::Host::HealthFlag::name, *host, health_status);
198
44
        HEALTH_FLAG_ENUM_VALUES(SET_HEALTH_FLAG)
199
4
#undef SET_HEALTH_FLAG
200

            
201
4
        double success_rate = host->outlierDetector().successRate(
202
4
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin);
203
4
        if (success_rate >= 0.0) {
204
1
          host_status.mutable_success_rate()->set_value(success_rate);
205
1
        }
206

            
207
4
        host_status.set_weight(host->weight());
208

            
209
4
        host_status.set_priority(host->priority());
210
4
        success_rate = host->outlierDetector().successRate(
211
4
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin);
212
4
        if (success_rate >= 0.0) {
213
1
          host_status.mutable_local_origin_success_rate()->set_value(success_rate);
214
1
        }
215
4
      }
216
15
    }
217
15
  }
218
9
  response.add(MessageUtil::getJsonStringFromMessageOrError(clusters, true)); // pretty-print
219
9
}
220

            
221
// TODO(efimki): Add support of text readouts stats.
222
void ClustersHandler::writeClustersAsText(const absl::optional<const re2::RE2>& filter,
223
5
                                          Buffer::Instance& response) {
224
  // TODO(mattklein123): Add ability to see warming clusters in admin output.
225
5
  auto all_clusters = server_.clusterManager().clusters();
226
8
  for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) {
227
8
    UNREFERENCED_PARAMETER(name);
228
8
    const Upstream::Cluster& cluster = cluster_ref.get();
229
8
    const std::string& cluster_name = cluster.info()->name();
230
8
    if (!shouldIncludeCluster(cluster_name, filter)) {
231
2
      continue;
232
2
    }
233

            
234
6
    response.add(fmt::format("{}::observability_name::{}\n", cluster_name,
235
6
                             cluster.info()->observabilityName()));
236
6
    addOutlierInfo(cluster_name, cluster.outlierDetector(), response);
237

            
238
6
    addCircuitBreakerSettingsAsText(
239
6
        cluster_name, "default",
240
6
        cluster.info()->resourceManager(Upstream::ResourcePriority::Default), response);
241
6
    addCircuitBreakerSettingsAsText(
242
6
        cluster_name, "high", cluster.info()->resourceManager(Upstream::ResourcePriority::High),
243
6
        response);
244

            
245
6
    response.add(
246
6
        fmt::format("{}::added_via_api::{}\n", cluster_name, cluster.info()->addedViaApi()));
247
6
    if (const auto& name = cluster.info()->edsServiceName(); !name.empty()) {
248
1
      response.add(fmt::format("{}::eds_service_name::{}\n", cluster_name, name));
249
1
    }
250
6
    for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) {
251
6
      for (auto& host : host_set->hosts()) {
252
4
        const std::string& host_address = host->address()->asString();
253
4
        std::map<absl::string_view, uint64_t> all_stats;
254
21
        for (const auto& [counter_name, counter] : host->counters()) {
255
21
          all_stats[counter_name] = counter.get().value();
256
21
        }
257

            
258
8
        for (const auto& [gauge_name, gauge] : host->gauges()) {
259
8
          all_stats[gauge_name] = gauge.get().value();
260
8
        }
261

            
262
29
        for (const auto& [stat_name, stat] : all_stats) {
263
29
          response.add(
264
29
              fmt::format("{}::{}::{}::{}\n", cluster_name, host_address, stat_name, stat));
265
29
        }
266

            
267
4
        response.add(
268
4
            fmt::format("{}::{}::hostname::{}\n", cluster_name, host_address, host->hostname()));
269
4
        response.add(fmt::format("{}::{}::health_flags::{}\n", cluster_name, host_address,
270
4
                                 Upstream::HostUtility::healthFlagsToString(*host)));
271
4
        response.add(
272
4
            fmt::format("{}::{}::weight::{}\n", cluster_name, host_address, host->weight()));
273
4
        response.add(fmt::format("{}::{}::region::{}\n", cluster_name, host_address,
274
4
                                 host->locality().region()));
275
4
        response.add(
276
4
            fmt::format("{}::{}::zone::{}\n", cluster_name, host_address, host->locality().zone()));
277
4
        response.add(fmt::format("{}::{}::sub_zone::{}\n", cluster_name, host_address,
278
4
                                 host->locality().sub_zone()));
279
4
        response.add(
280
4
            fmt::format("{}::{}::canary::{}\n", cluster_name, host_address, host->canary()));
281
4
        response.add(
282
4
            fmt::format("{}::{}::priority::{}\n", cluster_name, host_address, host->priority()));
283
4
        response.add(fmt::format(
284
4
            "{}::{}::success_rate::{}\n", cluster_name, host_address,
285
4
            host->outlierDetector().successRate(
286
4
                Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)));
287
4
        response.add(fmt::format(
288
4
            "{}::{}::local_origin_success_rate::{}\n", cluster_name, host_address,
289
4
            host->outlierDetector().successRate(
290
4
                Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)));
291
4
      }
292
6
    }
293
6
  }
294
5
}
295

            
296
bool ClustersHandler::shouldIncludeCluster(const std::string& cluster_name,
297
32
                                           const absl::optional<const re2::RE2>& filter) {
298
32
  return !filter.has_value() || re2::RE2::PartialMatch(cluster_name, filter.value());
299
32
}
300
void ClustersHandler::addOutlierInfo(const std::string& cluster_name,
301
                                     const Upstream::Outlier::Detector* outlier_detector,
302
6
                                     Buffer::Instance& response) {
303
6
  if (outlier_detector) {
304
1
    response.add(fmt::format(
305
1
        "{}::outlier::success_rate_average::{:g}\n", cluster_name,
306
1
        outlier_detector->successRateAverage(
307
1
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)));
308
1
    response.add(fmt::format(
309
1
        "{}::outlier::success_rate_ejection_threshold::{:g}\n", cluster_name,
310
1
        outlier_detector->successRateEjectionThreshold(
311
1
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)));
312
1
    response.add(fmt::format(
313
1
        "{}::outlier::local_origin_success_rate_average::{:g}\n", cluster_name,
314
1
        outlier_detector->successRateAverage(
315
1
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)));
316
1
    response.add(fmt::format(
317
1
        "{}::outlier::local_origin_success_rate_ejection_threshold::{:g}\n", cluster_name,
318
1
        outlier_detector->successRateEjectionThreshold(
319
1
            Upstream::Outlier::DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)));
320
1
  }
321
6
}
322

            
323
} // namespace Server
324
} // namespace Envoy