xds: include a status for the result of ConfigSelector (#7260)

diff --git a/api/src/main/java/io/grpc/InternalConfigSelector.java b/api/src/main/java/io/grpc/InternalConfigSelector.java
index 751ff01..c2d204c 100644
--- a/api/src/main/java/io/grpc/InternalConfigSelector.java
+++ b/api/src/main/java/io/grpc/InternalConfigSelector.java
@@ -16,7 +16,9 @@
 
 package io.grpc;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
 import javax.annotation.Nullable;
 
@@ -37,21 +39,43 @@
   public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args);
 
   public static final class Result {
+    private final Status status;
+    @Nullable
     private final Object config;
+    @Nullable
     private final CallOptions callOptions;
     @Nullable
     private final Runnable committedCallback;
 
-    private Result(Object config, CallOptions callOptions, @Nullable Runnable committedCallback) {
-      this.config = checkNotNull(config, "config");
-      this.callOptions = checkNotNull(callOptions, "callOptions");
+    private Result(
+        Status status, Object config, CallOptions callOptions, Runnable committedCallback) {
+      this.status = checkNotNull(status, "status");
+      this.config = config;
+      this.callOptions = callOptions;
       this.committedCallback = committedCallback;
     }
 
     /**
+     * Creates a {@code Result} with the given error status.
+     */
+    public static Result forError(Status status) {
+      checkArgument(!status.isOk(), "status is OK");
+      return new Result(status, null, null, null);
+    }
+
+    /**
+     * Returns the status of the config selection operation. If status is not {@link Status#OK},
+     * this result should not be used.
+     */
+    public Status getStatus() {
+      return status;
+    }
+
+    /**
      * Returns a parsed config. Must have been returned via
      * ServiceConfigParser.parseServiceConfig().getConfig()
      */
+    @Nullable
     public Object getConfig() {
       return config;
     }
@@ -59,6 +83,7 @@
     /**
      * Returns a config-selector-modified CallOptions for the RPC.
      */
+    @Nullable
     public CallOptions getCallOptions() {
       return callOptions;
     }
@@ -112,8 +137,13 @@
         return this;
       }
 
+      /**
+       * Build this {@link Result}.
+       */
       public Result build() {
-        return new Result(config, callOptions, committedCallback);
+        checkState(config != null, "config is not set");
+        checkState(callOptions != null, "callOptions is not set");
+        return new Result(Status.OK, config, callOptions, committedCallback);
       }
     }
   }
diff --git a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java
index 37ab2e5..f094065 100644
--- a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java
+++ b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import io.grpc.InternalConfigSelector.Result;
+import io.grpc.Status.Code;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -37,6 +39,7 @@
 
     InternalConfigSelector.Result result =
         builder.setConfig(config).setCallOptions(callOptions).build();
+    assertThat(result.getStatus().isOk()).isTrue();
     assertThat(result.getConfig()).isEqualTo(config);
     assertThat(result.getCallOptions()).isEqualTo(callOptions);
     assertThat(result.getCommittedCallback()).isNull();
@@ -46,8 +49,17 @@
         .setCallOptions(callOptions)
         .setCommittedCallback(committedCallback)
         .build();
+    assertThat(result.getStatus().isOk()).isTrue();
     assertThat(result.getConfig()).isEqualTo(config);
     assertThat(result.getCallOptions()).isEqualTo(callOptions);
     assertThat(result.getCommittedCallback()).isSameInstanceAs(committedCallback);
   }
+
+  @Test
+  public void errorResult() {
+    Result result = Result.forError(Status.INTERNAL.withDescription("failed"));
+    assertThat(result.getStatus().isOk()).isFalse();
+    assertThat(result.getStatus().getCode()).isEqualTo(Code.INTERNAL);
+    assertThat(result.getStatus().getDescription()).isEqualTo("failed");
+  }
 }