1 /*
2  * Copyright 2014 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.testing.integration;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import com.google.common.io.Files;
21 import io.grpc.ManagedChannel;
22 import io.grpc.alts.AltsChannelBuilder;
23 import io.grpc.alts.GoogleDefaultChannelBuilder;
24 import io.grpc.internal.AbstractManagedChannelImplBuilder;
25 import io.grpc.internal.GrpcUtil;
26 import io.grpc.internal.testing.TestUtils;
27 import io.grpc.netty.GrpcSslContexts;
28 import io.grpc.netty.NegotiationType;
29 import io.grpc.netty.NettyChannelBuilder;
30 import io.grpc.okhttp.OkHttpChannelBuilder;
31 import io.grpc.okhttp.internal.Platform;
32 import io.netty.handler.ssl.SslContext;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.nio.charset.Charset;
36 import javax.net.ssl.SSLSocketFactory;
37 
38 /**
39  * Application that starts a client for the {@link TestServiceGrpc.TestServiceImplBase} and runs
40  * through a series of tests.
41  */
42 public class TestServiceClient {
43 
44   private static final Charset UTF_8 = Charset.forName("UTF-8");
45 
46   /**
47    * The main application allowing this client to be launched from the command line.
48    */
main(String[] args)49   public static void main(String[] args) throws Exception {
50     // Let Netty or OkHttp use Conscrypt if it is available.
51     TestUtils.installConscryptIfAvailable();
52     final TestServiceClient client = new TestServiceClient();
53     client.parseArgs(args);
54     client.setUp();
55 
56     Runtime.getRuntime().addShutdownHook(new Thread() {
57       @Override
58       public void run() {
59         System.out.println("Shutting down");
60         try {
61           client.tearDown();
62         } catch (Exception e) {
63           e.printStackTrace();
64         }
65       }
66     });
67 
68     try {
69       client.run();
70     } finally {
71       client.tearDown();
72     }
73     System.exit(0);
74   }
75 
76   private String serverHost = "localhost";
77   private String serverHostOverride;
78   private int serverPort = 8080;
79   private String testCase = "empty_unary";
80   private boolean useTls = true;
81   private boolean useAlts = false;
82   private String customCredentialsType;
83   private boolean useTestCa;
84   private boolean useOkHttp;
85   private String defaultServiceAccount;
86   private String serviceAccountKeyFile;
87   private String oauthScope;
88   private boolean fullStreamDecompression;
89 
90   private Tester tester = new Tester();
91 
92   @VisibleForTesting
parseArgs(String[] args)93   void parseArgs(String[] args) {
94     boolean usage = false;
95     for (String arg : args) {
96       if (!arg.startsWith("--")) {
97         System.err.println("All arguments must start with '--': " + arg);
98         usage = true;
99         break;
100       }
101       String[] parts = arg.substring(2).split("=", 2);
102       String key = parts[0];
103       if ("help".equals(key)) {
104         usage = true;
105         break;
106       }
107       if (parts.length != 2) {
108         System.err.println("All arguments must be of the form --arg=value");
109         usage = true;
110         break;
111       }
112       String value = parts[1];
113       if ("server_host".equals(key)) {
114         serverHost = value;
115       } else if ("server_host_override".equals(key)) {
116         serverHostOverride = value;
117       } else if ("server_port".equals(key)) {
118         serverPort = Integer.parseInt(value);
119       } else if ("test_case".equals(key)) {
120         testCase = value;
121       } else if ("use_tls".equals(key)) {
122         useTls = Boolean.parseBoolean(value);
123       } else if ("use_alts".equals(key)) {
124         useAlts = Boolean.parseBoolean(value);
125       } else if ("custom_credentials_type".equals(key)) {
126         customCredentialsType = value;
127       } else if ("use_test_ca".equals(key)) {
128         useTestCa = Boolean.parseBoolean(value);
129       } else if ("use_okhttp".equals(key)) {
130         useOkHttp = Boolean.parseBoolean(value);
131       } else if ("grpc_version".equals(key)) {
132         if (!"2".equals(value)) {
133           System.err.println("Only grpc version 2 is supported");
134           usage = true;
135           break;
136         }
137       } else if ("default_service_account".equals(key)) {
138         defaultServiceAccount = value;
139       } else if ("service_account_key_file".equals(key)) {
140         serviceAccountKeyFile = value;
141       } else if ("oauth_scope".equals(key)) {
142         oauthScope = value;
143       } else if ("full_stream_decompression".equals(key)) {
144         fullStreamDecompression = Boolean.parseBoolean(value);
145       } else {
146         System.err.println("Unknown argument: " + key);
147         usage = true;
148         break;
149       }
150     }
151     if (useAlts) {
152       useTls = false;
153     }
154     if (usage) {
155       TestServiceClient c = new TestServiceClient();
156       System.out.println(
157           "Usage: [ARGS...]"
158           + "\n"
159           + "\n  --server_host=HOST          Server to connect to. Default " + c.serverHost
160           + "\n  --server_host_override=HOST Claimed identification expected of server."
161           + "\n                              Defaults to server host"
162           + "\n  --server_port=PORT          Port to connect to. Default " + c.serverPort
163           + "\n  --test_case=TESTCASE        Test case to run. Default " + c.testCase
164           + "\n    Valid options:"
165           + validTestCasesHelpText()
166           + "\n  --use_tls=true|false        Whether to use TLS. Default " + c.useTls
167           + "\n  --use_alts=true|false       Whether to use ALTS. Enable ALTS will disable TLS."
168           + "\n                              Default " + c.useAlts
169           + "\n  --custom_credentials_type   Custom credentials type to use. Default "
170             + c.customCredentialsType
171           + "\n  --use_test_ca=true|false    Whether to trust our fake CA. Requires --use_tls=true "
172           + "\n                              to have effect. Default " + c.useTestCa
173           + "\n  --use_okhttp=true|false     Whether to use OkHttp instead of Netty. Default "
174             + c.useOkHttp
175           + "\n  --default_service_account   Email of GCE default service account. Default "
176             + c.defaultServiceAccount
177           + "\n  --service_account_key_file  Path to service account json key file."
178             + c.serviceAccountKeyFile
179           + "\n  --oauth_scope               Scope for OAuth tokens. Default " + c.oauthScope
180           + "\n  --full_stream_decompression Enable full-stream decompression. Default "
181             + c.fullStreamDecompression
182       );
183       System.exit(1);
184     }
185   }
186 
187   @VisibleForTesting
setUp()188   void setUp() {
189     tester.setUp();
190   }
191 
tearDown()192   private synchronized void tearDown() {
193     try {
194       tester.tearDown();
195     } catch (RuntimeException ex) {
196       throw ex;
197     } catch (Exception ex) {
198       throw new RuntimeException(ex);
199     }
200   }
201 
run()202   private void run() {
203     System.out.println("Running test " + testCase);
204     try {
205       runTest(TestCases.fromString(testCase));
206     } catch (RuntimeException ex) {
207       throw ex;
208     } catch (Exception ex) {
209       throw new RuntimeException(ex);
210     }
211     System.out.println("Test completed.");
212   }
213 
runTest(TestCases testCase)214   private void runTest(TestCases testCase) throws Exception {
215     switch (testCase) {
216       case EMPTY_UNARY:
217         tester.emptyUnary();
218         break;
219 
220       case CACHEABLE_UNARY: {
221         tester.cacheableUnary();
222         break;
223       }
224 
225       case LARGE_UNARY:
226         tester.largeUnary();
227         break;
228 
229       case CLIENT_COMPRESSED_UNARY:
230         tester.clientCompressedUnary(true);
231         break;
232 
233       case CLIENT_COMPRESSED_UNARY_NOPROBE:
234         tester.clientCompressedUnary(false);
235         break;
236 
237       case SERVER_COMPRESSED_UNARY:
238         tester.serverCompressedUnary();
239         break;
240 
241       case CLIENT_STREAMING:
242         tester.clientStreaming();
243         break;
244 
245       case CLIENT_COMPRESSED_STREAMING:
246         tester.clientCompressedStreaming(true);
247         break;
248 
249       case CLIENT_COMPRESSED_STREAMING_NOPROBE:
250         tester.clientCompressedStreaming(false);
251         break;
252 
253       case SERVER_STREAMING:
254         tester.serverStreaming();
255         break;
256 
257       case SERVER_COMPRESSED_STREAMING:
258         tester.serverCompressedStreaming();
259         break;
260 
261       case PING_PONG:
262         tester.pingPong();
263         break;
264 
265       case EMPTY_STREAM:
266         tester.emptyStream();
267         break;
268 
269       case COMPUTE_ENGINE_CREDS:
270         tester.computeEngineCreds(defaultServiceAccount, oauthScope);
271         break;
272 
273       case SERVICE_ACCOUNT_CREDS: {
274         String jsonKey = Files.asCharSource(new File(serviceAccountKeyFile), UTF_8).read();
275         FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
276         tester.serviceAccountCreds(jsonKey, credentialsStream, oauthScope);
277         break;
278       }
279 
280       case JWT_TOKEN_CREDS: {
281         FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
282         tester.jwtTokenCreds(credentialsStream);
283         break;
284       }
285 
286       case OAUTH2_AUTH_TOKEN: {
287         String jsonKey = Files.asCharSource(new File(serviceAccountKeyFile), UTF_8).read();
288         FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
289         tester.oauth2AuthToken(jsonKey, credentialsStream, oauthScope);
290         break;
291       }
292 
293       case PER_RPC_CREDS: {
294         String jsonKey = Files.asCharSource(new File(serviceAccountKeyFile), UTF_8).read();
295         FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
296         tester.perRpcCreds(jsonKey, credentialsStream, oauthScope);
297         break;
298       }
299 
300       case CUSTOM_METADATA: {
301         tester.customMetadata();
302         break;
303       }
304 
305       case STATUS_CODE_AND_MESSAGE: {
306         tester.statusCodeAndMessage();
307         break;
308       }
309 
310       case SPECIAL_STATUS_MESSAGE:
311         tester.specialStatusMessage();
312         break;
313 
314       case UNIMPLEMENTED_METHOD: {
315         tester.unimplementedMethod();
316         break;
317       }
318 
319       case UNIMPLEMENTED_SERVICE: {
320         tester.unimplementedService();
321         break;
322       }
323 
324       case CANCEL_AFTER_BEGIN: {
325         tester.cancelAfterBegin();
326         break;
327       }
328 
329       case CANCEL_AFTER_FIRST_RESPONSE: {
330         tester.cancelAfterFirstResponse();
331         break;
332       }
333 
334       case TIMEOUT_ON_SLEEPING_SERVER: {
335         tester.timeoutOnSleepingServer();
336         break;
337       }
338 
339       case VERY_LARGE_REQUEST: {
340         tester.veryLargeRequest();
341         break;
342       }
343 
344       default:
345         throw new IllegalArgumentException("Unknown test case: " + testCase);
346     }
347   }
348 
349   private class Tester extends AbstractInteropTest {
350     @Override
createChannel()351     protected ManagedChannel createChannel() {
352       if (customCredentialsType != null
353           && customCredentialsType.equals("google_default_credentials")) {
354         return GoogleDefaultChannelBuilder.forAddress(serverHost, serverPort).build();
355       }
356       if (useAlts) {
357         return AltsChannelBuilder.forAddress(serverHost, serverPort).build();
358       }
359       AbstractManagedChannelImplBuilder<?> builder;
360       if (!useOkHttp) {
361         SslContext sslContext = null;
362         if (useTestCa) {
363           try {
364             sslContext = GrpcSslContexts.forClient().trustManager(
365                     TestUtils.loadCert("ca.pem")).build();
366           } catch (Exception ex) {
367             throw new RuntimeException(ex);
368           }
369         }
370         NettyChannelBuilder nettyBuilder =
371             NettyChannelBuilder.forAddress(serverHost, serverPort)
372                 .flowControlWindow(65 * 1024)
373                 .negotiationType(useTls ? NegotiationType.TLS : NegotiationType.PLAINTEXT)
374                 .sslContext(sslContext);
375         if (serverHostOverride != null) {
376           nettyBuilder.overrideAuthority(serverHostOverride);
377         }
378         if (fullStreamDecompression) {
379           nettyBuilder.enableFullStreamDecompression();
380         }
381         builder = nettyBuilder;
382       } else {
383         OkHttpChannelBuilder okBuilder = OkHttpChannelBuilder.forAddress(serverHost, serverPort);
384         if (serverHostOverride != null) {
385           // Force the hostname to match the cert the server uses.
386           okBuilder.overrideAuthority(
387               GrpcUtil.authorityFromHostAndPort(serverHostOverride, serverPort));
388         }
389         if (useTls) {
390           try {
391             SSLSocketFactory factory = useTestCa
392                 ? TestUtils.newSslSocketFactoryForCa(Platform.get().getProvider(),
393                     TestUtils.loadCert("ca.pem"))
394                 : (SSLSocketFactory) SSLSocketFactory.getDefault();
395             okBuilder.sslSocketFactory(factory);
396           } catch (Exception e) {
397             throw new RuntimeException(e);
398           }
399         } else {
400           okBuilder.usePlaintext();
401         }
402         if (fullStreamDecompression) {
403           okBuilder.enableFullStreamDecompression();
404         }
405         builder = okBuilder;
406       }
407       io.grpc.internal.TestingAccessor.setStatsImplementation(
408           builder, createClientCensusStatsModule());
409       return builder.build();
410     }
411 
412     @Override
metricsExpected()413     protected boolean metricsExpected() {
414       // Exact message size doesn't match when testing with Go servers:
415       // https://github.com/grpc/grpc-go/issues/1572
416       // TODO(zhangkun83): remove this override once the said issue is fixed.
417       return false;
418     }
419   }
420 
validTestCasesHelpText()421   private static String validTestCasesHelpText() {
422     StringBuilder builder = new StringBuilder();
423     for (TestCases testCase : TestCases.values()) {
424       String strTestcase = testCase.name().toLowerCase();
425       builder.append("\n      ")
426           .append(strTestcase)
427           .append(": ")
428           .append(testCase.description());
429     }
430     return builder.toString();
431   }
432 }
433