blob: 2516ab17ca297e9a8c86796dc4cc69e929c1b24e [file] [log] [blame]
/*
* Copyright 2019 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.xds;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.ListValue;
import com.google.protobuf.NullValue;
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern;
import com.google.re2j.PatternSyntaxException;
import io.envoyproxy.envoy.type.v3.FractionalPercent;
import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
import io.grpc.EquivalentAddressGroup;
import io.grpc.xds.RouteMatch.FractionMatcher;
import io.grpc.xds.RouteMatch.HeaderMatcher;
import io.grpc.xds.RouteMatch.PathMatcher;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Defines gRPC data types for Envoy protobuf messages used in xDS protocol. Each data type has
* the same name as Envoy's corresponding protobuf message, but only with fields used by gRPC.
*
* <p>Each data type should define a {@code fromEnvoyProtoXXX} static method to convert an Envoy
* proto message to an instance of that data type.
*
* <p>For data types that need to be sent as protobuf messages, a {@code toEnvoyProtoXXX} instance
* method is defined to convert an instance to Envoy proto message.
*
* <p>Data conversion should follow the invariant: converted data is guaranteed to be valid for
* gRPC. If the protobuf message contains invalid data, the conversion should fail and no object
* should be instantiated.
*/
final class EnvoyProtoData {
// Prevent instantiation.
private EnvoyProtoData() {
}
static final class StructOrError<T> {
/**
* Returns a {@link StructOrError} for the successfully converted data object.
*/
static <T> StructOrError<T> fromStruct(T struct) {
return new StructOrError<>(struct);
}
/**
* Returns a {@link StructOrError} for the failure to convert the data object.
*/
static <T> StructOrError<T> fromError(String errorDetail) {
return new StructOrError<>(errorDetail);
}
private final String errorDetail;
private final T struct;
private StructOrError(T struct) {
this.struct = checkNotNull(struct, "struct");
this.errorDetail = null;
}
private StructOrError(String errorDetail) {
this.struct = null;
this.errorDetail = checkNotNull(errorDetail, "errorDetail");
}
/**
* Returns struct if exists, otherwise null.
*/
@Nullable
public T getStruct() {
return struct;
}
/**
* Returns error detail if exists, otherwise null.
*/
@Nullable
String getErrorDetail() {
return errorDetail;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StructOrError<?> that = (StructOrError<?>) o;
return Objects.equals(errorDetail, that.errorDetail) && Objects.equals(struct, that.struct);
}
@Override
public int hashCode() {
return Objects.hash(errorDetail, struct);
}
@Override
public String toString() {
if (struct != null) {
return MoreObjects.toStringHelper(this)
.add("struct", struct)
.toString();
} else {
assert errorDetail != null;
return MoreObjects.toStringHelper(this)
.add("error", errorDetail)
.toString();
}
}
}
/**
* See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Node}.
*/
public static final class Node {
private final String id;
private final String cluster;
@Nullable
private final Map<String, ?> metadata;
@Nullable
private final Locality locality;
private final List<Address> listeningAddresses;
private final String buildVersion;
private final String userAgentName;
@Nullable
private final String userAgentVersion;
private final List<String> clientFeatures;
private Node(
String id, String cluster, @Nullable Map<String, ?> metadata, @Nullable Locality locality,
List<Address> listeningAddresses, String buildVersion, String userAgentName,
@Nullable String userAgentVersion, List<String> clientFeatures) {
this.id = checkNotNull(id, "id");
this.cluster = checkNotNull(cluster, "cluster");
this.metadata = metadata;
this.locality = locality;
this.listeningAddresses = Collections.unmodifiableList(
checkNotNull(listeningAddresses, "listeningAddresses"));
this.buildVersion = checkNotNull(buildVersion, "buildVersion");
this.userAgentName = checkNotNull(userAgentName, "userAgentName");
this.userAgentVersion = userAgentVersion;
this.clientFeatures = Collections.unmodifiableList(
checkNotNull(clientFeatures, "clientFeatures"));
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("cluster", cluster)
.add("metadata", metadata)
.add("locality", locality)
.add("listeningAddresses", listeningAddresses)
.add("buildVersion", buildVersion)
.add("userAgentName", userAgentName)
.add("userAgentVersion", userAgentVersion)
.add("clientFeatures", clientFeatures)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Node node = (Node) o;
return Objects.equals(id, node.id)
&& Objects.equals(cluster, node.cluster)
&& Objects.equals(metadata, node.metadata)
&& Objects.equals(locality, node.locality)
&& Objects.equals(listeningAddresses, node.listeningAddresses)
&& Objects.equals(buildVersion, node.buildVersion)
&& Objects.equals(userAgentName, node.userAgentName)
&& Objects.equals(userAgentVersion, node.userAgentVersion)
&& Objects.equals(clientFeatures, node.clientFeatures);
}
@Override
public int hashCode() {
return Objects
.hash(id, cluster, metadata, locality, listeningAddresses, buildVersion, userAgentName,
userAgentVersion, clientFeatures);
}
static final class Builder {
private String id = "";
private String cluster = "";
@Nullable
private Map<String, ?> metadata;
@Nullable
private Locality locality;
// TODO(sanjaypujare): eliminate usage of listening_addresses field.
private final List<Address> listeningAddresses = new ArrayList<>();
private String buildVersion = "";
private String userAgentName = "";
@Nullable
private String userAgentVersion;
private final List<String> clientFeatures = new ArrayList<>();
private Builder() {
}
Builder setId(String id) {
this.id = checkNotNull(id, "id");
return this;
}
Builder setCluster(String cluster) {
this.cluster = checkNotNull(cluster, "cluster");
return this;
}
Builder setMetadata(Map<String, ?> metadata) {
this.metadata = checkNotNull(metadata, "metadata");
return this;
}
Builder setLocality(Locality locality) {
this.locality = checkNotNull(locality, "locality");
return this;
}
Builder addListeningAddresses(Address address) {
listeningAddresses.add(checkNotNull(address, "address"));
return this;
}
Builder setBuildVersion(String buildVersion) {
this.buildVersion = checkNotNull(buildVersion, "buildVersion");
return this;
}
Builder setUserAgentName(String userAgentName) {
this.userAgentName = checkNotNull(userAgentName, "userAgentName");
return this;
}
Builder setUserAgentVersion(String userAgentVersion) {
this.userAgentVersion = checkNotNull(userAgentVersion, "userAgentVersion");
return this;
}
Builder addClientFeatures(String clientFeature) {
this.clientFeatures.add(checkNotNull(clientFeature, "clientFeature"));
return this;
}
Node build() {
return new Node(
id, cluster, metadata, locality, listeningAddresses, buildVersion, userAgentName,
userAgentVersion, clientFeatures);
}
}
static Builder newBuilder() {
return new Builder();
}
Builder toBuilder() {
Builder builder = new Builder().setId(id).setCluster(cluster);
if (metadata != null) {
builder.setMetadata(metadata);
}
if (locality != null) {
builder.setLocality(locality);
}
builder.listeningAddresses.addAll(listeningAddresses);
return builder;
}
String getId() {
return id;
}
String getCluster() {
return cluster;
}
@Nullable
Map<String, ?> getMetadata() {
return metadata;
}
@Nullable
Locality getLocality() {
return locality;
}
List<Address> getListeningAddresses() {
return listeningAddresses;
}
@SuppressWarnings("deprecation")
@VisibleForTesting
public io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() {
io.envoyproxy.envoy.config.core.v3.Node.Builder builder =
io.envoyproxy.envoy.config.core.v3.Node.newBuilder();
builder.setId(id);
builder.setCluster(cluster);
if (metadata != null) {
Struct.Builder structBuilder = Struct.newBuilder();
for (Map.Entry<String, ?> entry : metadata.entrySet()) {
structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue()));
}
builder.setMetadata(structBuilder);
}
if (locality != null) {
builder.setLocality(locality.toEnvoyProtoLocality());
}
for (Address address : listeningAddresses) {
builder.addListeningAddresses(address.toEnvoyProtoAddress());
}
builder.setUserAgentName(userAgentName);
if (userAgentVersion != null) {
builder.setUserAgentVersion(userAgentVersion);
}
builder.addAllClientFeatures(clientFeatures);
return builder.build();
}
@SuppressWarnings("deprecation") // Deprecated v2 API setBuildVersion().
public io.envoyproxy.envoy.api.v2.core.Node toEnvoyProtoNodeV2() {
io.envoyproxy.envoy.api.v2.core.Node.Builder builder =
io.envoyproxy.envoy.api.v2.core.Node.newBuilder();
builder.setId(id);
builder.setCluster(cluster);
if (metadata != null) {
Struct.Builder structBuilder = Struct.newBuilder();
for (Map.Entry<String, ?> entry : metadata.entrySet()) {
structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue()));
}
builder.setMetadata(structBuilder);
}
if (locality != null) {
builder.setLocality(locality.toEnvoyProtoLocalityV2());
}
for (Address address : listeningAddresses) {
builder.addListeningAddresses(address.toEnvoyProtoAddressV2());
}
builder.setBuildVersion(buildVersion);
builder.setUserAgentName(userAgentName);
if (userAgentVersion != null) {
builder.setUserAgentVersion(userAgentVersion);
}
builder.addAllClientFeatures(clientFeatures);
return builder.build();
}
}
/**
* Converts Java representation of the given JSON value to protobuf's {@link
* com.google.protobuf.Value} representation.
*
* <p>The given {@code rawObject} must be a valid JSON value in Java representation, which is
* either a {@code Map<String, ?>}, {@code List<?>}, {@code String}, {@code Double}, {@code
* Boolean}, or {@code null}.
*/
private static Value convertToValue(Object rawObject) {
Value.Builder valueBuilder = Value.newBuilder();
if (rawObject == null) {
valueBuilder.setNullValue(NullValue.NULL_VALUE);
} else if (rawObject instanceof Double) {
valueBuilder.setNumberValue((Double) rawObject);
} else if (rawObject instanceof String) {
valueBuilder.setStringValue((String) rawObject);
} else if (rawObject instanceof Boolean) {
valueBuilder.setBoolValue((Boolean) rawObject);
} else if (rawObject instanceof Map) {
Struct.Builder structBuilder = Struct.newBuilder();
@SuppressWarnings("unchecked")
Map<String, ?> map = (Map<String, ?>) rawObject;
for (Map.Entry<String, ?> entry : map.entrySet()) {
structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue()));
}
valueBuilder.setStructValue(structBuilder);
} else if (rawObject instanceof List) {
ListValue.Builder listBuilder = ListValue.newBuilder();
List<?> list = (List<?>) rawObject;
for (Object obj : list) {
listBuilder.addValues(convertToValue(obj));
}
valueBuilder.setListValue(listBuilder);
}
return valueBuilder.build();
}
/**
* See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Address}.
*/
static final class Address {
private final String address;
private final int port;
Address(String address, int port) {
this.address = checkNotNull(address, "address");
this.port = port;
}
io.envoyproxy.envoy.config.core.v3.Address toEnvoyProtoAddress() {
return
io.envoyproxy.envoy.config.core.v3.Address.newBuilder().setSocketAddress(
io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder().setAddress(address)
.setPortValue(port)).build();
}
io.envoyproxy.envoy.api.v2.core.Address toEnvoyProtoAddressV2() {
return
io.envoyproxy.envoy.api.v2.core.Address.newBuilder().setSocketAddress(
io.envoyproxy.envoy.api.v2.core.SocketAddress.newBuilder().setAddress(address)
.setPortValue(port)).build();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("address", address)
.add("port", port)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Address address1 = (Address) o;
return port == address1.port && Objects.equals(address, address1.address);
}
@Override
public int hashCode() {
return Objects.hash(address, port);
}
}
/**
* See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Locality}.
*/
static final class Locality {
private final String region;
private final String zone;
private final String subZone;
Locality(@Nullable String region, @Nullable String zone, @Nullable String subZone) {
this.region = region == null ? "" : region;
this.zone = zone == null ? "" : zone;
this.subZone = subZone == null ? "" : subZone;
}
static Locality fromEnvoyProtoLocality(io.envoyproxy.envoy.config.core.v3.Locality locality) {
return new Locality(
/* region = */ locality.getRegion(),
/* zone = */ locality.getZone(),
/* subZone = */ locality.getSubZone());
}
@VisibleForTesting
static Locality fromEnvoyProtoLocalityV2(io.envoyproxy.envoy.api.v2.core.Locality locality) {
return new Locality(
/* region = */ locality.getRegion(),
/* zone = */ locality.getZone(),
/* subZone = */ locality.getSubZone());
}
io.envoyproxy.envoy.config.core.v3.Locality toEnvoyProtoLocality() {
return io.envoyproxy.envoy.config.core.v3.Locality.newBuilder()
.setRegion(region)
.setZone(zone)
.setSubZone(subZone)
.build();
}
io.envoyproxy.envoy.api.v2.core.Locality toEnvoyProtoLocalityV2() {
return io.envoyproxy.envoy.api.v2.core.Locality.newBuilder()
.setRegion(region)
.setZone(zone)
.setSubZone(subZone)
.build();
}
String getRegion() {
return region;
}
String getZone() {
return zone;
}
String getSubZone() {
return subZone;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Locality locality = (Locality) o;
return Objects.equals(region, locality.region)
&& Objects.equals(zone, locality.zone)
&& Objects.equals(subZone, locality.subZone);
}
@Override
public int hashCode() {
return Objects.hash(region, zone, subZone);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("region", region)
.add("zone", zone)
.add("subZone", subZone)
.toString();
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints}.
*/
static final class LocalityLbEndpoints {
private final List<LbEndpoint> endpoints;
private final int localityWeight;
private final int priority;
/** Must only be used for testing. */
@VisibleForTesting
LocalityLbEndpoints(List<LbEndpoint> endpoints, int localityWeight, int priority) {
this.endpoints = endpoints;
this.localityWeight = localityWeight;
this.priority = priority;
}
static LocalityLbEndpoints fromEnvoyProtoLocalityLbEndpoints(
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto) {
List<LbEndpoint> endpoints = new ArrayList<>(proto.getLbEndpointsCount());
for (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint :
proto.getLbEndpointsList()) {
endpoints.add(LbEndpoint.fromEnvoyProtoLbEndpoint(endpoint));
}
return
new LocalityLbEndpoints(
endpoints,
proto.getLoadBalancingWeight().getValue(),
proto.getPriority());
}
@VisibleForTesting
static LocalityLbEndpoints fromEnvoyProtoLocalityLbEndpointsV2(
io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints proto) {
List<LbEndpoint> endpoints = new ArrayList<>(proto.getLbEndpointsCount());
for (io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint endpoint : proto.getLbEndpointsList()) {
endpoints.add(LbEndpoint.fromEnvoyProtoLbEndpointV2(endpoint));
}
return
new LocalityLbEndpoints(
endpoints,
proto.getLoadBalancingWeight().getValue(),
proto.getPriority());
}
List<LbEndpoint> getEndpoints() {
return Collections.unmodifiableList(endpoints);
}
int getLocalityWeight() {
return localityWeight;
}
int getPriority() {
return priority;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LocalityLbEndpoints that = (LocalityLbEndpoints) o;
return localityWeight == that.localityWeight
&& priority == that.priority
&& Objects.equals(endpoints, that.endpoints);
}
@Override
public int hashCode() {
return Objects.hash(endpoints, localityWeight, priority);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("endpoints", endpoints)
.add("localityWeight", localityWeight)
.add("priority", priority)
.toString();
}
}
/**
* See corresponding Envoy proto message
* {@link io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint}.
*/
static final class LbEndpoint {
private final EquivalentAddressGroup eag;
private final int loadBalancingWeight;
private final boolean isHealthy;
@VisibleForTesting
LbEndpoint(String address, int port, int loadBalancingWeight, boolean isHealthy) {
this(
new EquivalentAddressGroup(
new InetSocketAddress(address, port)),
loadBalancingWeight, isHealthy);
}
@VisibleForTesting
LbEndpoint(EquivalentAddressGroup eag, int loadBalancingWeight, boolean isHealthy) {
this.eag = eag;
this.loadBalancingWeight = loadBalancingWeight;
this.isHealthy = isHealthy;
}
static LbEndpoint fromEnvoyProtoLbEndpoint(
io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint proto) {
io.envoyproxy.envoy.config.core.v3.SocketAddress socketAddress =
proto.getEndpoint().getAddress().getSocketAddress();
InetSocketAddress addr =
new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
return new LbEndpoint(
new EquivalentAddressGroup(ImmutableList.<java.net.SocketAddress>of(addr)),
proto.getLoadBalancingWeight().getValue(),
proto.getHealthStatus() == io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY
|| proto.getHealthStatus()
== io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN);
}
private static LbEndpoint fromEnvoyProtoLbEndpointV2(
io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint proto) {
io.envoyproxy.envoy.api.v2.core.SocketAddress socketAddress =
proto.getEndpoint().getAddress().getSocketAddress();
InetSocketAddress addr =
new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
return new LbEndpoint(
new EquivalentAddressGroup(ImmutableList.<java.net.SocketAddress>of(addr)),
proto.getLoadBalancingWeight().getValue(),
proto.getHealthStatus() == io.envoyproxy.envoy.api.v2.core.HealthStatus.HEALTHY
|| proto.getHealthStatus() == io.envoyproxy.envoy.api.v2.core.HealthStatus.UNKNOWN);
}
EquivalentAddressGroup getAddress() {
return eag;
}
int getLoadBalancingWeight() {
return loadBalancingWeight;
}
boolean isHealthy() {
return isHealthy;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LbEndpoint that = (LbEndpoint) o;
return loadBalancingWeight == that.loadBalancingWeight
&& Objects.equals(eag, that.eag)
&& isHealthy == that.isHealthy;
}
@Override
public int hashCode() {
return Objects.hash(eag, loadBalancingWeight, isHealthy);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("eag", eag)
.add("loadBalancingWeight", loadBalancingWeight)
.add("isHealthy", isHealthy)
.toString();
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload}.
*/
static final class DropOverload {
private final String category;
private final int dropsPerMillion;
/** Must only be used for testing. */
@VisibleForTesting
DropOverload(String category, int dropsPerMillion) {
this.category = category;
this.dropsPerMillion = dropsPerMillion;
}
static DropOverload fromEnvoyProtoDropOverload(
io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload proto) {
FractionalPercent percent = proto.getDropPercentage();
int numerator = percent.getNumerator();
DenominatorType type = percent.getDenominator();
switch (type) {
case TEN_THOUSAND:
numerator *= 100;
break;
case HUNDRED:
numerator *= 10_000;
break;
case MILLION:
break;
case UNRECOGNIZED:
default:
throw new IllegalArgumentException("Unknown denominator type of " + percent);
}
if (numerator > 1_000_000) {
numerator = 1_000_000;
}
return new DropOverload(proto.getCategory(), numerator);
}
@VisibleForTesting
static DropOverload fromEnvoyProtoDropOverloadV2(
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload proto) {
io.envoyproxy.envoy.type.FractionalPercent percent = proto.getDropPercentage();
int numerator = percent.getNumerator();
io.envoyproxy.envoy.type.FractionalPercent.DenominatorType type = percent.getDenominator();
switch (type) {
case TEN_THOUSAND:
numerator *= 100;
break;
case HUNDRED:
numerator *= 10_000;
break;
case MILLION:
break;
case UNRECOGNIZED:
default:
throw new IllegalArgumentException("Unknown denominator type of " + percent);
}
if (numerator > 1_000_000) {
numerator = 1_000_000;
}
return new DropOverload(proto.getCategory(), numerator);
}
String getCategory() {
return category;
}
int getDropsPerMillion() {
return dropsPerMillion;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DropOverload that = (DropOverload) o;
return dropsPerMillion == that.dropsPerMillion && Objects.equals(category, that.category);
}
@Override
public int hashCode() {
return Objects.hash(category, dropsPerMillion);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("category", category)
.add("dropsPerMillion", dropsPerMillion)
.toString();
}
}
/** See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.route.v3.Route}. */
static final class Route {
private final RouteMatch routeMatch;
private final RouteAction routeAction;
@VisibleForTesting
Route(RouteMatch routeMatch, @Nullable RouteAction routeAction) {
this.routeMatch = routeMatch;
this.routeAction = routeAction;
}
RouteMatch getRouteMatch() {
return routeMatch;
}
RouteAction getRouteAction() {
return routeAction;
}
// TODO(chengyuanzhang): delete and do not use after routing feature is always ON.
boolean isDefaultRoute() {
// For backward compatibility, all the other matchers are ignored.
String prefix = routeMatch.getPathMatch().getPrefix();
if (prefix != null) {
return prefix.isEmpty() || prefix.equals("/");
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Route route = (Route) o;
return Objects.equals(routeMatch, route.routeMatch)
&& Objects.equals(routeAction, route.routeAction);
}
@Override
public int hashCode() {
return Objects.hash(routeMatch, routeAction);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("routeMatch", routeMatch)
.add("routeAction", routeAction)
.toString();
}
@Nullable
static StructOrError<Route> fromEnvoyProtoRoute(
io.envoyproxy.envoy.config.route.v3.Route proto) {
StructOrError<RouteMatch> routeMatch = convertEnvoyProtoRouteMatch(proto.getMatch());
if (routeMatch == null) {
return null;
}
if (routeMatch.getErrorDetail() != null) {
return StructOrError.fromError(
"Invalid route [" + proto.getName() + "]: " + routeMatch.getErrorDetail());
}
StructOrError<RouteAction> routeAction;
switch (proto.getActionCase()) {
case ROUTE:
routeAction = RouteAction.fromEnvoyProtoRouteAction(proto.getRoute());
break;
case REDIRECT:
return StructOrError.fromError("Unsupported action type: redirect");
case DIRECT_RESPONSE:
return StructOrError.fromError("Unsupported action type: direct_response");
case FILTER_ACTION:
return StructOrError.fromError("Unsupported action type: filter_action");
case ACTION_NOT_SET:
default:
return StructOrError.fromError("Unknown action type: " + proto.getActionCase());
}
if (routeAction == null) {
return null;
}
if (routeAction.getErrorDetail() != null) {
return StructOrError.fromError(
"Invalid route [" + proto.getName() + "]: " + routeAction.getErrorDetail());
}
return StructOrError.fromStruct(new Route(routeMatch.getStruct(), routeAction.getStruct()));
}
@VisibleForTesting
@SuppressWarnings("deprecation")
@Nullable
static StructOrError<RouteMatch> convertEnvoyProtoRouteMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch proto) {
if (proto.getQueryParametersCount() != 0) {
return null;
}
if (proto.hasCaseSensitive() && !proto.getCaseSensitive().getValue()) {
return StructOrError.fromError("Unsupported match option: case insensitive");
}
StructOrError<PathMatcher> pathMatch = convertEnvoyProtoPathMatcher(proto);
if (pathMatch.getErrorDetail() != null) {
return StructOrError.fromError(pathMatch.getErrorDetail());
}
FractionMatcher fractionMatch = null;
if (proto.hasRuntimeFraction()) {
StructOrError<FractionMatcher> parsedFraction =
convertEnvoyProtoFraction(proto.getRuntimeFraction().getDefaultValue());
if (parsedFraction.getErrorDetail() != null) {
return StructOrError.fromError(parsedFraction.getErrorDetail());
}
fractionMatch = parsedFraction.getStruct();
}
List<HeaderMatcher> headerMatchers = new ArrayList<>();
for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) {
StructOrError<HeaderMatcher> headerMatcher = convertEnvoyProtoHeaderMatcher(hmProto);
if (headerMatcher.getErrorDetail() != null) {
return StructOrError.fromError(headerMatcher.getErrorDetail());
}
headerMatchers.add(headerMatcher.getStruct());
}
return StructOrError.fromStruct(
new RouteMatch(
pathMatch.getStruct(), Collections.unmodifiableList(headerMatchers), fractionMatch));
}
@SuppressWarnings("deprecation")
private static StructOrError<PathMatcher> convertEnvoyProtoPathMatcher(
io.envoyproxy.envoy.config.route.v3.RouteMatch proto) {
String path = null;
String prefix = null;
Pattern safeRegEx = null;
switch (proto.getPathSpecifierCase()) {
case PREFIX:
prefix = proto.getPrefix();
break;
case PATH:
path = proto.getPath();
break;
case SAFE_REGEX:
String rawPattern = proto.getSafeRegex().getRegex();
try {
safeRegEx = Pattern.compile(rawPattern);
} catch (PatternSyntaxException e) {
return StructOrError.fromError("Malformed safe regex pattern: " + e.getMessage());
}
break;
case PATHSPECIFIER_NOT_SET:
default:
return StructOrError.fromError("Unknown path match type");
}
return StructOrError.fromStruct(new PathMatcher(path, prefix, safeRegEx));
}
private static StructOrError<FractionMatcher> convertEnvoyProtoFraction(
io.envoyproxy.envoy.type.v3.FractionalPercent proto) {
int numerator = proto.getNumerator();
int denominator = 0;
switch (proto.getDenominator()) {
case HUNDRED:
denominator = 100;
break;
case TEN_THOUSAND:
denominator = 10_000;
break;
case MILLION:
denominator = 1_000_000;
break;
case UNRECOGNIZED:
default:
return StructOrError.fromError(
"Unrecognized fractional percent denominator: " + proto.getDenominator());
}
return StructOrError.fromStruct(new FractionMatcher(numerator, denominator));
}
@VisibleForTesting
@SuppressWarnings("deprecation")
static StructOrError<HeaderMatcher> convertEnvoyProtoHeaderMatcher(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
String exactMatch = null;
Pattern safeRegExMatch = null;
HeaderMatcher.Range rangeMatch = null;
Boolean presentMatch = null;
String prefixMatch = null;
String suffixMatch = null;
switch (proto.getHeaderMatchSpecifierCase()) {
case EXACT_MATCH:
exactMatch = proto.getExactMatch();
break;
case SAFE_REGEX_MATCH:
String rawPattern = proto.getSafeRegexMatch().getRegex();
try {
safeRegExMatch = Pattern.compile(rawPattern);
} catch (PatternSyntaxException e) {
return StructOrError.fromError(
"HeaderMatcher [" + proto.getName() + "] contains malformed safe regex pattern: "
+ e.getMessage());
}
break;
case RANGE_MATCH:
rangeMatch =
new HeaderMatcher.Range(
proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd());
break;
case PRESENT_MATCH:
presentMatch = proto.getPresentMatch();
break;
case PREFIX_MATCH:
prefixMatch = proto.getPrefixMatch();
break;
case SUFFIX_MATCH:
suffixMatch = proto.getSuffixMatch();
break;
case HEADERMATCHSPECIFIER_NOT_SET:
default:
return StructOrError.fromError("Unknown header matcher type");
}
return StructOrError.fromStruct(
new HeaderMatcher(
proto.getName(), exactMatch, safeRegExMatch, rangeMatch, presentMatch,
prefixMatch, suffixMatch, proto.getInvertMatch()));
}
}
/**
* See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.route.v3.RouteAction}.
*/
static final class RouteAction {
private final long timeoutNano;
// Exactly one of the following fields is non-null.
@Nullable
private final String cluster;
@Nullable
private final List<ClusterWeight> weightedClusters;
@VisibleForTesting
RouteAction(
long timeoutNano,
@Nullable String cluster,
@Nullable List<ClusterWeight> weightedClusters) {
this.timeoutNano = timeoutNano;
this.cluster = cluster;
this.weightedClusters = weightedClusters;
}
Long getTimeoutNano() {
return timeoutNano;
}
@Nullable
String getCluster() {
return cluster;
}
@Nullable
List<ClusterWeight> getWeightedCluster() {
return weightedClusters;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RouteAction that = (RouteAction) o;
return Objects.equals(timeoutNano, that.timeoutNano)
&& Objects.equals(cluster, that.cluster)
&& Objects.equals(weightedClusters, that.weightedClusters);
}
@Override
public int hashCode() {
return Objects.hash(timeoutNano, cluster, weightedClusters);
}
@Override
public String toString() {
ToStringHelper toStringHelper = MoreObjects.toStringHelper(this);
toStringHelper.add("timeout", timeoutNano + "ns");
if (cluster != null) {
toStringHelper.add("cluster", cluster);
}
if (weightedClusters != null) {
toStringHelper.add("weightedClusters", weightedClusters);
}
return toStringHelper.toString();
}
@Nullable
@VisibleForTesting
static StructOrError<RouteAction> fromEnvoyProtoRouteAction(
io.envoyproxy.envoy.config.route.v3.RouteAction proto) {
String cluster = null;
List<ClusterWeight> weightedClusters = null;
switch (proto.getClusterSpecifierCase()) {
case CLUSTER:
cluster = proto.getCluster();
break;
case CLUSTER_HEADER:
return null;
case WEIGHTED_CLUSTERS:
List<io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight> clusterWeights
= proto.getWeightedClusters().getClustersList();
if (clusterWeights.isEmpty()) {
return StructOrError.fromError("No cluster found in weighted cluster list");
}
weightedClusters = new ArrayList<>();
for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight
: clusterWeights) {
weightedClusters.add(ClusterWeight.fromEnvoyProtoClusterWeight(clusterWeight));
}
// TODO(chengyuanzhang): validate if the sum of weights equals to total weight.
break;
case CLUSTERSPECIFIER_NOT_SET:
default:
return StructOrError.fromError(
"Unknown cluster specifier: " + proto.getClusterSpecifierCase());
}
long timeoutNano = TimeUnit.SECONDS.toNanos(15L); // default 15s
if (proto.hasMaxGrpcTimeout()) {
timeoutNano = Durations.toNanos(proto.getMaxGrpcTimeout());
} else if (proto.hasTimeout()) {
timeoutNano = Durations.toNanos(proto.getTimeout());
}
if (timeoutNano == 0) {
timeoutNano = Long.MAX_VALUE;
}
return StructOrError.fromStruct(new RouteAction(timeoutNano, cluster, weightedClusters));
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight}.
*/
static final class ClusterWeight {
private final String name;
private final int weight;
@VisibleForTesting
ClusterWeight(String name, int weight) {
this.name = name;
this.weight = weight;
}
String getName() {
return name;
}
int getWeight() {
return weight;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClusterWeight that = (ClusterWeight) o;
return weight == that.weight && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, weight);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("weight", weight)
.toString();
}
@VisibleForTesting
static ClusterWeight fromEnvoyProtoClusterWeight(
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto) {
return new ClusterWeight(proto.getName(), proto.getWeight().getValue());
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.endpoint.v3.ClusterStats}.
*/
static final class ClusterStats {
private final String clusterName;
@Nullable
private final String clusterServiceName;
private final List<UpstreamLocalityStats> upstreamLocalityStatsList;
private final List<DroppedRequests> droppedRequestsList;
private final long totalDroppedRequests;
private final long loadReportIntervalNanos;
private ClusterStats(
String clusterName,
@Nullable String clusterServiceName,
List<UpstreamLocalityStats> upstreamLocalityStatsList,
List<DroppedRequests> droppedRequestsList,
long totalDroppedRequests,
long loadReportIntervalNanos) {
this.clusterName = checkNotNull(clusterName, "clusterName");
this.clusterServiceName = clusterServiceName;
this.upstreamLocalityStatsList = Collections.unmodifiableList(
checkNotNull(upstreamLocalityStatsList, "upstreamLocalityStatsList"));
this.droppedRequestsList = Collections.unmodifiableList(
checkNotNull(droppedRequestsList, "dropRequestsList"));
this.totalDroppedRequests = totalDroppedRequests;
this.loadReportIntervalNanos = loadReportIntervalNanos;
}
String getClusterName() {
return clusterName;
}
@Nullable
String getClusterServiceName() {
return clusterServiceName;
}
List<UpstreamLocalityStats> getUpstreamLocalityStatsList() {
return upstreamLocalityStatsList;
}
List<DroppedRequests> getDroppedRequestsList() {
return droppedRequestsList;
}
long getTotalDroppedRequests() {
return totalDroppedRequests;
}
long getLoadReportIntervalNanos() {
return loadReportIntervalNanos;
}
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats toEnvoyProtoClusterStats() {
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.Builder builder =
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder()
.setClusterName(clusterName);
if (clusterServiceName != null) {
builder.setClusterServiceName(clusterServiceName);
}
for (UpstreamLocalityStats upstreamLocalityStats : upstreamLocalityStatsList) {
builder.addUpstreamLocalityStats(upstreamLocalityStats.toEnvoyProtoUpstreamLocalityStats());
}
for (DroppedRequests droppedRequests : droppedRequestsList) {
builder.addDroppedRequests(droppedRequests.toEnvoyProtoDroppedRequests());
}
return builder
.setTotalDroppedRequests(totalDroppedRequests)
.setLoadReportInterval(Durations.fromNanos(loadReportIntervalNanos))
.build();
}
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats toEnvoyProtoClusterStatsV2() {
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.Builder builder =
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder()
.setClusterName(clusterName);
for (UpstreamLocalityStats upstreamLocalityStats : upstreamLocalityStatsList) {
builder.addUpstreamLocalityStats(
upstreamLocalityStats.toEnvoyProtoUpstreamLocalityStatsV2());
}
for (DroppedRequests droppedRequests : droppedRequestsList) {
builder.addDroppedRequests(droppedRequests.toEnvoyProtoDroppedRequestsV2());
}
return builder
.setTotalDroppedRequests(totalDroppedRequests)
.setLoadReportInterval(Durations.fromNanos(loadReportIntervalNanos))
.build();
}
@VisibleForTesting
Builder toBuilder() {
Builder builder = new Builder()
.setClusterName(clusterName)
.setTotalDroppedRequests(totalDroppedRequests)
.setLoadReportIntervalNanos(loadReportIntervalNanos);
if (clusterServiceName != null) {
builder.setClusterServiceName(clusterServiceName);
}
for (UpstreamLocalityStats upstreamLocalityStats : upstreamLocalityStatsList) {
builder.addUpstreamLocalityStats(upstreamLocalityStats);
}
for (DroppedRequests droppedRequests : droppedRequestsList) {
builder.addDroppedRequests(droppedRequests);
}
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClusterStats that = (ClusterStats) o;
return totalDroppedRequests == that.totalDroppedRequests
&& loadReportIntervalNanos == that.loadReportIntervalNanos
&& Objects.equals(clusterName, that.clusterName)
&& Objects.equals(clusterServiceName, that.clusterServiceName)
&& Objects.equals(upstreamLocalityStatsList, that.upstreamLocalityStatsList)
&& Objects.equals(droppedRequestsList, that.droppedRequestsList);
}
@Override
public int hashCode() {
return Objects.hash(
clusterName, clusterServiceName, upstreamLocalityStatsList, droppedRequestsList,
totalDroppedRequests, loadReportIntervalNanos);
}
static Builder newBuilder() {
return new Builder();
}
static final class Builder {
private String clusterName;
private String clusterServiceName;
private final List<UpstreamLocalityStats> upstreamLocalityStatsList = new ArrayList<>();
private final List<DroppedRequests> droppedRequestsList = new ArrayList<>();
private long totalDroppedRequests;
private long loadReportIntervalNanos;
private Builder() {
}
Builder setClusterName(String clusterName) {
this.clusterName = checkNotNull(clusterName, "clusterName");
return this;
}
Builder setClusterServiceName(String clusterServiceName) {
this.clusterServiceName = checkNotNull(clusterServiceName, "clusterServiceName");
return this;
}
Builder setTotalDroppedRequests(long totalDroppedRequests) {
this.totalDroppedRequests = totalDroppedRequests;
return this;
}
Builder setLoadReportIntervalNanos(long loadReportIntervalNanos) {
this.loadReportIntervalNanos = loadReportIntervalNanos;
return this;
}
Builder addUpstreamLocalityStats(UpstreamLocalityStats upstreamLocalityStats) {
upstreamLocalityStatsList.add(checkNotNull(upstreamLocalityStats, "upstreamLocalityStats"));
return this;
}
Builder addAllUpstreamLocalityStats(Collection<UpstreamLocalityStats> upstreamLocalityStats) {
upstreamLocalityStatsList.addAll(upstreamLocalityStats);
return this;
}
Builder addDroppedRequests(DroppedRequests droppedRequests) {
droppedRequestsList.add(checkNotNull(droppedRequests, "dropRequests"));
return this;
}
ClusterStats build() {
return new ClusterStats(
clusterName, clusterServiceName,upstreamLocalityStatsList, droppedRequestsList,
totalDroppedRequests, loadReportIntervalNanos);
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests}.
*/
static final class DroppedRequests {
private final String category;
private final long droppedCount;
DroppedRequests(String category, long droppedCount) {
this.category = checkNotNull(category, "category");
this.droppedCount = droppedCount;
}
String getCategory() {
return category;
}
long getDroppedCount() {
return droppedCount;
}
private io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests
toEnvoyProtoDroppedRequests() {
return io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder()
.setCategory(category)
.setDroppedCount(droppedCount)
.build();
}
private io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests
toEnvoyProtoDroppedRequestsV2() {
return io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder()
.setCategory(category)
.setDroppedCount(droppedCount)
.build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DroppedRequests that = (DroppedRequests) o;
return droppedCount == that.droppedCount && Objects.equals(category, that.category);
}
@Override
public int hashCode() {
return Objects.hash(category, droppedCount);
}
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats}.
*/
static final class UpstreamLocalityStats {
private final Locality locality;
private final long totalSuccessfulRequests;
private final long totalErrorRequests;
private final long totalRequestsInProgress;
private final long totalIssuedRequests;
private final List<EndpointLoadMetricStats> loadMetricStatsList;
private UpstreamLocalityStats(
Locality locality,
long totalSuccessfulRequests,
long totalErrorRequests,
long totalRequestsInProgress,
long totalIssuedRequests,
List<EndpointLoadMetricStats> loadMetricStatsList) {
this.locality = checkNotNull(locality, "locality");
this.totalSuccessfulRequests = totalSuccessfulRequests;
this.totalErrorRequests = totalErrorRequests;
this.totalRequestsInProgress = totalRequestsInProgress;
this.totalIssuedRequests = totalIssuedRequests;
this.loadMetricStatsList = Collections.unmodifiableList(
checkNotNull(loadMetricStatsList, "loadMetricStatsList"));
}
Locality getLocality() {
return locality;
}
long getTotalSuccessfulRequests() {
return totalSuccessfulRequests;
}
long getTotalErrorRequests() {
return totalErrorRequests;
}
long getTotalRequestsInProgress() {
return totalRequestsInProgress;
}
long getTotalIssuedRequests() {
return totalIssuedRequests;
}
List<EndpointLoadMetricStats> getLoadMetricStatsList() {
return loadMetricStatsList;
}
private io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats
toEnvoyProtoUpstreamLocalityStats() {
io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.Builder builder
= io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder()
.setLocality(locality.toEnvoyProtoLocality())
.setTotalSuccessfulRequests(totalSuccessfulRequests)
.setTotalErrorRequests(totalErrorRequests)
.setTotalRequestsInProgress(totalRequestsInProgress)
.setTotalIssuedRequests(totalIssuedRequests);
for (EndpointLoadMetricStats endpointLoadMetricStats : loadMetricStatsList) {
builder.addLoadMetricStats(endpointLoadMetricStats.toEnvoyProtoEndpointLoadMetricStats());
}
return builder.build();
}
private io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats
toEnvoyProtoUpstreamLocalityStatsV2() {
io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.Builder builder
= io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder()
.setLocality(locality.toEnvoyProtoLocalityV2())
.setTotalSuccessfulRequests(totalSuccessfulRequests)
.setTotalErrorRequests(totalErrorRequests)
.setTotalRequestsInProgress(totalRequestsInProgress)
.setTotalIssuedRequests(totalIssuedRequests);
for (EndpointLoadMetricStats endpointLoadMetricStats : loadMetricStatsList) {
builder.addLoadMetricStats(endpointLoadMetricStats.toEnvoyProtoEndpointLoadMetricStatsV2());
}
return builder.build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UpstreamLocalityStats that = (UpstreamLocalityStats) o;
return totalSuccessfulRequests == that.totalSuccessfulRequests
&& totalErrorRequests == that.totalErrorRequests
&& totalRequestsInProgress == that.totalRequestsInProgress
&& totalIssuedRequests == that.totalIssuedRequests
&& Objects.equals(locality, that.locality)
&& Objects.equals(loadMetricStatsList, that.loadMetricStatsList);
}
@Override
public int hashCode() {
return Objects.hash(
locality, totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress,
totalIssuedRequests, loadMetricStatsList);
}
static Builder newBuilder() {
return new Builder();
}
static final class Builder {
private Locality locality;
private long totalSuccessfulRequests;
private long totalErrorRequests;
private long totalRequestsInProgress;
private long totalIssuedRequests;
private final List<EndpointLoadMetricStats> loadMetricStatsList = new ArrayList<>();
private Builder() {
}
Builder setLocality(Locality locality) {
this.locality = checkNotNull(locality, "locality");
return this;
}
Builder setTotalSuccessfulRequests(long totalSuccessfulRequests) {
this.totalSuccessfulRequests = totalSuccessfulRequests;
return this;
}
Builder setTotalErrorRequests(long totalErrorRequests) {
this.totalErrorRequests = totalErrorRequests;
return this;
}
Builder setTotalRequestsInProgress(long totalRequestsInProgress) {
this.totalRequestsInProgress = totalRequestsInProgress;
return this;
}
Builder setTotalIssuedRequests(long totalIssuedRequests) {
this.totalIssuedRequests = totalIssuedRequests;
return this;
}
Builder addLoadMetricStats(EndpointLoadMetricStats endpointLoadMetricStats) {
loadMetricStatsList.add(checkNotNull(endpointLoadMetricStats, "endpointLoadMetricStats"));
return this;
}
Builder addAllLoadMetricStats(Collection<EndpointLoadMetricStats> endpointLoadMetricStats) {
loadMetricStatsList.addAll(
checkNotNull(endpointLoadMetricStats, "endpointLoadMetricStats"));
return this;
}
UpstreamLocalityStats build() {
return new UpstreamLocalityStats(
locality, totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress,
totalIssuedRequests, loadMetricStatsList);
}
}
}
/**
* See corresponding Envoy proto message {@link
* io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats}.
*/
static final class EndpointLoadMetricStats {
private final String metricName;
private final long numRequestsFinishedWithMetric;
private final double totalMetricValue;
private EndpointLoadMetricStats(String metricName, long numRequestsFinishedWithMetric,
double totalMetricValue) {
this.metricName = checkNotNull(metricName, "metricName");
this.numRequestsFinishedWithMetric = numRequestsFinishedWithMetric;
this.totalMetricValue = totalMetricValue;
}
String getMetricName() {
return metricName;
}
long getNumRequestsFinishedWithMetric() {
return numRequestsFinishedWithMetric;
}
double getTotalMetricValue() {
return totalMetricValue;
}
private io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats
toEnvoyProtoEndpointLoadMetricStats() {
return io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats.newBuilder()
.setMetricName(metricName)
.setNumRequestsFinishedWithMetric(numRequestsFinishedWithMetric)
.setTotalMetricValue(totalMetricValue)
.build();
}
private io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats
toEnvoyProtoEndpointLoadMetricStatsV2() {
return io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats.newBuilder()
.setMetricName(metricName)
.setNumRequestsFinishedWithMetric(numRequestsFinishedWithMetric)
.setTotalMetricValue(totalMetricValue)
.build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EndpointLoadMetricStats that = (EndpointLoadMetricStats) o;
return numRequestsFinishedWithMetric == that.numRequestsFinishedWithMetric
&& Double.compare(that.totalMetricValue, totalMetricValue) == 0
&& Objects.equals(metricName, that.metricName);
}
@Override
public int hashCode() {
return Objects.hash(metricName, numRequestsFinishedWithMetric, totalMetricValue);
}
static Builder newBuilder() {
return new Builder();
}
static final class Builder {
private String metricName;
private long numRequestsFinishedWithMetric;
private double totalMetricValue;
private Builder() {
}
Builder setMetricName(String metricName) {
this.metricName = checkNotNull(metricName, "metricName");
return this;
}
Builder setNumRequestsFinishedWithMetric(long numRequestsFinishedWithMetric) {
this.numRequestsFinishedWithMetric = numRequestsFinishedWithMetric;
return this;
}
Builder setTotalMetricValue(double totalMetricValue) {
this.totalMetricValue = totalMetricValue;
return this;
}
EndpointLoadMetricStats build() {
return new EndpointLoadMetricStats(
metricName, numRequestsFinishedWithMetric, totalMetricValue);
}
}
}
}