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