Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package_group(
proto_library(
name = "protos",
deps = [
"//envoy/admin/v2alpha:clusters",
"//envoy/admin/v2alpha:config_dump",
"//envoy/api/v2:cds",
"//envoy/api/v2:discovery",
Expand Down
11 changes: 11 additions & 0 deletions api/envoy/admin/v2alpha/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@ api_proto_library(
"//envoy/config/bootstrap/v2:bootstrap",
],
)

api_proto_library(
name = "clusters",
srcs = ["clusters.proto"],
visibility = ["//visibility:public"],
deps = [
"//envoy/api/v2/core:address",
"//envoy/api/v2/core:base",
"//envoy/type:percent",
],
)
93 changes: 93 additions & 0 deletions api/envoy/admin/v2alpha/clusters.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
syntax = "proto3";

package envoy.admin.v2alpha;

import "envoy/api/v2/core/base.proto";
import "envoy/api/v2/core/address.proto";
import "envoy/type/percent.proto";

// [#protodoc-title: Clusters]

// Admin endpoint uses this wrapper for `/clusters` to display cluster status information.
// See :ref:`/clusters <operations_admin_interface_clusters>` for more information.
message Clusters {
// Mapping from cluster name to each cluster's status.
repeated ClusterStatus cluster_statuses = 1;
}

// Details an individual cluster's current status.
message ClusterStatus {
// Name of the cluster.
string name = 1;

// General outlier statistics if installed for this cluster.
OutlierInfo outlier_info = 2;

// Denotes whether this cluster was added via API or configured statically.
bool added_via_api = 3;

// Mapping from host address to the host's current status.
repeated HostStatus host_statuses = 4;
}

// :ref:`Cluster outlier detection <arch_overview_outlier_detection>` statistics. The omission of
// any statistic denotes that there was not enough data to compute it.
message OutlierInfo {
// The average success rate of the hosts in the Detector for the last aggregation interval.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

References back to docs on this?

//
// Note: the message will be omitted if there were not enough hosts with enough request volume to
// proceed with success rate based outlier ejection.
envoy.type.Percent success_rate_average = 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be derived from the individual hosts' success_rate by the consumer of the endpoint?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yes (see below for a more detailed answer). Is it worth providing it for usability?

They will be close. Technically, they may differ under two scenarios IIUC:

  1. A host has been removed since the last time the success rates were updated (they operate on a timer).
  2. I'm not 100% sure, but I think hosts that go from getting enough requests to be considered back to not having enough requests to be considered will report an outdated number for the successRate() call (this one is probably a bug since this goes against the documented behavior). Same effect if the number of hosts drops back below the threshold for calculating the cluster-wide success rate average - none of their values get updated until the cluster gets back above that threshold.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the distinction is important to the use for (1) and (2), thenI would vote to just provide it at the host-level and allow the consumer to compute.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the internally computed cluster-wide average is compared to each host's average to determine whether the host gets ejected or not, I would think the exact cluster-wide average might be important for users when debugging why a host got ejected. However, it's something that's easy to add later if someone specifically requests it, so SGTM.


// The success rate threshold used in the last interval. The threshold is used to eject hosts
// based on their success rate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//
// Note: the message will be omitted if there were not enough hosts with enough request volume to
// proceed with success rate based outlier ejection. Since this value can technically be negative
// due to the way that it's computed, negative values will also be represented by the absence of
// this field because a negative value implies that there was no threshold for that interval.
envoy.type.Percent success_rate_ejection_threshold = 2;
}

// Current state of a particular host.
message HostStatus {
// Address of this host.
envoy.api.v2.core.Address address = 1;

// Mapping from the name of the statistic to the current value.
map<string, int64> stats = 2;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the first time we're defining in proto the stats data model?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly - see here. The only more sophisticated definition I could imagine would be differentiating between gauges and counters.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think it's fine to have a simpler variant without relying on Prometheus protos. Might belong in its own message though, so we can reuse again later. Would suggest the domain to be uint64.

@mrice32 mrice32 Jun 19, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM. Where do you think this stat message should go? Under api/envoy/data/, in api/envoy/metrics/v2/stats.proto, or somewhere else?


// The host's current health status.
HostHealthStatus health_status = 3;

// Configured load balancing weight for this host.
uint64 weight = 4;

// Configured locality for the host.
envoy.api.v2.core.Locality locality = 5;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm, yes, there's definitely some overlap, but I worry that this will creep into us providing the entire EDS config in /clusters (the overlap isn't 100%, so we would have to leave out fields). Didn't you have a suggestion on the previous round of review that we should eliminate config details that could easily be found in /config_dump to delineate status from configuration? Given that EDS info should be available in /config_dump, should we try to remove all of that from the host portion as well? I'm not super opinionated, but I think our approach should be consistent.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/config_dump doesn't provide EDS today :) @mattklein123 do you think we should provide EDS here or in /config_dump? I think the latter, but this PR seems related.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it should be provided in /config_dump. The reason I didn't do it is that it isn't a trivial change/design to figure out how to do it. Should it be top level? Should it be embedded within clusters? How will the config source be managed? Etc. I opened the issue to track adding it and hopefully we can discuss there when someone wants to work on it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM, I'll remove weight, locality, and canary from HostStatus.


// Denotes whether this is configured as a canary host or not.
bool canary = 6;

// Request success rate for this host over the last calculated interval.
//
// Note: the message will not be present if host did not have enough request volume to calculate
// success rate or the cluster did not have enough hosts to run through success rate outlier
// ejection.
envoy.type.Percent success_rate = 7;
}

// Health status for a host.
message HostHealthStatus {
// The host is currently marked as healthy.
bool healthy = 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify this to just indicating when a host is unhealthy, and then when there are no bits set saying it's unhealthy, it's considered healthy. I wonder how this relates to https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/core/health_check.proto#L182.. it seems we should have the ability to also indicate draining? Should we have a single health status message?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the former, I thought about that. My only hesitation is from a usability perspective. The highest order bit here is whether the host is healthy or not, but that would be the hardest for the user to determine if we were to eliminate the redundant healthy bool. You could probably get a little clever by omitting this message or a submessage for a healthy host to create an artificial way to signal a healthy host, but that might be more trouble than it's worth. WDYT?

As for how this relates to the EDS health status, that is reduced to a bool here IIUC - failed_eds_health_check. We could make this bool an enum to represent the full range of EDS health states rather than just healthy or unhealthy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't have to spend too much time making these "usable", in that the APIs are intended for machine parsing first. So, I think we can drop the healthy status. The EDS health status is really a reflection of other health status possibilities. We might for example, via HDS, discover that a host is draining independent to EDS. So, I think we should be able to convey these possibilities. I would argue that we probably just want a single health status proto and it should be https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/core/health_check.proto#L182, we should make it work if it's not expressive enough for the requiremetns here (e.g. indicating outlier detection).


// The host is currently failing active health checks.
bool failed_active_health_check = 2;

// The host is currently considered an outlier and has been ejected.
bool failed_outlier_check = 3;

// The host is currently marked as unhealthy by EDS.
bool failed_eds_health_check = 4;
}
1 change: 1 addition & 0 deletions docs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ bazel --batch build ${BAZEL_BUILD_OPTIONS} @envoy_api//docs:protos --aspects \
# These are the protos we want to put in docs, this list will grow.
# TODO(htuch): Factor this out of this script.
PROTO_RST="
/envoy/admin/v2alpha/clusters/envoy/admin/v2alpha/clusters.proto.rst
/envoy/admin/v2alpha/config_dump/envoy/admin/v2alpha/config_dump.proto.rst
/envoy/api/v2/core/address/envoy/api/v2/core/address.proto.rst
/envoy/api/v2/core/base/envoy/api/v2/core/base.proto.rst
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v2/admin/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Admin
:maxdepth: 2

../admin/v2alpha/config_dump.proto
../admin/v2alpha/clusters.proto
2 changes: 2 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Version history
to filter logs based on request headers
* admin: added :http:get:`/config_dump` for dumping the current configuration and associated xDS
version information (if applicable).
* admin: added :http:get:`/clusters?format=json` for outputing a JSON-serialized proto detailing
the current status of all clusters.
* admin: added :http:get:`/stats/prometheus` as an alternative endpoint for getting stats in prometheus format.
* admin: added :ref:`/runtime_modify endpoint <operations_admin_interface_runtime_modify>` to add or change runtime values
* admin: mutations must be sent as POSTs, rather than GETs. Mutations include:
Expand Down
5 changes: 5 additions & 0 deletions docs/root/operations/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ modify different aspects of the server:

*/failed_outlier_check*: The host has failed an outlier detection check.

.. http:get:: /clusters?format=json

Dump the */clusters* output in a JSON-serialized proto. See the
:ref:`definition <envoy_api_msg_admin.v2alpha.Clusters>` for more information.

.. _operations_admin_interface_config_dump:

.. http:get:: /config_dump
Expand Down
2 changes: 2 additions & 0 deletions source/server/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ envoy_cc_library(
"//source/common/http/http1:codec_lib",
"//source/common/network:listen_socket_lib",
"//source/common/network:raw_buffer_socket_lib",
"//source/common/network:utility_lib",
"//source/common/profiler:profiler_lib",
"//source/common/router:config_lib",
"//source/common/stats:stats_lib",
"//source/common/upstream:host_utility_lib",
"//source/extensions/access_loggers/file:file_access_log_lib",
"@envoy_api//envoy/admin/v2alpha:clusters_cc",
"@envoy_api//envoy/admin/v2alpha:config_dump_cc",
],
)
Expand Down
81 changes: 79 additions & 2 deletions source/server/http/admin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <utility>
#include <vector>

