Skip to content

Commit 84d0b04

Browse files
authored
core: Replace inappropriate picker result status codes (grpc#9530)
Certain status codes are inappropriate for a control plane to be returning and indicate a bug in the control plane. These status codes will be replaced by INTERNAL, to make it clearer to see that the control plane is misbehaving.
1 parent eac4178 commit 84d0b04

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

core/src/main/java/io/grpc/internal/GrpcUtil.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@
5656
import java.net.URISyntaxException;
5757
import java.nio.charset.Charset;
5858
import java.util.Collection;
59+
import java.util.Collections;
60+
import java.util.EnumSet;
5961
import java.util.List;
62+
import java.util.Set;
6063
import java.util.concurrent.Executor;
6164
import java.util.concurrent.ExecutorService;
6265
import java.util.concurrent.Executors;
@@ -75,6 +78,17 @@ public final class GrpcUtil {
7578

7679
private static final Logger log = Logger.getLogger(GrpcUtil.class.getName());
7780

81+
private static final Set<Status.Code> INAPPROPRIATE_CONTROL_PLANE_STATUS
82+
= Collections.unmodifiableSet(EnumSet.of(
83+
Status.Code.OK,
84+
Status.Code.INVALID_ARGUMENT,
85+
Status.Code.NOT_FOUND,
86+
Status.Code.ALREADY_EXISTS,
87+
Status.Code.FAILED_PRECONDITION,
88+
Status.Code.ABORTED,
89+
Status.Code.OUT_OF_RANGE,
90+
Status.Code.DATA_LOSS));
91+
7892
public static final Charset US_ASCII = Charset.forName("US-ASCII");
7993

8094
/**
@@ -747,10 +761,12 @@ public ListenableFuture<SocketStats> getStats() {
747761
}
748762
if (!result.getStatus().isOk()) {
749763
if (result.isDrop()) {
750-
return new FailingClientTransport(result.getStatus(), RpcProgress.DROPPED);
764+
return new FailingClientTransport(
765+
replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.DROPPED);
751766
}
752767
if (!isWaitForReady) {
753-
return new FailingClientTransport(result.getStatus(), RpcProgress.PROCESSED);
768+
return new FailingClientTransport(
769+
replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.PROCESSED);
754770
}
755771
}
756772
return null;
@@ -805,6 +821,19 @@ public static void exhaust(InputStream in) throws IOException {
805821
while (in.read(buf) != -1) {}
806822
}
807823

824+
/**
825+
* Some status codes from the control plane are not appropritate to use in the data plane. If one
826+
* is given it will be replaced with INTERNAL, indicating a bug in the control plane
827+
* implementation.
828+
*/
829+
public static Status replaceInappropriateControlPlaneStatus(Status status) {
830+
checkArgument(status != null);
831+
return INAPPROPRIATE_CONTROL_PLANE_STATUS.contains(status.getCode())
832+
? Status.INTERNAL.withDescription(
833+
"Inappropriate status code from control plane: " + status.getCode() + " "
834+
+ status.getDescription()).withCause(status.getCause()) : status;
835+
}
836+
808837
/**
809838
* Checks whether the given item exists in the iterable. This is copied from Guava Collect's
810839
* {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't

core/src/test/java/io/grpc/internal/GrpcUtilTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.grpc.internal;
1818

19+
import static com.google.common.truth.Truth.assertThat;
1920
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertNotNull;
@@ -27,19 +28,26 @@
2728
import static org.mockito.Mockito.mock;
2829
import static org.mockito.Mockito.verify;
2930

31+
import com.google.common.collect.Lists;
3032
import io.grpc.CallOptions;
3133
import io.grpc.ClientStreamTracer;
3234
import io.grpc.LoadBalancer.PickResult;
3335
import io.grpc.Metadata;
3436
import io.grpc.Status;
37+
import io.grpc.Status.Code;
3538
import io.grpc.internal.ClientStreamListener.RpcProgress;
3639
import io.grpc.internal.GrpcUtil.Http2Error;
3740
import io.grpc.testing.TestMethodDescriptors;
41+
import java.util.ArrayList;
3842
import org.junit.Rule;
3943
import org.junit.Test;
4044
import org.junit.rules.ExpectedException;
4145
import org.junit.runner.RunWith;
4246
import org.junit.runners.JUnit4;
47+
import org.mockito.ArgumentCaptor;
48+
import org.mockito.Captor;
49+
import org.mockito.junit.MockitoJUnit;
50+
import org.mockito.junit.MockitoRule;
4351

4452
/** Unit tests for {@link GrpcUtil}. */
4553
@RunWith(JUnit4.class)
@@ -51,6 +59,11 @@ public class GrpcUtilTest {
5159

5260
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
5361
@Rule public final ExpectedException thrown = ExpectedException.none();
62+
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
63+
64+
@Captor
65+
private ArgumentCaptor<Status> statusCaptor;
66+
5467

5568
@Test
5669
public void http2ErrorForCode() {
@@ -258,6 +271,69 @@ public void getTransportFromPickResult_errorPickResult_failFast() {
258271
verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class));
259272
}
260273

274+
/* Status codes that a control plane should not be returned get replaced by INTERNAL. */
275+
@Test
276+
public void getTransportFromPickResult_errorPickResult_noInappropriateControlPlaneStatus() {
277+
278+
// These are NOT appropriate for a control plane to return.
279+
ArrayList<Status> inappropriateStatus = Lists.newArrayList(
280+
Status.INVALID_ARGUMENT.withDescription("bad one").withCause(new RuntimeException()),
281+
Status.NOT_FOUND.withDescription("not here").withCause(new RuntimeException()),
282+
Status.ALREADY_EXISTS.withDescription("not again").withCause(new RuntimeException()),
283+
Status.FAILED_PRECONDITION.withDescription("naah").withCause(new RuntimeException()),
284+
Status.ABORTED.withDescription("nope").withCause(new RuntimeException()),
285+
Status.OUT_OF_RANGE.withDescription("outta range").withCause(new RuntimeException()),
286+
Status.DATA_LOSS.withDescription("lost").withCause(new RuntimeException()));
287+
288+
for (Status status : inappropriateStatus) {
289+
PickResult pickResult = PickResult.withError(status);
290+
ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, false);
291+
292+
ClientStream stream = transport.newStream(
293+
TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
294+
tracers);
295+
ClientStreamListener listener = mock(ClientStreamListener.class);
296+
stream.start(listener);
297+
298+
verify(listener).closed(statusCaptor.capture(), eq(RpcProgress.PROCESSED),
299+
any(Metadata.class));
300+
Status usedStatus = statusCaptor.getValue();
301+
assertThat(usedStatus.getCode()).isEqualTo(Code.INTERNAL);
302+
assertThat(usedStatus.getDescription()).contains("Inappropriate status");
303+
assertThat(usedStatus.getCause()).isInstanceOf(RuntimeException.class);
304+
}
305+
}
306+
307+
/* Status codes a control plane can return are not replaced. */
308+
@Test
309+
public void getTransportFromPickResult_errorPickResult_appropriateControlPlaneStatus() {
310+
311+
// These ARE appropriate for a control plane to return.
312+
ArrayList<Status> inappropriateStatus = Lists.newArrayList(
313+
Status.CANCELLED,
314+
Status.UNKNOWN,
315+
Status.DEADLINE_EXCEEDED,
316+
Status.PERMISSION_DENIED,
317+
Status.RESOURCE_EXHAUSTED,
318+
Status.UNIMPLEMENTED,
319+
Status.INTERNAL,
320+
Status.UNAVAILABLE,
321+
Status.UNAUTHENTICATED);
322+
323+
for (Status status : inappropriateStatus) {
324+
PickResult pickResult = PickResult.withError(status);
325+
ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, false);
326+
327+
ClientStream stream = transport.newStream(
328+
TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
329+
tracers);
330+
ClientStreamListener listener = mock(ClientStreamListener.class);
331+
stream.start(listener);
332+
333+
verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class));
334+
}
335+
}
336+
261337
@Test
262338
public void getTransportFromPickResult_dropPickResult_waitForReady() {
263339
Status status = Status.UNAVAILABLE;

0 commit comments

Comments
 (0)