diff --git a/it/xds-client/src/test/java/com/linecorp/armeria/xds/it/ErrorHandlingTest.java b/it/xds-client/src/test/java/com/linecorp/armeria/xds/it/ErrorHandlingTest.java index e04aa9372ef..5ccc9d406a7 100644 --- a/it/xds-client/src/test/java/com/linecorp/armeria/xds/it/ErrorHandlingTest.java +++ b/it/xds-client/src/test/java/com/linecorp/armeria/xds/it/ErrorHandlingTest.java @@ -49,6 +49,7 @@ import com.linecorp.armeria.xds.XdsBootstrap; import com.linecorp.armeria.xds.XdsResourceException; import com.linecorp.armeria.xds.XdsType; +import com.linecorp.armeria.xds.validator.XdsValidationException; import io.envoyproxy.controlplane.cache.v3.SimpleCache; import io.envoyproxy.controlplane.cache.v3.Snapshot; @@ -366,7 +367,7 @@ void staticResourceValidationFailure(String bootstrapYaml, String errorMsg) thro try (XdsBootstrap xdsBootstrap = XdsBootstrap.of(bootstrap)) { // do nothing } - }).isInstanceOf(IllegalArgumentException.class) + }).isInstanceOf(XdsValidationException.class) .hasMessageContaining(errorMsg); } @@ -561,7 +562,7 @@ public void onUpdate(@Nullable Object snapshot, @Nullable Throwable t) { assertThat(xdsResourceException.type()).isEqualTo(type); assertThat(xdsResourceException.name()).isEqualTo(name); assertThat(xdsResourceException).cause() - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(XdsValidationException.class) .cause() .isInstanceOf(ValidationException.class) .hasMessageContaining(errorMsg); @@ -619,7 +620,7 @@ public void onUpdate(@Nullable Object snapshot, @Nullable Throwable t) { assertThat(xdsResourceException.type()).isEqualTo(type); assertThat(xdsResourceException.name()).isEqualTo(name); assertThat(xdsResourceException).cause() - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(XdsValidationException.class) .cause() .isInstanceOf(ValidationException.class) .hasMessageContaining(errorMsg); diff --git a/it/xds-client/src/test/resources/META-INF/services/com.linecorp.armeria.xds.validator.XdsValidatorIndex b/it/xds-client/src/test/resources/META-INF/services/com.linecorp.armeria.xds.validator.XdsValidatorIndex new file mode 100644 index 00000000000..910a54a3784 --- /dev/null +++ b/it/xds-client/src/test/resources/META-INF/services/com.linecorp.armeria.xds.validator.XdsValidatorIndex @@ -0,0 +1 @@ +com.linecorp.armeria.xds.api.StrictXdsValidatorIndex diff --git a/it/xds-no-validation/src/test/java/com/linecorp/armeria/xds/it/NoopXdsValidatorIndex.java b/it/xds-no-validation/src/test/java/com/linecorp/armeria/xds/it/NoopXdsValidatorIndex.java index a7497f9b15b..5f7d2ad18e0 100644 --- a/it/xds-no-validation/src/test/java/com/linecorp/armeria/xds/it/NoopXdsValidatorIndex.java +++ b/it/xds-no-validation/src/test/java/com/linecorp/armeria/xds/it/NoopXdsValidatorIndex.java @@ -16,11 +16,14 @@ package com.linecorp.armeria.xds.it; +import com.google.protobuf.Message; + import com.linecorp.armeria.xds.validator.XdsValidatorIndex; public class NoopXdsValidatorIndex implements XdsValidatorIndex { + @Override - public void assertValid(Object message) { + public void assertValid(Message message) { } @Override diff --git a/xds-api/src/main/java/com/linecorp/armeria/xds/api/DefaultXdsValidatorIndex.java b/xds-api/src/main/java/com/linecorp/armeria/xds/api/DefaultXdsValidatorIndex.java index dfb78cf1f71..b07981348b3 100644 --- a/xds-api/src/main/java/com/linecorp/armeria/xds/api/DefaultXdsValidatorIndex.java +++ b/xds-api/src/main/java/com/linecorp/armeria/xds/api/DefaultXdsValidatorIndex.java @@ -16,40 +16,36 @@ package com.linecorp.armeria.xds.api; +import static java.util.Objects.requireNonNull; + +import com.google.protobuf.Message; + import com.linecorp.armeria.common.annotation.UnstableApi; import com.linecorp.armeria.xds.validator.XdsValidatorIndex; -import io.envoyproxy.pgv.ReflectiveValidatorIndex; -import io.envoyproxy.pgv.ValidationException; -import io.envoyproxy.pgv.Validator; -import io.envoyproxy.pgv.ValidatorIndex; - /** - * The default validator which uses reflection in conjunction with pgv to validate messages. + * The default validator which applies pgv (protoc-gen-validate) structural validation + * and warns usage of unsupported fields. */ @UnstableApi -public final class DefaultXdsValidatorIndex implements ValidatorIndex, XdsValidatorIndex { +public final class DefaultXdsValidatorIndex implements XdsValidatorIndex { + + private static final DefaultXdsValidatorIndex INSTANCE = new DefaultXdsValidatorIndex(); - private final ValidatorIndex delegate; + private final PgvValidator pgvValidator = PgvValidator.of(); + private final SupportedFieldValidator supportedFieldValidator = SupportedFieldValidator.of(); /** - * Creates a validator. + * Returns the default singleton instance. */ - public DefaultXdsValidatorIndex() { - delegate = new ReflectiveValidatorIndex(); - } - - @Override - public Validator validatorFor(Class clazz) { - return delegate.validatorFor(clazz); + public static DefaultXdsValidatorIndex of() { + return INSTANCE; } @Override - public void assertValid(Object message) { - try { - validatorFor(message).assertValid(message); - } catch (ValidationException e) { - throw new IllegalArgumentException(e); - } + public void assertValid(Message message) { + requireNonNull(message, "message"); + pgvValidator.assertValid(message); + supportedFieldValidator.validate(message); } } diff --git a/xds-api/src/main/java/com/linecorp/armeria/xds/api/PgvValidator.java b/xds-api/src/main/java/com/linecorp/armeria/xds/api/PgvValidator.java new file mode 100644 index 00000000000..3c7a7ca21a6 --- /dev/null +++ b/xds-api/src/main/java/com/linecorp/armeria/xds/api/PgvValidator.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 LY Corporation + * + * LY Corporation licenses this file to you 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: + * + * https://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 com.linecorp.armeria.xds.api; + +import com.google.protobuf.Message; + +import com.linecorp.armeria.common.annotation.UnstableApi; +import com.linecorp.armeria.xds.validator.XdsValidationException; + +import io.envoyproxy.pgv.ReflectiveValidatorIndex; +import io.envoyproxy.pgv.ValidationException; +import io.envoyproxy.pgv.ValidatorIndex; + +/** + * Validates xDS protobuf messages using pgv (protoc-gen-validate) structural validation. + */ +@UnstableApi +public final class PgvValidator { + + private static final PgvValidator INSTANCE = new PgvValidator(); + + private static final ValidatorIndex delegate = new ReflectiveValidatorIndex(); + + private PgvValidator() {} + + /** + * Returns the singleton {@link PgvValidator} instance. + */ + public static PgvValidator of() { + return INSTANCE; + } + + /** + * Validates the given message using pgv structural validation. + * + * @throws XdsValidationException if validation fails + */ + public void assertValid(Message message) { + try { + delegate.validatorFor(message).assertValid(message); + } catch (ValidationException e) { + throw XdsValidationException.of(message, e); + } + } +} diff --git a/xds-api/src/main/java/com/linecorp/armeria/xds/api/StrictXdsValidatorIndex.java b/xds-api/src/main/java/com/linecorp/armeria/xds/api/StrictXdsValidatorIndex.java new file mode 100644 index 00000000000..2b24566d1a6 --- /dev/null +++ b/xds-api/src/main/java/com/linecorp/armeria/xds/api/StrictXdsValidatorIndex.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 LY Corporation + * + * LY Corporation licenses this file to you 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: + * + * https://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 com.linecorp.armeria.xds.api; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import com.google.protobuf.Message; + +import com.linecorp.armeria.common.annotation.UnstableApi; +import com.linecorp.armeria.xds.validator.XdsValidationException; +import com.linecorp.armeria.xds.validator.XdsValidatorIndex; + +/** + * A strict {@link XdsValidatorIndex} that composes pgv (protoc-gen-validate) structural validation + * with supported-field validation. All unsupported field violations are collected and reported + * together in a single {@link XdsValidationException}. + * + *

This validator is intended for use in test environments via SPI to catch unsupported + * field usage early. Its {@link #priority()} is {@code 1}, which is higher than + * {@link DefaultXdsValidatorIndex} ({@code 0}), so it wins SPI selection when present + * on the classpath. + */ +@UnstableApi +public final class StrictXdsValidatorIndex implements XdsValidatorIndex { + + private final PgvValidator pgvValidator = PgvValidator.of(); + + @Override + public void assertValid(Message message) { + requireNonNull(message, "message"); + pgvValidator.assertValid(message); + final List violations = new ArrayList<>(); + final SupportedFieldValidator validator = + SupportedFieldValidator.of((descriptorName, fieldPath, value) -> + violations.add(descriptorName + ": " + fieldPath)); + validator.validate(message); + if (!violations.isEmpty()) { + throw XdsValidationException.of( + message, "Unsupported xDS fields detected: " + String.join(", ", violations)); + } + } + + @Override + public int priority() { + return 1; + } +} diff --git a/xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java b/xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java index 3e31b0a3614..68e1f068d43 100644 --- a/xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java +++ b/xds-api/src/main/java/com/linecorp/armeria/xds/api/SupportedFieldValidator.java @@ -37,13 +37,22 @@ import com.linecorp.armeria.common.annotation.UnstableApi; /** - * Validates protobuf messages against the {@code (armeria.xds.supported)} field annotation. - * Any set field that lacks the annotation is reported as an unsupported field violation. - * The validator walks recursively into supported message-typed fields. + * Validates protobuf messages against the {@code (armeria.xds.supported.field)} and + * {@code (armeria.xds.supported.oneof_field)} annotations. Any set field whose number is not listed + * is reported as an unsupported field violation. The validator walks recursively into supported + * message-typed fields. * - *

Currently, supported fields are annotated inline on each field declaration in the proto files, e.g.: + *

Supported fields are annotated in the proto files, e.g.: *

{@code
- * string exact = 1 [(armeria.xds.supported) = true];
+ * message Address {
+ *   option (armeria.xds.supported.field) = 2;
+ *   string address = 2;
+ *
+ *   oneof port_specifier {
+ *     option (armeria.xds.supported.oneof_field) = 3;
+ *     uint32 port_value = 3;
+ *   }
+ * }
  * }
*/ @UnstableApi @@ -162,7 +171,10 @@ private void validateFieldValue(FieldDescriptor fd, Object value, } private static boolean unsupportedEnumValue(EnumValueDescriptor ev) { - return !ev.getOptions().getExtension(SupportedFieldProto.supportedValue); + final List supportedValues = + ev.getType().getOptions().getExtension(SupportedFieldProto.supported.enumValue); + // If no enum values are annotated, treat as "no opinion" — skip validation. + return !supportedValues.isEmpty() && !supportedValues.contains(ev.getNumber()); } private static boolean unsupportedPackage(String pkg) { @@ -171,9 +183,15 @@ private static boolean unsupportedPackage(String pkg) { private Set supportedFields(Descriptors.Descriptor descriptor) { return supportedFieldsCache.computeIfAbsent(descriptor, d -> { + final Set supportedNumbers = new HashSet<>( + d.getOptions().getExtension(SupportedFieldProto.supported.field)); + for (Descriptors.OneofDescriptor oneof : d.getOneofs()) { + supportedNumbers.addAll( + oneof.getOptions().getExtension(SupportedFieldProto.supported.oneofField)); + } final Set result = new HashSet<>(); for (FieldDescriptor fd : d.getFields()) { - if (fd.getOptions().getExtension(SupportedFieldProto.supported)) { + if (supportedNumbers.contains(fd.getNumber())) { result.add(fd); } } diff --git a/xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java b/xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java index 93b02eff63b..d44cd8a2358 100644 --- a/xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java +++ b/xds-api/src/main/java/com/linecorp/armeria/xds/api/UnsupportedFieldHandler.java @@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull; import com.linecorp.armeria.common.annotation.UnstableApi; +import com.linecorp.armeria.xds.validator.XdsValidationException; /** * A handler that is invoked when unsupported xDS fields are detected in a protobuf message. @@ -59,11 +60,11 @@ static UnsupportedFieldHandler warn() { } /** - * Returns a handler that throws an {@link IllegalArgumentException} on the first unsupported field. + * Returns a handler that throws an {@link XdsValidationException} on the first unsupported field. */ static UnsupportedFieldHandler reject() { return (descriptorName, fieldPath, value) -> { - throw new IllegalArgumentException( + throw XdsValidationException.of( "Unsupported xDS field detected in " + descriptorName + ": " + fieldPath); }; } diff --git a/xds-api/src/main/proto/armeria/xds/supported.proto b/xds-api/src/main/proto/armeria/xds/supported.proto index ba594135ca6..4bc479bc748 100644 --- a/xds-api/src/main/proto/armeria/xds/supported.proto +++ b/xds-api/src/main/proto/armeria/xds/supported.proto @@ -6,11 +6,26 @@ option java_outer_classname = "SupportedFieldProto"; import "google/protobuf/descriptor.proto"; -extend google.protobuf.FieldOptions { - optional bool supported = 50000; -} +// Enclosing message so that option usage reads as +// `(armeria.xds.supported.field)`, `(armeria.xds.supported.oneof_field)`, +// and `(armeria.xds.supported.enum_value)`. +message supported { + extend google.protobuf.MessageOptions { + // Field numbers of supported fields in this message. + // Place each `option (armeria.xds.supported.field) = ;` + // directly above the corresponding field declaration. + repeated int32 field = 50000; + } + + extend google.protobuf.OneofOptions { + // Field numbers of supported oneof members. + // Place each `option (armeria.xds.supported.oneof_field) = ;` + // directly above the corresponding field inside a oneof block. + repeated int32 oneof_field = 50000; + } -extend google.protobuf.EnumValueOptions { - // Distinct name from the FieldOptions extension to avoid Java codegen name collision. - optional bool supported_value = 50000; + extend google.protobuf.EnumOptions { + // Enum value numbers of supported values in this enum. + repeated int32 enum_value = 50000; + } } diff --git a/xds-api/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto b/xds-api/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto index 28b1eba6680..e56c4d2577b 100644 --- a/xds-api/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto +++ b/xds-api/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto @@ -29,6 +29,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.bootstrap.v3"; option java_outer_classname = "BootstrapProto"; option java_multiple_files = true; @@ -52,16 +54,19 @@ message Bootstrap { // Static :ref:`Listeners `. These listeners are // available regardless of LDS configuration. + option (armeria.xds.supported.field) = 1; repeated listener.v3.Listener listeners = 1; // If a network based configuration source is specified for :ref:`cds_config // `, it's necessary // to have some initial cluster definitions available to allow Envoy to know // how to speak to the management server. + option (armeria.xds.supported.field) = 2; repeated cluster.v3.Cluster clusters = 2; // These static secrets can be used by :ref:`SdsSecretConfig // ` + option (armeria.xds.supported.field) = 3; repeated envoy.extensions.transport_sockets.tls.v3.Secret secrets = 3; } @@ -74,6 +79,7 @@ message Bootstrap { // All :ref:`Listeners ` are provided by a single // :ref:`LDS ` configuration source. + option (armeria.xds.supported.field) = 1; core.v3.ConfigSource lds_config = 1; // xdstp:// resource locator for listener collection. @@ -83,6 +89,7 @@ message Bootstrap { // All post-bootstrap :ref:`Cluster ` definitions are // provided by a single :ref:`CDS ` // configuration source. + option (armeria.xds.supported.field) = 2; core.v3.ConfigSource cds_config = 2; // xdstp:// resource locator for cluster collection. @@ -96,6 +103,7 @@ message Bootstrap { // :ref:`ConfigSources ` that have // the :ref:`ads ` field set will be // streamed on the ADS channel. + option (armeria.xds.supported.field) = 3; core.v3.ApiConfigSource ads_config = 3; } @@ -147,6 +155,7 @@ message Bootstrap { // Node identity to present to the management server and for instance // identification purposes (e.g. in generated headers). + option (armeria.xds.supported.field) = 1; core.v3.Node node = 1; // A list of :ref:`Node ` field names @@ -184,13 +193,16 @@ message Bootstrap { repeated string node_context_params = 26; // Statically specified resources. + option (armeria.xds.supported.field) = 2; StaticResources static_resources = 2; // xDS configuration sources. + option (armeria.xds.supported.field) = 3; DynamicResources dynamic_resources = 3; // Configuration for the cluster manager which owns all upstream clusters // within the server. + option (armeria.xds.supported.field) = 4; ClusterManager cluster_manager = 4; // Health discovery service config option. @@ -485,6 +497,7 @@ message ClusterManager { // `. This is unrelated to // the :option:`--service-cluster` option which does not `affect zone aware // routing `_. + option (armeria.xds.supported.field) = 1; string local_cluster_name = 1; // Optional global configuration for outlier detection. diff --git a/xds-api/src/main/proto/envoy/config/cluster/v3/cluster.proto b/xds-api/src/main/proto/envoy/config/cluster/v3/cluster.proto index c5112458a71..922637a1d38 100644 --- a/xds-api/src/main/proto/envoy/config/cluster/v3/cluster.proto +++ b/xds-api/src/main/proto/envoy/config/cluster/v3/cluster.proto @@ -30,6 +30,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.cluster.v3"; option java_outer_classname = "ClusterProto"; option java_multiple_files = true; @@ -54,11 +56,13 @@ message Cluster { enum DiscoveryType { // Refer to the :ref:`static discovery type` // for an explanation. + option (armeria.xds.supported.enum_value) = 0; STATIC = 0; // Refer to the :ref:`strict DNS discovery // type` // for an explanation. + option (armeria.xds.supported.enum_value) = 1; STRICT_DNS = 1; // Refer to the :ref:`logical DNS discovery @@ -68,6 +72,7 @@ message Cluster { // Refer to the :ref:`service discovery type` // for an explanation. + option (armeria.xds.supported.enum_value) = 3; EDS = 3; // Refer to the :ref:`original destination discovery @@ -86,11 +91,13 @@ message Cluster { // Refer to the :ref:`round robin load balancing // policy` // for an explanation. + option (armeria.xds.supported.enum_value) = 0; ROUND_ROBIN = 0; // Refer to the :ref:`least request load balancing // policy` // for an explanation. + option (armeria.xds.supported.enum_value) = 1; LEAST_REQUEST = 1; // Refer to the :ref:`ring hash load balancing @@ -101,6 +108,7 @@ message Cluster { // Refer to the :ref:`random load balancing // policy` // for an explanation. + option (armeria.xds.supported.enum_value) = 3; RANDOM = 3; // Refer to the :ref:`Maglev load balancing policy` @@ -166,6 +174,7 @@ message Cluster { "envoy.api.v2.Cluster.TransportSocketMatch"; // The name of the match, used in stats generation. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1}]; // Optional metadata match criteria. @@ -173,10 +182,12 @@ message Cluster { // will use the transport socket configuration specified here. // The endpoint's metadata entry in ``envoy.transport_socket_match`` is used to match // against the values specified in this field. + option (armeria.xds.supported.field) = 2; google.protobuf.Struct match = 2; // The configuration of the transport socket. // [#extension-category: envoy.transport_sockets.upstream] + option (armeria.xds.supported.field) = 3; core.v3.TransportSocket transport_socket = 3; } @@ -200,11 +211,13 @@ message Cluster { "envoy.api.v2.Cluster.EdsClusterConfig"; // Configuration for the source of EDS updates for this Cluster. + option (armeria.xds.supported.field) = 1; core.v3.ConfigSource eds_config = 1; // Optional alternative to cluster name to present to EDS. This does not // have the same restrictions as cluster name, i.e. it may be arbitrary // length. This may be a xdstp:// URL. + option (armeria.xds.supported.field) = 2; string service_name = 2; } @@ -221,8 +234,11 @@ message Cluster { // etc). If DEFAULT_SUBSET is selected, load balancing is performed over the // endpoints matching the values from the default_subset field. enum LbSubsetFallbackPolicy { + option (armeria.xds.supported.enum_value) = 0; NO_FALLBACK = 0; + option (armeria.xds.supported.enum_value) = 1; ANY_ENDPOINT = 1; + option (armeria.xds.supported.enum_value) = 2; DEFAULT_SUBSET = 2; } @@ -302,6 +318,7 @@ message Cluster { } // List of keys to match with the weighted cluster metadata. + option (armeria.xds.supported.field) = 1; repeated string keys = 1; // Selects a mode of operation in which each subset has only one host. This mode uses the same rules for @@ -335,6 +352,7 @@ message Cluster { // The behavior used when no endpoint subset matches the selected route's // metadata. The value defaults to // :ref:`NO_FALLBACK`. + option (armeria.xds.supported.field) = 1; LbSubsetFallbackPolicy fallback_policy = 1 [(validate.rules).enum = {defined_only: true}]; // Specifies the default subset of endpoints used during fallback if @@ -361,6 +379,7 @@ message Cluster { // A subset is matched when the metadata from the selected route and // weighted cluster contains the same keys and values as the subset's // metadata. The same host may appear in multiple subsets. + option (armeria.xds.supported.field) = 3; repeated LbSubsetSelector subset_selectors = 3; // If true, routing to subsets will take into account the localities and locality weights of the @@ -410,6 +429,7 @@ message Cluster { // Represents the size of slow start window. // If set, the newly created host remains in slow start mode starting from its creation time // for the duration of slow start window. + option (armeria.xds.supported.field) = 1; google.protobuf.Duration slow_start_window = 1; // This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, @@ -424,11 +444,13 @@ message Cluster { // // As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. // Once host exits slow start, time_factor and aggression no longer affect its weight. + option (armeria.xds.supported.field) = 2; core.v3.RuntimeDouble aggression = 2; // Configures the minimum percentage of origin weight that avoids too small new weight, // which may cause endpoints in slow start mode receive no traffic in slow start window. // If not specified, the default is 10%. + option (armeria.xds.supported.field) = 3; type.v3.Percent min_weight_percent = 3; } @@ -436,6 +458,7 @@ message Cluster { message RoundRobinLbConfig { // Configuration for slow start mode. // If this configuration is not set, slow start will not be not enabled. + option (armeria.xds.supported.field) = 1; SlowStartConfig slow_start_config = 1; } @@ -475,6 +498,7 @@ message Cluster { // Configuration for slow start mode. // If this configuration is not set, slow start will not be not enabled. + option (armeria.xds.supported.field) = 3; SlowStartConfig slow_start_config = 3; } @@ -574,6 +598,7 @@ message Cluster { // if zone aware routing is configured. If not specified, the default is 100%. // * :ref:`runtime values `. // * :ref:`Zone aware routing support `. + option (armeria.xds.supported.field) = 1; type.v3.Percent routing_enabled = 1; // Configures minimum upstream cluster size required for zone aware routing @@ -581,12 +606,14 @@ message Cluster { // even if zone aware routing is configured. If not specified, the default is 6. // * :ref:`runtime values `. // * :ref:`Zone aware routing support `. + option (armeria.xds.supported.field) = 2; google.protobuf.UInt64Value min_cluster_size = 2; // If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic // mode`. Instead, the cluster will fail all // requests as if all hosts are unhealthy. This can help avoid potentially overwhelming a // failing service. + option (armeria.xds.supported.field) = 3; bool fail_traffic_on_panic = 3; } @@ -633,11 +660,14 @@ message Cluster { // // .. note:: // The specified percent will be truncated to the nearest 1%. + option (armeria.xds.supported.field) = 1; type.v3.Percent healthy_panic_threshold = 1; oneof locality_config_specifier { + option (armeria.xds.supported.oneof_field) = 2; ZoneAwareLbConfig zone_aware_lb_config = 2; + option (armeria.xds.supported.oneof_field) = 3; LocalityWeightedLbConfig locality_weighted_lb_config = 3; } @@ -685,6 +715,7 @@ message Cluster { // Specifies the base interval between refreshes. This parameter is required and must be greater // than zero and less than // :ref:`max_interval `. + option (armeria.xds.supported.field) = 1; google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gt {nanos: 1000000} @@ -694,6 +725,7 @@ message Cluster { // greater than or equal to the // :ref:`base_interval ` if set. The default // is 10 times the :ref:`base_interval `. + option (armeria.xds.supported.field) = 2; google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {nanos: 1000000}}]; } @@ -807,6 +839,7 @@ message Cluster { // :ref:`transport socket match criteria ` field. // // [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] + option (armeria.xds.supported.field) = 43; repeated TransportSocketMatch transport_socket_matches = 43; // Supplies the name of the cluster which must be unique across all clusters. @@ -814,6 +847,7 @@ message Cluster { // :ref:`statistics ` if :ref:`alt_stat_name // ` is not provided. // Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1}]; // An optional alternative to the cluster name to be used for observability. This name is used @@ -830,6 +864,7 @@ message Cluster { oneof cluster_discovery_type { // The :ref:`service discovery type ` // to use for resolving the cluster. + option (armeria.xds.supported.oneof_field) = 2; DiscoveryType type = 2 [(validate.rules).enum = {defined_only: true}]; // The custom cluster type. @@ -837,10 +872,12 @@ message Cluster { } // Configuration to use for EDS updates for the Cluster. + option (armeria.xds.supported.field) = 3; EdsClusterConfig eds_cluster_config = 3; // The timeout for new network connections to hosts in the cluster. // If not set, a default value of 5s will be used. + option (armeria.xds.supported.field) = 4; google.protobuf.Duration connect_timeout = 4 [(validate.rules).duration = {gt {}}]; // Soft limit on size of the cluster’s connections read and write buffers. If @@ -850,6 +887,7 @@ message Cluster { // The :ref:`load balancer type ` to use // when picking a host in the cluster. + option (armeria.xds.supported.field) = 6; LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true}]; // Setting this is required for specifying members of @@ -863,12 +901,14 @@ message Cluster { // Setting this allows non-EDS cluster types to contain embedded EDS equivalent // :ref:`endpoint assignments`. // + option (armeria.xds.supported.field) = 33; endpoint.v3.ClusterLoadAssignment load_assignment = 33; // Optional :ref:`active health checking ` // configuration for the cluster. If no // configuration is specified no health checking will be done and all cluster // members will be considered healthy at all times. + option (armeria.xds.supported.field) = 8; repeated core.v3.HealthCheck health_checks = 8; // Optional maximum requests for a single upstream connection. This parameter @@ -961,6 +1001,7 @@ message Cluster { // extension point and configuring it with :ref:`DnsCluster`. // If :ref:`cluster_type` is configured with // :ref:`DnsCluster`, this field will be ignored. + option (armeria.xds.supported.field) = 16; google.protobuf.Duration dns_refresh_rate = 16 [ deprecated = true, (validate.rules).duration = {gt {nanos: 1000000}}, @@ -998,6 +1039,7 @@ message Cluster { // extension point and configuring it with :ref:`DnsCluster`. // If :ref:`cluster_type` is configured with // :ref:`DnsCluster`, this field will be ignored. + option (armeria.xds.supported.field) = 44; RefreshRate dns_failure_refresh_rate = 44 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -1008,6 +1050,7 @@ message Cluster { // extension point and configuring it with :ref:`DnsCluster`. // If :ref:`cluster_type` is configured with // :ref:`DnsCluster`, this field will be ignored. + option (armeria.xds.supported.field) = 39; bool respect_dns_ttl = 39 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -1098,6 +1141,7 @@ message Cluster { core.v3.BindConfig upstream_bind_config = 21; // Configuration for load balancing subsetting. + option (armeria.xds.supported.field) = 22; LbSubsetConfig lb_subset_config = 22; // Optional configuration for the load balancing algorithm selected by @@ -1119,13 +1163,16 @@ message Cluster { OriginalDstLbConfig original_dst_lb_config = 34; // Optional configuration for the LeastRequest load balancing policy. + option (armeria.xds.supported.oneof_field) = 37; LeastRequestLbConfig least_request_lb_config = 37; // Optional configuration for the RoundRobin load balancing policy. + option (armeria.xds.supported.oneof_field) = 56; RoundRobinLbConfig round_robin_lb_config = 56; } // Common configuration for all load balancer implementations. + option (armeria.xds.supported.field) = 27; CommonLbConfig common_lb_config = 27; // Optional custom transport socket implementation to use for upstream connections. @@ -1133,6 +1180,7 @@ message Cluster { // :ref:`UpstreamTlsContexts ` in the ``typed_config``. // If no transport socket configuration is specified, new connections // will be set up with plaintext. + option (armeria.xds.supported.field) = 24; core.v3.TransportSocket transport_socket = 24; // The Metadata field can be used to provide additional information about the diff --git a/xds-api/src/main/proto/envoy/config/core/v3/address.proto b/xds-api/src/main/proto/envoy/config/core/v3/address.proto index 238494a09c7..50f192ef76c 100644 --- a/xds-api/src/main/proto/envoy/config/core/v3/address.proto +++ b/xds-api/src/main/proto/envoy/config/core/v3/address.proto @@ -12,6 +12,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.core.v3"; option java_outer_classname = "AddressProto"; option java_multiple_files = true; @@ -27,6 +29,7 @@ message Pipe { // abstract namespace. The starting '@' is replaced by a null byte by Envoy. // Paths starting with '@' will result in an error in environments other than // Linux. + option (armeria.xds.supported.field) = 1; string path = 1 [(validate.rules).string = {min_len: 1}]; // The mode for the Pipe. Not applicable for abstract sockets. @@ -72,11 +75,13 @@ message SocketAddress { // address must be an IP (``STATIC`` or ``EDS`` clusters) or a hostname resolved by DNS // (``STRICT_DNS`` or ``LOGICAL_DNS`` clusters). Address resolution can be customized // via :ref:`resolver_name `. + option (armeria.xds.supported.field) = 2; string address = 2 [(validate.rules).string = {min_len: 1}]; oneof port_specifier { option (validate.required) = true; + option (armeria.xds.supported.oneof_field) = 3; uint32 port_value = 3 [(validate.rules).uint32 = {lte: 65535}]; // This is only valid if :ref:`resolver_name @@ -189,8 +194,10 @@ message Address { oneof address { option (validate.required) = true; + option (armeria.xds.supported.oneof_field) = 1; SocketAddress socket_address = 1; + option (armeria.xds.supported.oneof_field) = 2; Pipe pipe = 2; // Specifies a user-space address handled by :ref:`internal listeners diff --git a/xds-api/src/main/proto/envoy/config/core/v3/base.proto b/xds-api/src/main/proto/envoy/config/core/v3/base.proto index 978f365d5f9..d6dafbdbadf 100644 --- a/xds-api/src/main/proto/envoy/config/core/v3/base.proto +++ b/xds-api/src/main/proto/envoy/config/core/v3/base.proto @@ -20,6 +20,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.core.v3"; option java_outer_classname = "BaseProto"; option java_multiple_files = true; @@ -71,6 +73,7 @@ message Locality { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Locality"; // Region this :ref:`zone ` belongs to. + option (armeria.xds.supported.field) = 1; string region = 1; // Defines the local service zone where Envoy is running. Though optional, it @@ -81,11 +84,13 @@ message Locality { // `_ // on AWS, `Zone `_ on // GCP, etc. + option (armeria.xds.supported.field) = 2; string zone = 2; // When used for locality of upstream hosts, this field further splits zone // into smaller chunks of sub-zones so they can be load balanced // independently. + option (armeria.xds.supported.field) = 3; string sub_zone = 3; } @@ -181,6 +186,7 @@ message Node { map dynamic_parameters = 12; // Locality specifying where the Envoy instance is running. + option (armeria.xds.supported.field) = 4; Locality locality = 4; // Free-form string that identifies the entity requesting config. @@ -245,6 +251,7 @@ message Metadata { // :ref:`typed_filter_metadata ` // fields are present in the metadata with same keys, // only ``typed_filter_metadata`` field will be parsed. + option (armeria.xds.supported.field) = 1; map filter_metadata = 1 [(validate.rules).map = {keys {string {min_len: 1}}}]; @@ -401,6 +408,7 @@ message HeaderValue { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.HeaderValue"; // Header name. + option (armeria.xds.supported.field) = 1; string key = 1 [(validate.rules).string = {min_len: 1 max_bytes: 16384 well_known_regex: HTTP_HEADER_NAME strict: false}]; @@ -412,6 +420,7 @@ message HeaderValue { // unknown header values are replaced with the empty string instead of ``-``. // Header value is encoded as string. This does not work for non-utf8 characters. // Only one of ``value`` or ``raw_value`` can be set. + option (armeria.xds.supported.field) = 2; string value = 2 [ (validate.rules).string = {max_bytes: 16384 well_known_regex: HTTP_HEADER_VALUE strict: false}, (udpa.annotations.field_migrate).oneof_promotion = "value_type" @@ -492,6 +501,7 @@ message HeaderMap { // events inside this directory trigger the watch. message WatchedDirectory { // Directory path to watch. + option (armeria.xds.supported.field) = 1; string path = 1 [(validate.rules).string = {min_len: 1}]; } @@ -504,12 +514,15 @@ message DataSource { option (validate.required) = true; // Local filesystem data source. + option (armeria.xds.supported.oneof_field) = 1; string filename = 1 [(validate.rules).string = {min_len: 1}]; // Bytes inlined in the configuration. + option (armeria.xds.supported.oneof_field) = 2; bytes inline_bytes = 2; // String inlined in the configuration. + option (armeria.xds.supported.oneof_field) = 3; string inline_string = 3; // Environment variable data source. @@ -619,6 +632,7 @@ message TransportSocket { reserved "config"; + option (armeria.xds.supported.field) = 1; // The name of the transport socket to instantiate. The name must match a supported transport // socket implementation. string name = 1 [(validate.rules).string = {min_len: 1}]; @@ -626,6 +640,7 @@ message TransportSocket { // Implementation specific configuration which depends on the implementation being instantiated. // See the supported transport socket implementations for further documentation. oneof config_type { + option (armeria.xds.supported.oneof_field) = 3; google.protobuf.Any typed_config = 3; } } diff --git a/xds-api/src/main/proto/envoy/config/core/v3/config_source.proto b/xds-api/src/main/proto/envoy/config/core/v3/config_source.proto index 430562aa5bd..aea28e5853f 100644 --- a/xds-api/src/main/proto/envoy/config/core/v3/config_source.proto +++ b/xds-api/src/main/proto/envoy/config/core/v3/config_source.proto @@ -17,6 +17,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.core.v3"; option java_outer_classname = "ConfigSourceProto"; option java_multiple_files = true; @@ -28,12 +30,14 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // xDS API and non-xDS services version. This is used to describe both resource and transport // protocol versions (in distinct configuration fields). enum ApiVersion { + option (armeria.xds.supported.enum_value) = 0; // When not specified, we assume v3; it is the only supported version. AUTO = 0; // Use xDS v2 API. This is no longer supported. V2 = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version_enum) = "3.0"]; + option (armeria.xds.supported.enum_value) = 2; // Use xDS v3 API. V3 = 2; } @@ -56,30 +60,36 @@ message ApiConfigSource { // the v2 protos is used. REST = 1; + option (armeria.xds.supported.enum_value) = 2; // SotW gRPC service. GRPC = 2; + option (armeria.xds.supported.enum_value) = 3; // Using the delta xDS gRPC service, i.e. DeltaDiscovery{Request,Response} // rather than Discovery{Request,Response}. Rather than sending Envoy the entire state // with every update, the xDS server only sends what has changed since the last update. DELTA_GRPC = 3; + option (armeria.xds.supported.enum_value) = 5; // SotW xDS gRPC with ADS. All resources which resolve to this configuration source will be // multiplexed on a single connection to an ADS endpoint. // [#not-implemented-hide:] AGGREGATED_GRPC = 5; + option (armeria.xds.supported.enum_value) = 6; // Delta xDS gRPC with ADS. All resources which resolve to this configuration source will be // multiplexed on a single connection to an ADS endpoint. // [#not-implemented-hide:] AGGREGATED_DELTA_GRPC = 6; } + option (armeria.xds.supported.field) = 1; // API type (gRPC, REST, delta gRPC) ApiType api_type = 1 [(validate.rules).enum = {defined_only: true}]; // API version for xDS transport protocol. This describes the xDS gRPC/REST // endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + option (armeria.xds.supported.field) = 8; ApiVersion transport_api_version = 8 [(validate.rules).enum = {defined_only: true}]; // Cluster names should be used only with REST. If > 1 @@ -94,6 +104,7 @@ message ApiConfigSource { // Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, // services will be cycled through if any kind of failure occurs. + option (armeria.xds.supported.field) = 4; repeated GrpcService grpc_services = 4; // For REST APIs, the delay between successive polls. @@ -217,10 +228,12 @@ message ConfigSource { PathConfigSource path_config_source = 8; // API configuration source. + option (armeria.xds.supported.oneof_field) = 2; ApiConfigSource api_config_source = 2; // When set, ADS will be used to fetch resources. The ADS API configuration // source in the bootstrap configuration is used. + option (armeria.xds.supported.oneof_field) = 3; AggregatedConfigSource ads = 3; // [#not-implemented-hide:] @@ -234,6 +247,7 @@ message ConfigSource { // [#next-major-version: In xDS v3, consider replacing the ads field with this one, since // this field can implicitly mean to use the same stream in the case where the ConfigSource // is provided via ADS and the specified data can also be obtained via ADS.] + option (armeria.xds.supported.oneof_field) = 5; SelfConfigSource self = 5; } @@ -244,11 +258,13 @@ message ConfigSource { // when the xDS API subscription starts, and is disarmed on first config update or on error. 0 // means no timeout - Envoy will wait indefinitely for the first xDS config (unless another // timeout applies). The default is 15s. + option (armeria.xds.supported.field) = 4; google.protobuf.Duration initial_fetch_timeout = 4; // API version for xDS resources. This implies the type URLs that the client // will request for resources and the resource type that the client will in // turn expect to be delivered. + option (armeria.xds.supported.field) = 6; ApiVersion resource_api_version = 6 [(validate.rules).enum = {defined_only: true}]; } diff --git a/xds-api/src/main/proto/envoy/config/core/v3/grpc_service.proto b/xds-api/src/main/proto/envoy/config/core/v3/grpc_service.proto index f8feb2f516f..d7fa9b7c13a 100644 --- a/xds-api/src/main/proto/envoy/config/core/v3/grpc_service.proto +++ b/xds-api/src/main/proto/envoy/config/core/v3/grpc_service.proto @@ -15,6 +15,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.core.v3"; option java_outer_classname = "GrpcServiceProto"; option java_multiple_files = true; @@ -37,6 +39,7 @@ message GrpcService { // The name of the upstream gRPC cluster. SSL credentials will be supplied // in the :ref:`Cluster ` :ref:`transport_socket // `. + option (armeria.xds.supported.field) = 1; string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // The ``:authority`` header in the grpc request. If this field is not set, the authority header value will be ``cluster_name``. @@ -310,6 +313,7 @@ message GrpcService { // Envoy's in-built gRPC client. // See the :ref:`gRPC services overview ` // documentation for discussion on gRPC client selection. + option (armeria.xds.supported.oneof_field) = 1; EnvoyGrpc envoy_grpc = 1; // `Google C++ gRPC client `_ @@ -327,6 +331,7 @@ message GrpcService { // be injected. For more information, including details on header value syntax, see the // documentation on :ref:`custom request headers // `. + option (armeria.xds.supported.field) = 5; repeated HeaderValue initial_metadata = 5; // Optional default retry policy for streams toward the service. diff --git a/xds-api/src/main/proto/envoy/config/core/v3/health_check.proto b/xds-api/src/main/proto/envoy/config/core/v3/health_check.proto index a4ed6e91818..594c996f640 100644 --- a/xds-api/src/main/proto/envoy/config/core/v3/health_check.proto +++ b/xds-api/src/main/proto/envoy/config/core/v3/health_check.proto @@ -20,6 +20,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.config.core.v3"; option java_outer_classname = "HealthCheckProto"; option java_multiple_files = true; @@ -36,12 +38,15 @@ enum HealthStatus { // The health status is not known. This is interpreted by Envoy as ``HEALTHY``. UNKNOWN = 0; + option (armeria.xds.supported.enum_value) = 1; // Healthy. HEALTHY = 1; + option (armeria.xds.supported.enum_value) = 2; // Unhealthy. UNHEALTHY = 2; + option (armeria.xds.supported.enum_value) = 3; // Connection draining in progress. E.g., // ``_ // or @@ -53,6 +58,7 @@ enum HealthStatus { // ``UNHEALTHY``. TIMEOUT = 4; + option (armeria.xds.supported.enum_value) = 5; // Degraded. DEGRADED = 5; } @@ -96,10 +102,12 @@ message HealthCheck { // left empty (default value), the name of the cluster this health check is associated // with will be used. The host header can be customized for a specific endpoint by setting the // :ref:`hostname ` field. + option (armeria.xds.supported.field) = 1; string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE}]; // Specifies the HTTP path that will be requested during health checking. For example // ``/healthcheck``. + option (armeria.xds.supported.field) = 2; string path = 2 [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE}]; // HTTP specific payload to be sent as the request body during health checking. @@ -166,6 +174,7 @@ message HealthCheck { // Request body payloads are supported for POST, PUT, PATCH, and OPTIONS methods only. // CONNECT method is disallowed because it is not appropriate for health check request. // If a non-200 response is expected by the method, it needs to be set in :ref:`expected_statuses `. + option (armeria.xds.supported.field) = 13; RequestMethod method = 13 [(validate.rules).enum = {defined_only: true not_in: 6}]; } @@ -268,12 +277,14 @@ message HealthCheck { // The time to wait for a health check response. If the timeout is reached the // health check attempt will be considered a failure. + option (armeria.xds.supported.field) = 1; google.protobuf.Duration timeout = 1 [(validate.rules).duration = { required: true gt {} }]; // The interval between health checks. + option (armeria.xds.supported.field) = 2; google.protobuf.Duration interval = 2 [(validate.rules).duration = { required: true gt {} @@ -301,11 +312,13 @@ message HealthCheck { // :ref:`expected_statuses ` // or :ref:`retriable_statuses `, // this threshold is ignored and the host is considered immediately unhealthy. + option (armeria.xds.supported.field) = 4; google.protobuf.UInt32Value unhealthy_threshold = 4 [(validate.rules).message = {required: true}]; // The number of healthy health checks required before a host is marked // healthy. Note that during startup, only a single successful health check is // required to mark a host healthy. + option (armeria.xds.supported.field) = 5; google.protobuf.UInt32Value healthy_threshold = 5 [(validate.rules).message = {required: true}]; // [#not-implemented-hide:] Non-serving port for health checking. @@ -318,6 +331,7 @@ message HealthCheck { option (validate.required) = true; // HTTP health check. + option (armeria.xds.supported.oneof_field) = 8; HttpHealthCheck http_health_check = 8; // TCP health check. diff --git a/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint.proto b/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint.proto index a149f6095c1..f3504f8c852 100644 --- a/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint.proto +++ b/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint.proto @@ -8,6 +8,7 @@ import "envoy/type/v3/percent.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; +import "armeria/xds/supported.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -99,6 +100,7 @@ message ClusterLoadAssignment { // // Read more at :ref:`priority levels ` and // :ref:`localities `. + option (armeria.xds.supported.field) = 3; google.protobuf.UInt32Value overprovisioning_factor = 3 [(validate.rules).uint32 = {gt: 0}]; // The max time until which the endpoints from this assignment can be used. @@ -116,6 +118,7 @@ message ClusterLoadAssignment { // .. note:: // This is not currently implemented for // :ref:`locality weighted load balancing `. + option (armeria.xds.supported.field) = 6; bool weighted_priority_health = 6; } @@ -123,9 +126,11 @@ message ClusterLoadAssignment { // ` value if specified // in the cluster :ref:`EdsClusterConfig // `. + option (armeria.xds.supported.field) = 1; string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // List of endpoints to load balance to. + option (armeria.xds.supported.field) = 2; repeated LocalityLbEndpoints endpoints = 2; // Map of named endpoints that can be referenced in LocalityLbEndpoints. @@ -133,5 +138,6 @@ message ClusterLoadAssignment { map named_endpoints = 5; // Load balancing policy settings. + option (armeria.xds.supported.field) = 4; Policy policy = 4; } diff --git a/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto b/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto index eacc555df73..37783668e99 100644 --- a/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto +++ b/xds-api/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto @@ -11,6 +11,7 @@ import "google/protobuf/wrappers.proto"; import "xds/core/v3/collection_entry.proto"; +import "armeria/xds/supported.proto"; import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -39,6 +40,7 @@ message Endpoint { // as the host's serving address port. This provides an alternative health // check port. Setting this with a non-zero value allows an upstream host // to have different health check address port. + option (armeria.xds.supported.field) = 1; uint32 port_value = 1 [(validate.rules).uint32 = {lte: 65535}]; // By default, the host header for L7 health checks is controlled by cluster level configuration @@ -46,6 +48,7 @@ message Endpoint { // :ref:`authority `). Setting this // to a non-empty value allows overriding the cluster level configuration for a specific // endpoint. + option (armeria.xds.supported.field) = 2; string hostname = 2; // Optional alternative health check host address. @@ -53,10 +56,12 @@ message Endpoint { // .. attention:: // // The form of the health check host address is expected to be a direct IP address. + option (armeria.xds.supported.field) = 3; core.v3.Address address = 3; // Optional flag to control if perform active health check for this endpoint. // Active health check is enabled by default if there is a health checker. + option (armeria.xds.supported.field) = 4; bool disable_active_health_check = 4; } @@ -74,6 +79,7 @@ message Endpoint { // specified :ref:`resolver ` // in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, // and will be resolved via DNS. + option (armeria.xds.supported.field) = 1; core.v3.Address address = 1; // The optional health check configuration is used as configuration for the @@ -83,12 +89,14 @@ message Endpoint { // // This takes into effect only for upstream clusters with // :ref:`active health checking ` enabled. + option (armeria.xds.supported.field) = 2; HealthCheckConfig health_check_config = 2; // The hostname associated with this endpoint. This hostname is not used for routing or address // resolution. If provided, it will be associated with the endpoint, and can be used for features // that require a hostname, like // :ref:`auto_host_rewrite `. + option (armeria.xds.supported.field) = 3; string hostname = 3; // An ordered list of addresses that together with ``address`` comprise the @@ -106,6 +114,7 @@ message LbEndpoint { // Upstream host identifier or a named reference. oneof host_identifier { + option (armeria.xds.supported.oneof_field) = 1; Endpoint endpoint = 1; // [#not-implemented-hide:] @@ -113,6 +122,7 @@ message LbEndpoint { } // Optional health status when known and supplied by EDS server. + option (armeria.xds.supported.field) = 2; core.v3.HealthStatus health_status = 2; // The endpoint metadata specifies values that may be used by the load @@ -122,6 +132,7 @@ message LbEndpoint { // This may be matched against in a route's // :ref:`RouteAction ` metadata_match field // to subset the endpoints considered in cluster load balancing. + option (armeria.xds.supported.field) = 3; core.v3.Metadata metadata = 3; // The optional load balancing weight of the upstream host; at least 1. @@ -133,6 +144,7 @@ message LbEndpoint { // LocalityLbEndpoints. If unspecified, will be treated as 1. The sum // of the weights of all endpoints in the endpoint's locality must not // exceed uint32_t maximal value (4294967295). + option (armeria.xds.supported.field) = 4; google.protobuf.UInt32Value load_balancing_weight = 4 [(validate.rules).uint32 = {gte: 1}]; } @@ -172,14 +184,17 @@ message LocalityLbEndpoints { } // Identifies location of where the upstream hosts run. + option (armeria.xds.supported.field) = 1; core.v3.Locality locality = 1; // Metadata to provide additional information about the locality endpoints in aggregate. + option (armeria.xds.supported.field) = 9; core.v3.Metadata metadata = 9; // The group of endpoints belonging to the locality specified. // This is ignored if :ref:`leds_cluster_locality_config // ` is set. + option (armeria.xds.supported.field) = 2; repeated LbEndpoint lb_endpoints = 2; oneof lb_config { @@ -206,6 +221,7 @@ message LocalityLbEndpoints { // configured. These weights are ignored otherwise. If no weights are // specified when locality weighted load balancing is enabled, the locality is // assigned no load. + option (armeria.xds.supported.field) = 3; google.protobuf.UInt32Value load_balancing_weight = 3 [(validate.rules).uint32 = {gte: 1}]; // Optional: the priority for this LocalityLbEndpoints. If unspecified this will @@ -217,6 +233,7 @@ message LocalityLbEndpoints { // next highest priority group. Read more at :ref:`priority levels `. // // Priorities should range from 0 (highest) to N (lowest) without skipping. + option (armeria.xds.supported.field) = 5; uint32 priority = 5 [(validate.rules).uint32 = {lte: 128}]; // Optional: Per locality proximity value which indicates how close this diff --git a/xds-api/src/main/proto/envoy/config/listener/v3/api_listener.proto b/xds-api/src/main/proto/envoy/config/listener/v3/api_listener.proto index a3610e65688..3928c97915f 100644 --- a/xds-api/src/main/proto/envoy/config/listener/v3/api_listener.proto +++ b/xds-api/src/main/proto/envoy/config/listener/v3/api_listener.proto @@ -4,6 +4,7 @@ package envoy.config.listener.v3; import "google/protobuf/any.proto"; +import "armeria/xds/supported.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -30,5 +31,6 @@ message ApiListener { // it would have caused circular dependencies for go protos: lds.proto depends on this file, // and http_connection_manager.proto depends on rds.proto, which is in the same directory as // lds.proto, so lds.proto cannot depend on this file.] + option (armeria.xds.supported.field) = 1; google.protobuf.Any api_listener = 1; } diff --git a/xds-api/src/main/proto/envoy/config/listener/v3/listener.proto b/xds-api/src/main/proto/envoy/config/listener/v3/listener.proto index ff2f79d1137..6f08a7585eb 100644 --- a/xds-api/src/main/proto/envoy/config/listener/v3/listener.proto +++ b/xds-api/src/main/proto/envoy/config/listener/v3/listener.proto @@ -19,6 +19,7 @@ import "xds/annotations/v3/status.proto"; import "xds/core/v3/collection_entry.proto"; import "xds/type/matcher/v3/matcher.proto"; +import "armeria/xds/supported.proto"; import "envoy/annotations/deprecation.proto"; import "udpa/annotations/security.proto"; import "udpa/annotations/status.proto"; @@ -135,6 +136,7 @@ message Listener { // The unique name by which this listener is known. If no name is provided, // Envoy will allocate an internal UUID for the listener. If the listener is to be dynamically // updated or removed via :ref:`LDS ` a unique name must be provided. + option (armeria.xds.supported.field) = 1; string name = 1; // The address that the listener should listen on. In general, the address must be unique, though @@ -317,6 +319,7 @@ message Listener { // and the top-level Listener should essentially be a oneof that selects between the // socket listener and the various types of API listener. That way, a given Listener message // can structurally only contain the fields of the relevant type.] + option (armeria.xds.supported.field) = 19; ApiListener api_listener = 19; // The listener's connection balancer configuration, currently only applicable to TCP listeners. diff --git a/xds-api/src/main/proto/envoy/config/route/v3/route.proto b/xds-api/src/main/proto/envoy/config/route/v3/route.proto index c4d507d22b0..aaf32686791 100644 --- a/xds-api/src/main/proto/envoy/config/route/v3/route.proto +++ b/xds-api/src/main/proto/envoy/config/route/v3/route.proto @@ -9,6 +9,7 @@ import "envoy/config/route/v3/route_components.proto"; import "google/protobuf/any.proto"; import "google/protobuf/wrappers.proto"; +import "armeria/xds/supported.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -31,9 +32,11 @@ message RouteConfiguration { // :ref:`route_config_name // ` in // :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + option (armeria.xds.supported.field) = 1; string name = 1; // An array of virtual hosts that make up the route table. + option (armeria.xds.supported.field) = 2; repeated VirtualHost virtual_hosts = 2; // An array of virtual hosts will be dynamically loaded via the VHDS API. @@ -131,6 +134,7 @@ message RouteConfiguration { // With this option enabled, Envoy will ignore the port number in the :authority header (if any) when picking VirtualHost. // NOTE: this option will not strip the port number (if any) contained in route config // :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`.domains field. + option (armeria.xds.supported.field) = 14; bool ignore_port_in_host_matching = 14; // Ignore path-parameters in path-matching. diff --git a/xds-api/src/main/proto/envoy/config/route/v3/route_components.proto b/xds-api/src/main/proto/envoy/config/route/v3/route_components.proto index 7364a96251e..9cac28e3130 100644 --- a/xds-api/src/main/proto/envoy/config/route/v3/route_components.proto +++ b/xds-api/src/main/proto/envoy/config/route/v3/route_components.proto @@ -21,6 +21,7 @@ import "google/protobuf/wrappers.proto"; import "xds/type/matcher/v3/matcher.proto"; +import "armeria/xds/supported.proto"; import "envoy/annotations/deprecation.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; @@ -65,6 +66,7 @@ message VirtualHost { // The logical name of the virtual host. This is used when emitting certain // statistics but is not relevant for routing. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1}]; // A list of domains (host/authority header) that will be matched to this @@ -85,6 +87,7 @@ message VirtualHost { // must be unique across all virtual hosts or the config will fail to load. // // Domains cannot contain control characters. This is validated by the well_known_regex HTTP_HEADER_VALUE. + option (armeria.xds.supported.field) = 2; repeated string domains = 2 [(validate.rules).repeated = { min_items: 1 items {string {well_known_regex: HTTP_HEADER_VALUE strict: false}} @@ -93,6 +96,7 @@ message VirtualHost { // The list of routes that will be matched, in order, for incoming requests. // The first route that matches will be used. // Only one of this and ``matcher`` can be specified. + option (armeria.xds.supported.field) = 3; repeated Route routes = 3 [(udpa.annotations.field_migrate).oneof_promotion = "route_selection"]; // The match tree to use when resolving route actions for incoming requests. Only one of this and ``routes`` @@ -161,6 +165,7 @@ message VirtualHost { // [#comment: An entry's value may be wrapped in a // :ref:`FilterConfig` // message to specify additional options.] + option (armeria.xds.supported.field) = 15; map typed_per_filter_config = 15; // Decides whether the :ref:`x-envoy-attempt-count @@ -194,6 +199,7 @@ message VirtualHost { // Indicates the retry policy for all routes in this virtual host. Note that setting a // route level entry will take precedence over this config and it'll be treated // independently (e.g., values are not inherited). + option (armeria.xds.supported.field) = 16; RetryPolicy retry_policy = 16; // [#not-implemented-hide:] @@ -287,15 +293,18 @@ message Route { reserved "per_filter_config"; // Name for the route. + option (armeria.xds.supported.field) = 14; string name = 14; // Route matching parameters. + option (armeria.xds.supported.field) = 1; RouteMatch match = 1 [(validate.rules).message = {required: true}]; oneof action { option (validate.required) = true; // Route request to some upstream cluster. + option (armeria.xds.supported.oneof_field) = 2; RouteAction route = 2; // Return a redirect. @@ -336,6 +345,7 @@ message Route { // [#comment: An entry's value may be wrapped in a // :ref:`FilterConfig` // message to specify additional options.] + option (armeria.xds.supported.field) = 13; map typed_per_filter_config = 13; // Specifies a set of headers that will be added to requests matching this @@ -623,10 +633,12 @@ message RouteMatch { // If specified, the route is a prefix rule meaning that the prefix must // match the beginning of the ``:path`` header. + option (armeria.xds.supported.oneof_field) = 1; string prefix = 1; // If specified, the route is an exact path rule meaning that the path must // exactly match the ``:path`` header once the query string is removed. + option (armeria.xds.supported.oneof_field) = 2; string path = 2; // If specified, the route is a regular expression rule meaning that the @@ -641,6 +653,7 @@ message RouteMatch { // path_specifier entirely and just rely on a set of header matchers which can already match // on :path, etc. The issue with that is it is unclear how to generically deal with query string // stripping. This needs more thought.] + option (armeria.xds.supported.oneof_field) = 10; type.matcher.v3.RegexMatcher safe_regex = 10 [(validate.rules).message = {required: true}]; // If this is used as the matcher, the matcher will only match CONNECT or CONNECT-UDP requests. @@ -662,6 +675,7 @@ message RouteMatch { // but would not match ``/api/developer`` // // Expect the value to not contain ``?`` or ``#`` and not to end in ``/`` + option (armeria.xds.supported.oneof_field) = 14; string path_separated_prefix = 14 [(validate.rules).string = {pattern: "^[^?#]+[^?#/]$"}]; // [#extension-category: envoy.path.match] @@ -670,6 +684,7 @@ message RouteMatch { // Indicates that prefix/path matching should be case-sensitive. The default // is true. Ignored for safe_regex matching. + option (armeria.xds.supported.field) = 4; google.protobuf.BoolValue case_sensitive = 4; // Indicates that the route should additionally match on a runtime key. Every time the route @@ -695,6 +710,7 @@ message RouteMatch { // config. A match will happen if all the headers in the route are present in // the request with the same values (or based on presence if the value field // is not in the config). + option (armeria.xds.supported.field) = 6; repeated HeaderMatcher headers = 6; // Specifies a set of URL query parameters on which the route should @@ -711,11 +727,13 @@ message RouteMatch { // is used, the transcoded message fields may be different. The query parameters are // URL-encoded, but the message fields are not. For example, if a query // parameter is "foo%20bar", the message field will be "foo bar". + option (armeria.xds.supported.field) = 7; repeated QueryParameterMatcher query_parameters = 7; // If specified, only gRPC requests will be matched. The router will check // that the ``Content-Type`` header has ``application/grpc`` or one of the various // ``application/grpc+`` values. + option (armeria.xds.supported.field) = 8; GrpcRouteMatchOptions grpc = 8; // If specified, the client tls context will be matched against the defined @@ -1127,6 +1145,7 @@ message RouteAction { // Indicates the upstream cluster to which the request should be routed // to. + option (armeria.xds.supported.oneof_field) = 1; string cluster = 1 [(validate.rules).string = {min_len: 1}]; // Envoy will determine the cluster to route to by reading the value of the @@ -1173,6 +1192,7 @@ message RouteAction { // for load balancing. If using :ref:`weighted_clusters // `, metadata will be merged, with values // provided there taking precedence. The filter name should be specified as ``envoy.lb``. + option (armeria.xds.supported.field) = 4; core.v3.Metadata metadata_match = 4; // Indicates that during forwarding, the matched prefix (or path) should be @@ -1324,6 +1344,7 @@ message RouteAction { // :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, // :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the // :ref:`retry overview `. + option (armeria.xds.supported.field) = 8; google.protobuf.Duration timeout = 8; // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, @@ -1378,6 +1399,7 @@ message RouteAction { // Indicates that the route has a retry policy. Note that if this is set, // it'll take precedence over the virtual host level retry policy entirely // (e.g., policies are not merged, the most internal one becomes the enforced policy). + option (armeria.xds.supported.field) = 9; RetryPolicy retry_policy = 9; // [#not-implemented-hide:] @@ -1557,6 +1579,7 @@ message RetryPolicy { // than zero. Values less than 1 ms are rounded up to 1 ms. // See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's // back-off algorithm. + option (armeria.xds.supported.field) = 1; google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gt {} @@ -1566,6 +1589,7 @@ message RetryPolicy { // greater than or equal to the ``base_interval`` if set. The default is 10 times the // ``base_interval``. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion // of Envoy's back-off algorithm. + option (armeria.xds.supported.field) = 2; google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {}}]; } @@ -1575,6 +1599,7 @@ message RetryPolicy { // .. note:: // // If the header appears multiple times only the first value is used. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: false}]; @@ -1630,6 +1655,7 @@ message RetryPolicy { // to match against the response. Headers are tried in order, and matched case // insensitive. The first header to be parsed successfully is used. If no headers // match the default exponential back-off is used instead. + option (armeria.xds.supported.field) = 1; repeated ResetHeader reset_headers = 1 [(validate.rules).repeated = {min_items: 1}]; // Specifies the maximum back off interval that Envoy will allow. If a reset @@ -1637,17 +1663,20 @@ message RetryPolicy { // the next header will be tried. // // Defaults to 300 seconds. + option (armeria.xds.supported.field) = 2; google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {}}]; } // Specifies the conditions under which retry takes place. These are the same // conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and // :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. + option (armeria.xds.supported.field) = 1; string retry_on = 1; // Specifies the allowed number of retries. This parameter is optional and // defaults to 1. These are the same conditions documented for // :ref:`config_http_filters_router_x-envoy-max-retries`. + option (armeria.xds.supported.field) = 2; google.protobuf.UInt32Value num_retries = 2 [(udpa.annotations.field_migrate).rename = "max_retries"]; @@ -1662,6 +1691,7 @@ message RetryPolicy { // Consequently, when using a :ref:`5xx ` based // retry policy, a request that times out will not be retried as the total timeout budget // would have been exhausted. + option (armeria.xds.supported.field) = 3; google.protobuf.Duration per_try_timeout = 3; // Specifies an upstream idle timeout per retry attempt (including the initial attempt). This @@ -1707,6 +1737,7 @@ message RetryPolicy { int64 host_selection_retry_max_attempts = 6; // HTTP status codes that should trigger a retry in addition to those specified by retry_on. + option (armeria.xds.supported.field) = 7; repeated uint32 retriable_status_codes = 7; // Specifies parameters that control exponential retry back off. This parameter is optional, in which case the @@ -1714,6 +1745,7 @@ message RetryPolicy { // ``upstream.base_retry_backoff_ms`` runtime parameter. The default maximum interval is 10 times // the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` // describes Envoy's back-off algorithm. + option (armeria.xds.supported.field) = 8; RetryBackOff retry_back_off = 8; // Specifies parameters that control a retry back-off strategy that is used @@ -1723,14 +1755,17 @@ message RetryPolicy { // configured, this back-off strategy will be used instead of the // default exponential back off strategy (configured using ``retry_back_off``) // whenever a response includes the matching headers. + option (armeria.xds.supported.field) = 11; RateLimitedRetryBackOff rate_limited_retry_back_off = 11; // HTTP response headers that trigger a retry if present in the response. A retry will be // triggered if any of the header matches match the upstream response headers. // The field is only consulted if 'retriable-headers' retry policy is active. + option (armeria.xds.supported.field) = 9; repeated HeaderMatcher retriable_headers = 9; // HTTP headers which must be present in the request for retries to be attempted. + option (armeria.xds.supported.field) = 10; repeated HeaderMatcher retriable_request_headers = 10; } @@ -2481,6 +2516,7 @@ message HeaderMatcher { reserved "regex_match"; // Specifies the name of the header in the request. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: false}]; @@ -2515,10 +2551,12 @@ message HeaderMatcher { // // * For range [-10,0), route will match for header value -1, but not for 0, ``somestring``, 10.9, // ``-1somestring`` + option (armeria.xds.supported.oneof_field) = 6; type.v3.Int64Range range_match = 6; // If specified as true, header match will be performed based on whether the header is in the // request. If specified as false, header match will be performed based on whether the header is absent. + option (armeria.xds.supported.oneof_field) = 7; bool present_match = 7; // If specified, header match will be performed based on the prefix of the header value. @@ -2580,6 +2618,7 @@ message HeaderMatcher { ]; // If specified, header match will be performed based on the string match of the header value. + option (armeria.xds.supported.oneof_field) = 13; type.matcher.v3.StringMatcher string_match = 13; } @@ -2591,6 +2630,7 @@ message HeaderMatcher { // // * The regex ``\d{3}`` does not match the value ``1234``, so it will match when inverted. // * The range [-10,0) will match the value -1, so it will not match when inverted. + option (armeria.xds.supported.field) = 8; bool invert_match = 8; // If specified, for any header match rule, if the header match rule specified header @@ -2621,6 +2661,7 @@ message HeaderMatcher { // :ref:`treat_missing_header_as_empty ` // is set to false; The "header4" header is not present. // The match rule for "header4" will be ignored so it will not match. + option (armeria.xds.supported.field) = 14; bool treat_missing_header_as_empty = 14; } @@ -2637,13 +2678,16 @@ message QueryParameterMatcher { // Specifies the name of a key that must be present in the requested // ``path``'s query string. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1 max_bytes: 1024}]; oneof query_parameter_match_specifier { // Specifies whether a query parameter value should match against a string. + option (armeria.xds.supported.oneof_field) = 5; type.matcher.v3.StringMatcher string_match = 5 [(validate.rules).message = {required: true}]; // Specifies whether a query parameter should be present. + option (armeria.xds.supported.oneof_field) = 6; bool present_match = 6; } } diff --git a/xds-api/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/xds-api/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 730e065e6c4..1c2c60a03a6 100644 --- a/xds-api/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/xds-api/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -27,6 +27,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3"; option java_outer_classname = "HttpConnectionManagerProto"; option java_multiple_files = true; @@ -413,15 +415,18 @@ message HttpConnectionManager { // The human readable prefix to use when emitting statistics for the // connection manager. See the :ref:`statistics documentation ` for // more information. + option (armeria.xds.supported.field) = 2; string stat_prefix = 2 [(validate.rules).string = {min_len: 1}]; oneof route_specifier { option (validate.required) = true; // The connection manager’s route table will be dynamically loaded via the RDS API. + option (armeria.xds.supported.oneof_field) = 3; Rds rds = 3; // The route table for the connection manager is static and is specified in this property. + option (armeria.xds.supported.oneof_field) = 4; config.route.v3.RouteConfiguration route_config = 4; // A route table will be dynamically assigned to each request based on request attributes @@ -433,6 +438,7 @@ message HttpConnectionManager { // A list of individual HTTP filters that make up the filter chain for // requests made to the connection manager. :ref:`Order matters ` // as the filters are processed sequentially as request events happen. + option (armeria.xds.supported.field) = 5; repeated HttpFilter http_filters = 5; // Whether the connection manager manipulates the :ref:`config_http_conn_man_headers_user-agent` @@ -1046,12 +1052,14 @@ message Rds { "envoy.config.filter.network.http_connection_manager.v2.Rds"; // Configuration source specifier for RDS. + option (armeria.xds.supported.field) = 1; config.core.v3.ConfigSource config_source = 1; // The name of the route configuration. This name will be passed to the RDS // API. This allows an Envoy configuration with multiple HTTP listeners (and // associated HTTP connection manager filters) to use different route // configurations. + option (armeria.xds.supported.field) = 2; string route_config_name = 2; } @@ -1220,6 +1228,7 @@ message HttpFilter { reserved "config"; // The name of the filter configuration. It also serves as a resource name in ExtensionConfigDS. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1}]; oneof config_type { @@ -1230,6 +1239,7 @@ message HttpFilter { // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. // [#extension-category: envoy.filters.http] + option (armeria.xds.supported.oneof_field) = 4; google.protobuf.Any typed_config = 4; // Configuration source specifier for an extension configuration discovery service. @@ -1246,6 +1256,7 @@ message HttpFilter { // If true, clients that do not support this filter may ignore the // filter but otherwise accept the config. // Otherwise, clients that do not support this filter must reject the config. + option (armeria.xds.supported.field) = 6; bool is_optional = 6; // If true, the filter is disabled by default and must be explicitly enabled by setting diff --git a/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto b/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto index 9bc5fb5d029..8ae8ec82844 100644 --- a/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -16,6 +16,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v3"; option java_outer_classname = "CommonProto"; option java_multiple_files = true; @@ -217,6 +219,7 @@ message TlsCertificate { // parent directory for any file moves to support rotation. This currently // only applies to dynamic secrets, when the ``TlsCertificate`` is delivered via // SDS. + option (armeria.xds.supported.field) = 1; config.core.v3.DataSource certificate_chain = 1; // The TLS private key. @@ -224,6 +227,7 @@ message TlsCertificate { // If ``private_key`` is a filesystem path, a watch will be added to the parent // directory for any file moves to support rotation. This currently only // applies to dynamic secrets, when the ``TlsCertificate`` is delivered via SDS. + option (armeria.xds.supported.field) = 2; config.core.v3.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; // ``Pkcs12`` data containing TLS certificate, chain, and private key. @@ -251,6 +255,7 @@ message TlsCertificate { // specified. This only applies when a ``TlsCertificate`` is delivered by SDS // with references to filesystem paths. See the :ref:`SDS key rotation // ` documentation for further details. + option (armeria.xds.supported.field) = 7; config.core.v3.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key @@ -265,6 +270,7 @@ message TlsCertificate { // The password to decrypt the TLS private key. If this field is not set, it is assumed that the // TLS private key is not password encrypted. + option (armeria.xds.supported.field) = 3; config.core.v3.DataSource password = 3 [(udpa.annotations.sensitive) = true]; // The OCSP response to be stapled with this certificate during the handshake. @@ -333,14 +339,19 @@ message SubjectAltNameMatcher { // against. enum SanType { SAN_TYPE_UNSPECIFIED = 0; + option (armeria.xds.supported.enum_value) = 1; EMAIL = 1; + option (armeria.xds.supported.enum_value) = 2; DNS = 2; + option (armeria.xds.supported.enum_value) = 3; URI = 3; + option (armeria.xds.supported.enum_value) = 4; IP_ADDRESS = 4; OTHER_NAME = 5; } // Specification of type of SAN. Note that the default enum value is an invalid choice. + option (armeria.xds.supported.field) = 1; SanType san_type = 1 [(validate.rules).enum = {defined_only: true not_in: 0}]; // Matcher for SAN value. @@ -359,6 +370,7 @@ message SubjectAltNameMatcher { // * INTEGER/ENUMERATED: Validated against a string containing the integer value // * NULL: Validated against an empty string // * Other types: Validated directly against the string value + option (armeria.xds.supported.field) = 2; type.matcher.v3.StringMatcher matcher = 2 [(validate.rules).message = {required: true}]; // OID Value which is required if OTHER_NAME SAN type is used. @@ -428,6 +440,7 @@ message CertificateValidationContext { // of a full chain. // // If ``ca_certificate_provider_instance`` is set, it takes precedence over ``trusted_ca``. + option (armeria.xds.supported.field) = 1; config.core.v3.DataSource trusted_ca = 1 [(udpa.annotations.field_migrate).oneof_promotion = "ca_cert_source"]; @@ -442,6 +455,7 @@ message CertificateValidationContext { // If present, system root certs are used only if neither of the ``trusted_ca`` // or ``ca_certificate_provider_instance`` fields are set. // [#not-implemented-hide:] + option (armeria.xds.supported.field) = 17; SystemRootCerts system_root_certs = 17; // If specified, updates of a file-based ``trusted_ca`` source will be triggered @@ -451,6 +465,7 @@ message CertificateValidationContext { // ``CertificateValidationContext`` is delivered by SDS with references to // filesystem paths. See the :ref:`SDS key rotation ` // documentation for further details. + option (armeria.xds.supported.field) = 11; config.core.v3.WatchedDirectory watched_directory = 11; // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the @@ -483,6 +498,7 @@ message CertificateValidationContext { // `, // because SPKI is tied to a private key, so it doesn't change when the certificate // is renewed using the same private key. + option (armeria.xds.supported.field) = 3; repeated string verify_certificate_spki = 3 [(validate.rules).repeated = {items {string {min_len: 44 max_bytes: 44}}}]; @@ -512,6 +528,7 @@ message CertificateValidationContext { // :ref:`verify_certificate_spki // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. + option (armeria.xds.supported.field) = 2; repeated string verify_certificate_hash = 2 [(validate.rules).repeated = {items {string {min_len: 64 max_bytes: 95}}}]; @@ -537,6 +554,7 @@ message CertificateValidationContext { // Subject Alternative Names are easily spoofable and verifying only them is insecure, // therefore this option must be used together with :ref:`trusted_ca // `. + option (armeria.xds.supported.field) = 15; repeated SubjectAltNameMatcher match_typed_subject_alt_names = 15; // This field is deprecated in favor of @@ -545,6 +563,7 @@ message CertificateValidationContext { // Note that if both this field and :ref:`match_typed_subject_alt_names // ` // are specified, the former (deprecated field) is ignored. + option (armeria.xds.supported.field) = 9; repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; diff --git a/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto b/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto index 94660e2da9f..9f40df09c56 100644 --- a/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto +++ b/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto @@ -11,6 +11,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v3"; option java_outer_classname = "SecretProto"; option java_multiple_files = true; @@ -37,8 +39,10 @@ message SdsSecretConfig { // Name by which the secret can be uniquely referred to. When both name and config are specified, // then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret // will be loaded from static resources. + option (armeria.xds.supported.field) = 1; string name = 1 [(validate.rules).string = {min_len: 1}]; + option (armeria.xds.supported.field) = 2; config.core.v3.ConfigSource sds_config = 2; } @@ -47,13 +51,16 @@ message Secret { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.Secret"; // Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + option (armeria.xds.supported.field) = 1; string name = 1; oneof type { + option (armeria.xds.supported.oneof_field) = 2; TlsCertificate tls_certificate = 2; TlsSessionTicketKeys session_ticket_keys = 3; + option (armeria.xds.supported.oneof_field) = 4; CertificateValidationContext validation_context = 4; GenericSecret generic_secret = 5; diff --git a/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto b/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto index b292b18c45d..ad7806ddc54 100644 --- a/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto +++ b/xds-api/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto @@ -15,6 +15,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v3"; option java_outer_classname = "TlsProto"; option java_multiple_files = true; @@ -36,9 +38,11 @@ message UpstreamTlsContext { // // Server certificate verification is not enabled by default. To enable verification, configure // :ref:`trusted_ca`. + option (armeria.xds.supported.field) = 1; CommonTlsContext common_tls_context = 1; // SNI string to use when creating TLS backend connections. + option (armeria.xds.supported.field) = 2; string sni = 2 [(validate.rules).string = {max_bytes: 255}]; // If true, replaces the SNI for the connection with the hostname of the upstream host, if @@ -244,11 +248,13 @@ message CommonTlsContext { "envoy.api.v2.auth.CommonTlsContext.CombinedCertificateValidationContext"; // How to validate peer certificates. + option (armeria.xds.supported.field) = 1; CertificateValidationContext default_validation_context = 1 [(validate.rules).message = {required: true}]; // Config for fetching validation context via SDS API. Note SDS API allows certificates to be // fetched/refreshed over the network asynchronously with respect to the TLS handshake. + option (armeria.xds.supported.field) = 2; SdsSecretConfig validation_context_sds_secret_config = 2 [(validate.rules).message = {required: true}]; @@ -276,6 +282,7 @@ message CommonTlsContext { // // If ``tls_certificate_provider_instance`` is set, this field is ignored. // If this field is set, ``tls_certificate_sds_secret_configs`` is ignored. + option (armeria.xds.supported.field) = 2; repeated TlsCertificate tls_certificates = 2; // Configs for fetching TLS certificates via SDS API. Note SDS API allows certificates to be @@ -286,6 +293,7 @@ message CommonTlsContext { // // If ``tls_certificates`` or ``tls_certificate_provider_instance`` are set, this field // is ignored. + option (armeria.xds.supported.field) = 6; repeated SdsSecretConfig tls_certificate_sds_secret_configs = 6; // Certificate provider instance for fetching TLS certs. @@ -314,10 +322,12 @@ message CommonTlsContext { oneof validation_context_type { // How to validate peer certificates. + option (armeria.xds.supported.oneof_field) = 3; CertificateValidationContext validation_context = 3; // Config for fetching validation context via SDS API. Note SDS API allows certificates to be // fetched/refreshed over the network asynchronously with respect to the TLS handshake. + option (armeria.xds.supported.oneof_field) = 7; SdsSecretConfig validation_context_sds_secret_config = 7; // Combines the default ``CertificateValidationContext`` with the SDS-provided dynamic context for certificate @@ -331,6 +341,7 @@ message CommonTlsContext { // * **Boolean Fields:** Boolean fields are combined using a logical OR operation. // // The resulting ``CertificateValidationContext`` is used to perform certificate validation. + option (armeria.xds.supported.oneof_field) = 8; CombinedCertificateValidationContext combined_validation_context = 8; // Certificate provider for fetching validation context. diff --git a/xds-api/src/main/proto/envoy/type/matcher/v3/regex.proto b/xds-api/src/main/proto/envoy/type/matcher/v3/regex.proto index 10b3970ff84..26d4d074bc6 100644 --- a/xds-api/src/main/proto/envoy/type/matcher/v3/regex.proto +++ b/xds-api/src/main/proto/envoy/type/matcher/v3/regex.proto @@ -4,6 +4,7 @@ package envoy.type.matcher.v3; import "google/protobuf/wrappers.proto"; +import "armeria/xds/supported.proto"; import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -63,6 +64,7 @@ message RegexMatcher { // The regex match string. The string must be supported by the configured engine. The regex is matched // against the full string, not as a partial match. + option (armeria.xds.supported.field) = 2; string regex = 2 [(validate.rules).string = {min_len: 1}]; } diff --git a/xds-api/src/main/proto/envoy/type/matcher/v3/string.proto b/xds-api/src/main/proto/envoy/type/matcher/v3/string.proto index 56d39565ca5..5eead08dbb1 100644 --- a/xds-api/src/main/proto/envoy/type/matcher/v3/string.proto +++ b/xds-api/src/main/proto/envoy/type/matcher/v3/string.proto @@ -6,6 +6,7 @@ import "envoy/type/matcher/v3/regex.proto"; import "xds/core/v3/extension.proto"; +import "armeria/xds/supported.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -35,6 +36,7 @@ message StringMatcher { // Examples: // // * ``abc`` only matches the value ``abc``. + option (armeria.xds.supported.oneof_field) = 1; string exact = 1; // The input string must have the prefix specified here. @@ -46,6 +48,7 @@ message StringMatcher { // Examples: // // * ``abc`` matches the value ``abc.xyz`` + option (armeria.xds.supported.oneof_field) = 2; string prefix = 2 [(validate.rules).string = {min_len: 1}]; // The input string must have the suffix specified here. @@ -57,9 +60,11 @@ message StringMatcher { // Examples: // // * ``abc`` matches the value ``xyz.abc`` + option (armeria.xds.supported.oneof_field) = 3; string suffix = 3 [(validate.rules).string = {min_len: 1}]; // The input string must match the regular expression specified here. + option (armeria.xds.supported.oneof_field) = 5; RegexMatcher safe_regex = 5 [(validate.rules).message = {required: true}]; // The input string must have the substring specified here. @@ -71,6 +76,7 @@ message StringMatcher { // Examples: // // * ``abc`` matches the value ``xyz.abc.def`` + option (armeria.xds.supported.oneof_field) = 7; string contains = 7 [(validate.rules).string = {min_len: 1}]; // Use an extension as the matcher type. @@ -82,6 +88,7 @@ message StringMatcher { // has no effect for the ``safe_regex`` match. // For example, the matcher ``data`` will match both input string ``Data`` and ``data`` if this option // is set to ``true``. + option (armeria.xds.supported.field) = 6; bool ignore_case = 6; } diff --git a/xds-api/src/main/proto/envoy/type/v3/percent.proto b/xds-api/src/main/proto/envoy/type/v3/percent.proto index e041ecddc78..a93e0fe5441 100644 --- a/xds-api/src/main/proto/envoy/type/v3/percent.proto +++ b/xds-api/src/main/proto/envoy/type/v3/percent.proto @@ -6,6 +6,8 @@ import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; +import "armeria/xds/supported.proto"; + option java_package = "io.envoyproxy.envoy.type.v3"; option java_outer_classname = "PercentProto"; option java_multiple_files = true; @@ -18,6 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; message Percent { option (udpa.annotations.versioning).previous_message_type = "envoy.type.Percent"; + option (armeria.xds.supported.field) = 1; double value = 1 [(validate.rules).double = {lte: 100.0 gte: 0.0}]; } diff --git a/xds-api/src/main/proto/envoy/type/v3/range.proto b/xds-api/src/main/proto/envoy/type/v3/range.proto index 3b1af814858..ebcb33292f0 100644 --- a/xds-api/src/main/proto/envoy/type/v3/range.proto +++ b/xds-api/src/main/proto/envoy/type/v3/range.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.type.v3; +import "armeria/xds/supported.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -19,9 +20,11 @@ message Int64Range { option (udpa.annotations.versioning).previous_message_type = "envoy.type.Int64Range"; // start of the range (inclusive) + option (armeria.xds.supported.field) = 1; int64 start = 1; // end of the range (exclusive) + option (armeria.xds.supported.field) = 2; int64 end = 2; } diff --git a/xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java b/xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java index 1f5c7bc1d05..113ce3d5793 100644 --- a/xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java +++ b/xds-api/src/test/java/com/linecorp/armeria/xds/api/SupportedFieldValidatorTest.java @@ -33,6 +33,7 @@ import com.linecorp.armeria.xds.api.testing.TestDiscoveryType; import com.linecorp.armeria.xds.api.testing.TestEdsConfig; import com.linecorp.armeria.xds.api.testing.TestOutlierDetection; +import com.linecorp.armeria.xds.validator.XdsValidationException; class SupportedFieldValidatorTest { @@ -144,7 +145,7 @@ void rejectHandler() { .setUnsupportedField("bad") .build(); assertThatThrownBy(() -> validator.validate(cluster)) - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(XdsValidationException.class) .hasMessageContaining("$.unsupportedField"); } @@ -217,7 +218,7 @@ void rejectFailsFast() { .build()) .build(); assertThatThrownBy(() -> validator.validate(cluster)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(XdsValidationException.class); } @Test diff --git a/xds-api/src/test/proto/armeria/xds/testing/test_supported.proto b/xds-api/src/test/proto/armeria/xds/testing/test_supported.proto index 318d622633f..d34a3ec7ee1 100644 --- a/xds-api/src/test/proto/armeria/xds/testing/test_supported.proto +++ b/xds-api/src/test/proto/armeria/xds/testing/test_supported.proto @@ -10,25 +10,35 @@ import "google/protobuf/wrappers.proto"; import "google/protobuf/duration.proto"; message TestCluster { - string name = 1 [(armeria.xds.supported) = true]; - TestDiscoveryType type = 2 [(armeria.xds.supported) = true]; - TestEdsConfig eds_config = 3 [(armeria.xds.supported) = true]; + option (armeria.xds.supported.field) = 1; + string name = 1; + option (armeria.xds.supported.field) = 2; + TestDiscoveryType type = 2; + option (armeria.xds.supported.field) = 3; + TestEdsConfig eds_config = 3; string unsupported_field = 4; TestOutlierDetection outlier_detection = 5; - google.protobuf.Any typed_config = 6 [(armeria.xds.supported) = true]; - google.protobuf.UInt32Value max_requests = 7 [(armeria.xds.supported) = true]; - google.protobuf.Duration timeout = 8 [(armeria.xds.supported) = true]; - map type_map = 9 [(armeria.xds.supported) = true]; + option (armeria.xds.supported.field) = 6; + google.protobuf.Any typed_config = 6; + option (armeria.xds.supported.field) = 7; + google.protobuf.UInt32Value max_requests = 7; + option (armeria.xds.supported.field) = 8; + google.protobuf.Duration timeout = 8; + option (armeria.xds.supported.field) = 9; + map type_map = 9; } enum TestDiscoveryType { - STATIC = 0 [(armeria.xds.supported_value) = true]; - EDS = 1 [(armeria.xds.supported_value) = true]; + option (armeria.xds.supported.enum_value) = 0; + STATIC = 0; + option (armeria.xds.supported.enum_value) = 1; + EDS = 1; LOGICAL_DNS = 2; } message TestEdsConfig { - string service_name = 1 [(armeria.xds.supported) = true]; + option (armeria.xds.supported.field) = 1; + string service_name = 1; string unsupported_nested = 2; } diff --git a/xds-validator/build.gradle b/xds-validator/build.gradle index 6aee4715c99..a5692305753 100644 --- a/xds-validator/build.gradle +++ b/xds-validator/build.gradle @@ -1,3 +1,7 @@ +dependencies { + api libs.protobuf.java +} + tasks.withType(Test).configureEach { failOnNoDiscoveredTests = false } diff --git a/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidationException.java b/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidationException.java new file mode 100644 index 00000000000..504b91c44b9 --- /dev/null +++ b/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidationException.java @@ -0,0 +1,69 @@ +/* + * Copyright 2026 LY Corporation + * + * LY Corporation licenses this file to you 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: + * + * https://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 com.linecorp.armeria.xds.validator; + +import static java.util.Objects.requireNonNull; + +import com.google.protobuf.Message; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +/** + * A {@link RuntimeException} raised when an xDS resource fails validation. + */ +@UnstableApi +public final class XdsValidationException extends RuntimeException { + + private static final long serialVersionUID = -2825861127354315199L; + + /** + * Returns a new {@link XdsValidationException} for the specified {@code resource} and {@code cause}. + */ + public static XdsValidationException of(Message resource, Throwable cause) { + requireNonNull(resource, "resource"); + requireNonNull(cause, "cause"); + return new XdsValidationException( + resource.getDescriptorForType().getFullName() + ": " + cause.getMessage(), cause); + } + + /** + * Returns a new {@link XdsValidationException} for the specified {@code resource} with + * the specified detail {@code message}. + */ + public static XdsValidationException of(Message resource, String message) { + requireNonNull(resource, "resource"); + requireNonNull(message, "message"); + return new XdsValidationException( + resource.getDescriptorForType().getFullName() + ": " + message); + } + + /** + * Returns a new {@link XdsValidationException} with the specified detail {@code message}. + */ + public static XdsValidationException of(String message) { + requireNonNull(message, "message"); + return new XdsValidationException(message); + } + + private XdsValidationException(String message) { + super(message); + } + + private XdsValidationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java b/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java index 957f9d615b4..25877dd31d9 100644 --- a/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java +++ b/xds-validator/src/main/java/com/linecorp/armeria/xds/validator/XdsValidatorIndex.java @@ -16,6 +16,8 @@ package com.linecorp.armeria.xds.validator; +import com.google.protobuf.Message; + import com.linecorp.armeria.common.annotation.UnstableApi; /** @@ -28,7 +30,7 @@ public interface XdsValidatorIndex { /** * Validates whether the specified message is valid. */ - void assertValid(Object message); + void assertValid(Message message); /** * The priority this validator will have. The validator with the highest priority diff --git a/xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java b/xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java index 2cadbdc9351..e3ec5a4d7f1 100644 --- a/xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java +++ b/xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java @@ -83,7 +83,7 @@ XdsResourceValidator validator() { * Validates the given message using both pgv structural validation and supported-field * validation. */ - void assertValid(Object message) { + void assertValid(Message message) { validator.assertValid(message); } diff --git a/xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java b/xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java index f290e510877..29ffefd078c 100644 --- a/xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java +++ b/xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java @@ -35,14 +35,14 @@ * *

Validation is performed at exactly two levels: *

    - *
  • Static resources — {@link XdsBootstrapImpl} calls {@link #assertValid(Object)} once + *
  • Static resources — {@link XdsBootstrapImpl} calls {@link #assertValid(Message)} once * on the entire {@code Bootstrap} message at construction time. Both pgv and supported-field * validators recurse into nested messages, so this single call covers all static clusters, * listeners, secrets, and their sub-messages. Inline sub-resources (e.g. {@code VirtualHost} * within a {@code RouteConfiguration}, {@code ClusterLoadAssignment} within a {@code Cluster}) * are covered by parent validation and do not need separate calls.
  • *
  • Dynamic resources — calls - * {@link #assertValid(Object)} on each top-level resource unpacked from a + * {@link #assertValid(Message)} on each top-level resource unpacked from a * {@code DiscoveryResponse}. Validation failures are caught and reported as invalid * resources (NACK'd back to the control plane).
  • *
@@ -66,7 +66,7 @@ public final class XdsResourceValidator { /** * Validates the given message using the SPI-loaded {@link XdsValidatorIndex}. */ - void assertValid(Object message) { + void assertValid(Message message) { requireNonNull(message, "message"); spiValidator.assertValid(message); } diff --git a/xds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.java b/xds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.java index e2907ae8161..f8e21d49afd 100644 --- a/xds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.java +++ b/xds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.java @@ -23,6 +23,8 @@ import com.google.protobuf.Any; +import com.linecorp.armeria.xds.validator.XdsValidationException; + import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.route.v3.VirtualHost; import io.envoyproxy.pgv.ValidationException; @@ -34,7 +36,7 @@ void pgvValidationIsEnabled() { final XdsResourceValidator validator = new XdsResourceValidator(); final VirtualHost virtualHost = VirtualHost.getDefaultInstance(); assertThatThrownBy(() -> validator.assertValid(virtualHost)) - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(XdsValidationException.class) .cause() .isInstanceOf(ValidationException.class) .hasMessageContaining("length must be at least 1 but got: 0"); @@ -66,6 +68,6 @@ void unpackFailsOnInvalidMessage() { final VirtualHost vhost = VirtualHost.getDefaultInstance(); final Any packed = Any.pack(vhost); assertThatThrownBy(() -> validator.unpack(packed, VirtualHost.class)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(XdsValidationException.class); } }