#include "envoy/admin/v2alpha/clusters.pb.h"
#include "envoy/admin/v2alpha/config_dump.pb.h"
#include "envoy/filesystem/filesystem.h"
#include "envoy/runtime/runtime.h"
Expand All @@ -34,6 +35,7 @@
#include "common/http/http1/codec_impl.h"
#include "common/json/json_loader.h"
#include "common/network/listen_socket_impl.h"
#include "common/network/utility.h"
#include "common/profiler/profiler.h"
#include "common/router/config_impl.h"
#include "common/stats/stats_impl.h"
Expand Down Expand Up @@ -242,8 +244,69 @@ void AdminImpl::addCircuitSettings(const std::string& cluster_name, const std::s
resource_manager.retries().max()));
}

Http::Code AdminImpl::handlerClusters(absl::string_view, Http::HeaderMap&,
Buffer::Instance& response, AdminStream&) {
void AdminImpl::writeClustersAsJson(Buffer::Instance& response) {
envoy::admin::v2alpha::Clusters clusters;
for (auto& cluster_pair : server_.clusterManager().clusters()) {
const Upstream::Cluster& cluster = cluster_pair.second.get();
Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info();

envoy::admin::v2alpha::ClusterStatus& cluster_status = *clusters.add_cluster_statuses();
cluster_status.set_name(cluster_info->name());

const Upstream::Outlier::Detector* outlier_detector = cluster.outlierDetector();
if (outlier_detector != nullptr) {
double success_rate_average = outlier_detector->successRateAverage();
if (success_rate_average >= 0.0) {
cluster_status.mutable_outlier_info()->mutable_success_rate_average()->set_value(
success_rate_average);
}

double success_rate_ejection_threshold = outlier_detector->successRateEjectionThreshold();
if (success_rate_ejection_threshold >= 0.0) {
cluster_status.mutable_outlier_info()->mutable_success_rate_ejection_threshold()->set_value(
success_rate_ejection_threshold);
}
}

cluster_status.set_added_via_api(cluster_info->addedViaApi());

for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) {
for (auto& host : host_set->hosts()) {
envoy::admin::v2alpha::HostStatus& host_status = *cluster_status.add_host_statuses();
Network::Utility::addressToProtobufAddress(*host->address(),
*host_status.mutable_address());

for (const Stats::CounterSharedPtr& counter : host->counters()) {
(*host_status.mutable_stats())[counter->name()] = counter->value();
}

for (const Stats::GaugeSharedPtr& gauge : host->gauges()) {
(*host_status.mutable_stats())[gauge->name()] = gauge->value();
}

envoy::admin::v2alpha::HostHealthStatus& health_status =
*host_status.mutable_health_status();
health_status.set_healthy(host->healthy());
health_status.set_failed_active_health_check(
host->healthFlagGet(Upstream::Host::HealthFlag::FAILED_ACTIVE_HC));
health_status.set_failed_outlier_check(
host->healthFlagGet(Upstream::Host::HealthFlag::FAILED_OUTLIER_CHECK));
health_status.set_failed_eds_health_check(
host->healthFlagGet(Upstream::Host::HealthFlag::FAILED_EDS_HEALTH));
host_status.set_weight(host->weight());
*host_status.mutable_locality() = host->locality();
host_status.set_canary(host->canary());
double success_rate = host->outlierDetector().successRate();
if (success_rate >= 0) {
host_status.mutable_success_rate()->set_value(success_rate);
}
}
}
}
response.add(MessageUtil::getJsonStringFromMessage(clusters, true)); // pretty-print
}

