InteropClient.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. #region Copyright notice and license
  2. // Copyright 2015, Google Inc.
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are
  7. // met:
  8. //
  9. // * Redistributions of source code must retain the above copyright
  10. // notice, this list of conditions and the following disclaimer.
  11. // * Redistributions in binary form must reproduce the above
  12. // copyright notice, this list of conditions and the following disclaimer
  13. // in the documentation and/or other materials provided with the
  14. // distribution.
  15. // * Neither the name of Google Inc. nor the names of its
  16. // contributors may be used to endorse or promote products derived from
  17. // this software without specific prior written permission.
  18. //
  19. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. #endregion
  31. using System;
  32. using System.Collections.Generic;
  33. using System.Text.RegularExpressions;
  34. using System.Threading;
  35. using System.Threading.Tasks;
  36. using Google.Apis.Auth.OAuth2;
  37. using Google.Protobuf;
  38. using Grpc.Auth;
  39. using Grpc.Core;
  40. using Grpc.Core.Utils;
  41. using Grpc.Testing;
  42. using NUnit.Framework;
  43. namespace Grpc.IntegrationTesting
  44. {
  45. public class InteropClient
  46. {
  47. private const string ServiceAccountUser = "155450119199-3psnrh1sdr3d8cpj1v46naggf81mhdnk@developer.gserviceaccount.com";
  48. private const string ComputeEngineUser = "155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel@developer.gserviceaccount.com";
  49. private const string AuthScope = "https://www.googleapis.com/auth/xapi.zoo";
  50. private const string AuthScopeResponse = "xapi.zoo";
  51. private class ClientOptions
  52. {
  53. public bool help;
  54. public string serverHost = "127.0.0.1";
  55. public string serverHostOverride = TestCredentials.DefaultHostOverride;
  56. public int? serverPort;
  57. public string testCase = "large_unary";
  58. public bool useTls;
  59. public bool useTestCa;
  60. }
  61. ClientOptions options;
  62. private InteropClient(ClientOptions options)
  63. {
  64. this.options = options;
  65. }
  66. public static void Run(string[] args)
  67. {
  68. Console.WriteLine("gRPC C# interop testing client");
  69. ClientOptions options = ParseArguments(args);
  70. if (options.serverHost == null || !options.serverPort.HasValue || options.testCase == null)
  71. {
  72. Console.WriteLine("Missing required argument.");
  73. Console.WriteLine();
  74. options.help = true;
  75. }
  76. if (options.help)
  77. {
  78. Console.WriteLine("Usage:");
  79. Console.WriteLine(" --server_host=HOSTNAME");
  80. Console.WriteLine(" --server_host_override=HOSTNAME");
  81. Console.WriteLine(" --server_port=PORT");
  82. Console.WriteLine(" --test_case=TESTCASE");
  83. Console.WriteLine(" --use_tls=BOOLEAN");
  84. Console.WriteLine(" --use_test_ca=BOOLEAN");
  85. Console.WriteLine();
  86. Environment.Exit(1);
  87. }
  88. var interopClient = new InteropClient(options);
  89. interopClient.Run().Wait();
  90. }
  91. private async Task Run()
  92. {
  93. Credentials credentials = null;
  94. if (options.useTls)
  95. {
  96. credentials = TestCredentials.CreateTestClientCredentials(options.useTestCa);
  97. }
  98. List<ChannelOption> channelOptions = null;
  99. if (!string.IsNullOrEmpty(options.serverHostOverride))
  100. {
  101. channelOptions = new List<ChannelOption>
  102. {
  103. new ChannelOption(ChannelOptions.SslTargetNameOverride, options.serverHostOverride)
  104. };
  105. }
  106. var channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions);
  107. TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
  108. await RunTestCaseAsync(options.testCase, client);
  109. channel.ShutdownAsync().Wait();
  110. }
  111. private async Task RunTestCaseAsync(string testCase, TestService.TestServiceClient client)
  112. {
  113. switch (testCase)
  114. {
  115. case "empty_unary":
  116. RunEmptyUnary(client);
  117. break;
  118. case "large_unary":
  119. RunLargeUnary(client);
  120. break;
  121. case "client_streaming":
  122. await RunClientStreamingAsync(client);
  123. break;
  124. case "server_streaming":
  125. await RunServerStreamingAsync(client);
  126. break;
  127. case "ping_pong":
  128. await RunPingPongAsync(client);
  129. break;
  130. case "empty_stream":
  131. await RunEmptyStreamAsync(client);
  132. break;
  133. case "service_account_creds":
  134. await RunServiceAccountCredsAsync(client);
  135. break;
  136. case "compute_engine_creds":
  137. await RunComputeEngineCredsAsync(client);
  138. break;
  139. case "jwt_token_creds":
  140. await RunJwtTokenCredsAsync(client);
  141. break;
  142. case "oauth2_auth_token":
  143. await RunOAuth2AuthTokenAsync(client);
  144. break;
  145. case "per_rpc_creds":
  146. await RunPerRpcCredsAsync(client);
  147. break;
  148. case "cancel_after_begin":
  149. await RunCancelAfterBeginAsync(client);
  150. break;
  151. case "cancel_after_first_response":
  152. await RunCancelAfterFirstResponseAsync(client);
  153. break;
  154. case "timeout_on_sleeping_server":
  155. await RunTimeoutOnSleepingServerAsync(client);
  156. break;
  157. case "benchmark_empty_unary":
  158. RunBenchmarkEmptyUnary(client);
  159. break;
  160. default:
  161. throw new ArgumentException("Unknown test case " + testCase);
  162. }
  163. }
  164. public static void RunEmptyUnary(TestService.ITestServiceClient client)
  165. {
  166. Console.WriteLine("running empty_unary");
  167. var response = client.EmptyCall(new Empty());
  168. Assert.IsNotNull(response);
  169. Console.WriteLine("Passed!");
  170. }
  171. public static void RunLargeUnary(TestService.ITestServiceClient client)
  172. {
  173. Console.WriteLine("running large_unary");
  174. var request = new SimpleRequest
  175. {
  176. ResponseType = PayloadType.COMPRESSABLE,
  177. ResponseSize = 314159,
  178. Payload = CreateZerosPayload(271828)
  179. };
  180. var response = client.UnaryCall(request);
  181. Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
  182. Assert.AreEqual(314159, response.Payload.Body.Length);
  183. Console.WriteLine("Passed!");
  184. }
  185. public static async Task RunClientStreamingAsync(TestService.ITestServiceClient client)
  186. {
  187. Console.WriteLine("running client_streaming");
  188. var bodySizes = new List<int> { 27182, 8, 1828, 45904 }.ConvertAll((size) => new StreamingInputCallRequest { Payload = CreateZerosPayload(size) });
  189. using (var call = client.StreamingInputCall())
  190. {
  191. await call.RequestStream.WriteAllAsync(bodySizes);
  192. var response = await call.ResponseAsync;
  193. Assert.AreEqual(74922, response.AggregatedPayloadSize);
  194. }
  195. Console.WriteLine("Passed!");
  196. }
  197. public static async Task RunServerStreamingAsync(TestService.ITestServiceClient client)
  198. {
  199. Console.WriteLine("running server_streaming");
  200. var bodySizes = new List<int> { 31415, 9, 2653, 58979 };
  201. var request = new StreamingOutputCallRequest
  202. {
  203. ResponseType = PayloadType.COMPRESSABLE,
  204. ResponseParameters = { bodySizes.ConvertAll((size) => new ResponseParameters { Size = size }) }
  205. };
  206. using (var call = client.StreamingOutputCall(request))
  207. {
  208. var responseList = await call.ResponseStream.ToListAsync();
  209. foreach (var res in responseList)
  210. {
  211. Assert.AreEqual(PayloadType.COMPRESSABLE, res.Payload.Type);
  212. }
  213. CollectionAssert.AreEqual(bodySizes, responseList.ConvertAll((item) => item.Payload.Body.Length));
  214. }
  215. Console.WriteLine("Passed!");
  216. }
  217. public static async Task RunPingPongAsync(TestService.ITestServiceClient client)
  218. {
  219. Console.WriteLine("running ping_pong");
  220. using (var call = client.FullDuplexCall())
  221. {
  222. await call.RequestStream.WriteAsync(new StreamingOutputCallRequest
  223. {
  224. ResponseType = PayloadType.COMPRESSABLE,
  225. ResponseParameters = { new ResponseParameters { Size = 31415 } },
  226. Payload = CreateZerosPayload(27182)
  227. });
  228. Assert.IsTrue(await call.ResponseStream.MoveNext());
  229. Assert.AreEqual(PayloadType.COMPRESSABLE, call.ResponseStream.Current.Payload.Type);
  230. Assert.AreEqual(31415, call.ResponseStream.Current.Payload.Body.Length);
  231. await call.RequestStream.WriteAsync(new StreamingOutputCallRequest
  232. {
  233. ResponseType = PayloadType.COMPRESSABLE,
  234. ResponseParameters = { new ResponseParameters { Size = 9 } },
  235. Payload = CreateZerosPayload(8)
  236. });
  237. Assert.IsTrue(await call.ResponseStream.MoveNext());
  238. Assert.AreEqual(PayloadType.COMPRESSABLE, call.ResponseStream.Current.Payload.Type);
  239. Assert.AreEqual(9, call.ResponseStream.Current.Payload.Body.Length);
  240. await call.RequestStream.WriteAsync(new StreamingOutputCallRequest
  241. {
  242. ResponseType = PayloadType.COMPRESSABLE,
  243. ResponseParameters = { new ResponseParameters { Size = 2653 } },
  244. Payload = CreateZerosPayload(1828)
  245. });
  246. Assert.IsTrue(await call.ResponseStream.MoveNext());
  247. Assert.AreEqual(PayloadType.COMPRESSABLE, call.ResponseStream.Current.Payload.Type);
  248. Assert.AreEqual(2653, call.ResponseStream.Current.Payload.Body.Length);
  249. await call.RequestStream.WriteAsync(new StreamingOutputCallRequest
  250. {
  251. ResponseType = PayloadType.COMPRESSABLE,
  252. ResponseParameters = { new ResponseParameters { Size = 58979 } },
  253. Payload = CreateZerosPayload(45904)
  254. });
  255. Assert.IsTrue(await call.ResponseStream.MoveNext());
  256. Assert.AreEqual(PayloadType.COMPRESSABLE, call.ResponseStream.Current.Payload.Type);
  257. Assert.AreEqual(58979, call.ResponseStream.Current.Payload.Body.Length);
  258. await call.RequestStream.CompleteAsync();
  259. Assert.IsFalse(await call.ResponseStream.MoveNext());
  260. }
  261. Console.WriteLine("Passed!");
  262. }
  263. public static async Task RunEmptyStreamAsync(TestService.ITestServiceClient client)
  264. {
  265. Console.WriteLine("running empty_stream");
  266. using (var call = client.FullDuplexCall())
  267. {
  268. await call.RequestStream.CompleteAsync();
  269. var responseList = await call.ResponseStream.ToListAsync();
  270. Assert.AreEqual(0, responseList.Count);
  271. }
  272. Console.WriteLine("Passed!");
  273. }
  274. public static async Task RunServiceAccountCredsAsync(TestService.TestServiceClient client)
  275. {
  276. Console.WriteLine("running service_account_creds");
  277. var credential = await GoogleCredential.GetApplicationDefaultAsync();
  278. credential = credential.CreateScoped(new[] { AuthScope });
  279. client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
  280. var request = new SimpleRequest
  281. {
  282. ResponseType = PayloadType.COMPRESSABLE,
  283. ResponseSize = 314159,
  284. Payload = CreateZerosPayload(271828),
  285. FillUsername = true,
  286. FillOauthScope = true
  287. };
  288. var response = client.UnaryCall(request);
  289. Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
  290. Assert.AreEqual(314159, response.Payload.Body.Length);
  291. Assert.AreEqual(AuthScopeResponse, response.OauthScope);
  292. Assert.AreEqual(ServiceAccountUser, response.Username);
  293. Console.WriteLine("Passed!");
  294. }
  295. public static async Task RunComputeEngineCredsAsync(TestService.TestServiceClient client)
  296. {
  297. Console.WriteLine("running compute_engine_creds");
  298. var credential = await GoogleCredential.GetApplicationDefaultAsync();
  299. Assert.IsFalse(credential.IsCreateScopedRequired);
  300. client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
  301. var request = new SimpleRequest
  302. {
  303. ResponseType = PayloadType.COMPRESSABLE,
  304. ResponseSize = 314159,
  305. Payload = CreateZerosPayload(271828),
  306. FillUsername = true,
  307. FillOauthScope = true
  308. };
  309. var response = client.UnaryCall(request);
  310. Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
  311. Assert.AreEqual(314159, response.Payload.Body.Length);
  312. Assert.AreEqual(AuthScopeResponse, response.OauthScope);
  313. Assert.AreEqual(ComputeEngineUser, response.Username);
  314. Console.WriteLine("Passed!");
  315. }
  316. public static async Task RunJwtTokenCredsAsync(TestService.TestServiceClient client)
  317. {
  318. Console.WriteLine("running jwt_token_creds");
  319. var credential = await GoogleCredential.GetApplicationDefaultAsync();
  320. // check this a credential with scope support, but don't add the scope.
  321. Assert.IsTrue(credential.IsCreateScopedRequired);
  322. client.HeaderInterceptor = AuthInterceptors.FromCredential(credential);
  323. var request = new SimpleRequest
  324. {
  325. ResponseType = PayloadType.COMPRESSABLE,
  326. ResponseSize = 314159,
  327. Payload = CreateZerosPayload(271828),
  328. FillUsername = true,
  329. FillOauthScope = true
  330. };
  331. var response = client.UnaryCall(request);
  332. Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
  333. Assert.AreEqual(314159, response.Payload.Body.Length);
  334. Assert.AreEqual(ServiceAccountUser, response.Username);
  335. Console.WriteLine("Passed!");
  336. }
  337. public static async Task RunOAuth2AuthTokenAsync(TestService.TestServiceClient client)
  338. {
  339. Console.WriteLine("running oauth2_auth_token");
  340. ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope });
  341. string oauth2Token = await credential.GetAccessTokenForRequestAsync();
  342. client.HeaderInterceptor = AuthInterceptors.FromAccessToken(oauth2Token);
  343. var request = new SimpleRequest
  344. {
  345. FillUsername = true,
  346. FillOauthScope = true
  347. };
  348. var response = client.UnaryCall(request);
  349. Assert.AreEqual(AuthScopeResponse, response.OauthScope);
  350. Assert.AreEqual(ServiceAccountUser, response.Username);
  351. Console.WriteLine("Passed!");
  352. }
  353. public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client)
  354. {
  355. Console.WriteLine("running per_rpc_creds");
  356. ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope });
  357. string oauth2Token = await credential.GetAccessTokenForRequestAsync();
  358. var headerInterceptor = AuthInterceptors.FromAccessToken(oauth2Token);
  359. var request = new SimpleRequest
  360. {
  361. FillUsername = true,
  362. FillOauthScope = true
  363. };
  364. var headers = new Metadata();
  365. headerInterceptor(null, "", headers);
  366. var response = client.UnaryCall(request, headers: headers);
  367. Assert.AreEqual(AuthScopeResponse, response.OauthScope);
  368. Assert.AreEqual(ServiceAccountUser, response.Username);
  369. Console.WriteLine("Passed!");
  370. }
  371. public static async Task RunCancelAfterBeginAsync(TestService.ITestServiceClient client)
  372. {
  373. Console.WriteLine("running cancel_after_begin");
  374. var cts = new CancellationTokenSource();
  375. using (var call = client.StreamingInputCall(cancellationToken: cts.Token))
  376. {
  377. // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it.
  378. await Task.Delay(1000);
  379. cts.Cancel();
  380. var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync);
  381. Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
  382. }
  383. Console.WriteLine("Passed!");
  384. }
  385. public static async Task RunCancelAfterFirstResponseAsync(TestService.ITestServiceClient client)
  386. {
  387. Console.WriteLine("running cancel_after_first_response");
  388. var cts = new CancellationTokenSource();
  389. using (var call = client.FullDuplexCall(cancellationToken: cts.Token))
  390. {
  391. await call.RequestStream.WriteAsync(new StreamingOutputCallRequest
  392. {
  393. ResponseType = PayloadType.COMPRESSABLE,
  394. ResponseParameters = { new ResponseParameters { Size = 31415 } },
  395. Payload = CreateZerosPayload(27182)
  396. });
  397. Assert.IsTrue(await call.ResponseStream.MoveNext());
  398. Assert.AreEqual(PayloadType.COMPRESSABLE, call.ResponseStream.Current.Payload.Type);
  399. Assert.AreEqual(31415, call.ResponseStream.Current.Payload.Body.Length);
  400. cts.Cancel();
  401. var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
  402. Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
  403. }
  404. Console.WriteLine("Passed!");
  405. }
  406. public static async Task RunTimeoutOnSleepingServerAsync(TestService.ITestServiceClient client)
  407. {
  408. Console.WriteLine("running timeout_on_sleeping_server");
  409. var deadline = DateTime.UtcNow.AddMilliseconds(1);
  410. using (var call = client.FullDuplexCall(deadline: deadline))
  411. {
  412. try
  413. {
  414. await call.RequestStream.WriteAsync(new StreamingOutputCallRequest { Payload = CreateZerosPayload(27182) });
  415. }
  416. catch (InvalidOperationException)
  417. {
  418. // Deadline was reached before write has started. Eat the exception and continue.
  419. }
  420. var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
  421. Assert.AreEqual(StatusCode.DeadlineExceeded, ex.Status.StatusCode);
  422. }
  423. Console.WriteLine("Passed!");
  424. }
  425. // This is not an official interop test, but it's useful.
  426. public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client)
  427. {
  428. BenchmarkUtil.RunBenchmark(10000, 10000,
  429. () => { client.EmptyCall(new Empty()); });
  430. }
  431. private static Payload CreateZerosPayload(int size)
  432. {
  433. return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
  434. }
  435. private static ClientOptions ParseArguments(string[] args)
  436. {
  437. var options = new ClientOptions();
  438. foreach (string arg in args)
  439. {
  440. ParseArgument(arg, options);
  441. if (options.help)
  442. {
  443. break;
  444. }
  445. }
  446. return options;
  447. }
  448. private static void ParseArgument(string arg, ClientOptions options)
  449. {
  450. Match match;
  451. match = Regex.Match(arg, "--server_host=(.*)");
  452. if (match.Success)
  453. {
  454. options.serverHost = match.Groups[1].Value.Trim();
  455. return;
  456. }
  457. match = Regex.Match(arg, "--server_host_override=(.*)");
  458. if (match.Success)
  459. {
  460. options.serverHostOverride = match.Groups[1].Value.Trim();
  461. return;
  462. }
  463. match = Regex.Match(arg, "--server_port=(.*)");
  464. if (match.Success)
  465. {
  466. options.serverPort = int.Parse(match.Groups[1].Value.Trim());
  467. return;
  468. }
  469. match = Regex.Match(arg, "--test_case=(.*)");
  470. if (match.Success)
  471. {
  472. options.testCase = match.Groups[1].Value.Trim();
  473. return;
  474. }
  475. match = Regex.Match(arg, "--use_tls=(.*)");
  476. if (match.Success)
  477. {
  478. options.useTls = bool.Parse(match.Groups[1].Value.Trim());
  479. return;
  480. }
  481. match = Regex.Match(arg, "--use_test_ca=(.*)");
  482. if (match.Success)
  483. {
  484. options.useTestCa = bool.Parse(match.Groups[1].Value.Trim());
  485. return;
  486. }
  487. Console.WriteLine(string.Format("Unrecognized argument \"{0}\"", arg));
  488. options.help = true;
  489. }
  490. }
  491. }