void AdminImpl::writeClustersAsText(Buffer::Instance& response) {
for (auto& cluster : server_.clusterManager().clusters()) {
addOutlierInfo(cluster.second.get().info()->name(), cluster.second.get().outlierDetector(),
response);
Expand Down Expand Up @@ -293,6 +356,20 @@ Http::Code AdminImpl::handlerClusters(absl::string_view, Http::HeaderMap&,
}
}
}
}

Http::Code AdminImpl::handlerClusters(absl::string_view url, Http::HeaderMap& response_headers,
Buffer::Instance& response, AdminStream&) {
Http::Utility::QueryParams query_params = Http::Utility::parseQueryString(url);
auto it = query_params.find("format");

if (it != query_params.end() && it->second == "json") {
writeClustersAsJson(response);
response_headers.insertContentType().value().setReference(
Http::Headers::get().ContentTypeValues.Json);
} else {
writeClustersAsText(response);
}

return Http::Code::OK;
}
Expand Down
8 changes: 8 additions & 0 deletions source/server/http/admin.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <utility>
#include <vector>

#include "envoy/admin/v2alpha/clusters.pb.h"
#include "envoy/http/filter.h"
#include "envoy/network/filter.h"
#include "envoy/network/listen_socket.h"
Expand Down Expand Up @@ -137,11 +138,18 @@ class AdminImpl : public Admin,
* @return TRUE if level change succeeded, FALSE otherwise.
*/
bool changeLogLevel(const Http::Utility::QueryParams& params);

/**
* Helper methods for the /clusters url handler.
*/
void addCircuitSettings(const std::string& cluster_name, const std::string& priority_str,
Upstream::ResourceManager& resource_manager, Buffer::Instance& response);
void addOutlierInfo(const std::string& cluster_name,
const Upstream::Outlier::Detector* outlier_detector,
Buffer::Instance& response);
void writeClustersAsJson(Buffer::Instance& response);
void writeClustersAsText(Buffer::Instance& response);

static std::string statsAsJson(const std::map<std::string, uint64_t>& all_stats,
const std::vector<Stats::ParentHistogramSharedPtr>& all_histograms,
bool show_all, bool pretty_print = false);
Expand Down
2 changes: 2 additions & 0 deletions test/server/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ envoy_cc_test(
"//source/common/http:message_lib",
"//source/common/json:json_loader_lib",
"//source/common/profiler:profiler_lib",
"//source/common/protobuf",
"//source/common/protobuf:utility_lib",
"//source/common/stats:thread_local_store_lib",
"//source/server/http:admin_lib",
"//test/mocks/runtime:runtime_mocks",
Expand Down
Loading