1 /* 2 * Copyright 2022 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.istio; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertNotEquals; 22 import static org.junit.Assert.fail; 23 24 import com.google.common.base.Splitter; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableSet; 27 import io.grpc.Grpc; 28 import io.grpc.InsecureChannelCredentials; 29 import io.grpc.InsecureServerCredentials; 30 import io.grpc.ManagedChannel; 31 import io.grpc.ManagedChannelBuilder; 32 import io.grpc.Metadata; 33 import io.grpc.Server; 34 import io.grpc.Status; 35 import io.grpc.StatusRuntimeException; 36 import io.grpc.stub.MetadataUtils; 37 import io.grpc.stub.StreamObserver; 38 import io.istio.test.Echo.EchoRequest; 39 import io.istio.test.Echo.EchoResponse; 40 import io.istio.test.Echo.ForwardEchoRequest; 41 import io.istio.test.Echo.ForwardEchoResponse; 42 import io.istio.test.Echo.Header; 43 import io.istio.test.EchoTestServiceGrpc; 44 import io.istio.test.EchoTestServiceGrpc.EchoTestServiceImplBase; 45 import java.io.IOException; 46 import java.time.Duration; 47 import java.time.Instant; 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 import java.util.concurrent.TimeUnit; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.junit.runners.JUnit4; 56 57 /** 58 * Unit tests for {@link EchoTestServer}. 59 */ 60 @RunWith(JUnit4.class) 61 public class EchoTestServerTest { 62 63 private static final String[] EXPECTED_KEY_SET = { 64 "--server_first", "--forwarding-address", 65 "--bind_ip", "--istio-version", "--bind_localhost", "--grpc", "--tls", 66 "--cluster", "--key", "--tcp", "--crt", "--metrics", "--port", "--version" 67 }; 68 69 private static final String TEST_ARGS = 70 "--metrics=15014 --cluster=\"cluster-0\" --port=\"18080\" --grpc=\"17070\" --port=\"18085\"" 71 + " --tcp=\"19090\" --port=\"18443\" --tls=18443 --tcp=\"16060\" --server_first=16060" 72 + " --tcp=\"19091\" --tcp=\"16061\" --server_first=16061 --port=\"18081\"" 73 + " --grpc=\"17071\" --port=\"19443\" --tls=19443 --port=\"18082\" --bind_ip=18082" 74 + " --port=\"18084\" --bind_localhost=18084 --tcp=\"19092\" --port=\"18083\"" 75 + " --port=\"8080\" --port=\"3333\" --version=\"v1\" --istio-version=3 --crt=/cert.crt" 76 + " --key=/cert.key --forwarding-address=192.168.1.10:7072"; 77 78 private static final String TEST_ARGS_PORTS = 79 "--metrics=15014 --cluster=\"cluster-0\" --port=\"18080\" --grpc=17070 --port=18085" 80 + " --tcp=\"19090\" --port=\"18443\" --tls=18443 --tcp=16060 --server_first=16060" 81 + " --tcp=\"19091\" --tcp=\"16061\" --server_first=16061 --port=\"18081\"" 82 + " --grpc=\"17071\" --port=\"19443\" --tls=\"19443\" --port=\"18082\" --bind_ip=18082" 83 + " --port=\"18084\" --bind_localhost=18084 --tcp=\"19092\" --port=\"18083\"" 84 + " --port=\"8080\" --port=3333 --version=\"v1\" --istio-version=3 --crt=/cert.crt" 85 + " --key=/cert.key --xds-grpc-server=12034 --xds-grpc-server=\"34012\""; 86 87 @Test preprocessArgsTest()88 public void preprocessArgsTest() { 89 String[] splitArgs = TEST_ARGS.split(" "); 90 Map<String, List<String>> processedArgs = EchoTestServer.preprocessArgs(splitArgs); 91 92 assertEquals(processedArgs.keySet(), ImmutableSet.copyOf(EXPECTED_KEY_SET)); 93 assertEquals(processedArgs.get("--server_first"), ImmutableList.of("16060", "16061")); 94 assertEquals(processedArgs.get("--bind_ip"), ImmutableList.of("18082")); 95 assertEquals(processedArgs.get("--bind_localhost"), ImmutableList.of("18084")); 96 assertEquals(processedArgs.get("--grpc"), ImmutableList.of("\"17070\"", "\"17071\"")); 97 assertEquals(processedArgs.get("--tls"), ImmutableList.of("18443", "19443")); 98 assertEquals(processedArgs.get("--cluster"), ImmutableList.of("\"cluster-0\"")); 99 assertEquals(processedArgs.get("--key"), ImmutableList.of("/cert.key")); 100 assertEquals(processedArgs.get("--tcp"), ImmutableList.of("\"19090\"", "\"16060\"", 101 "\"19091\"","\"16061\"","\"19092\"")); 102 assertEquals(processedArgs.get("--istio-version"), ImmutableList.of("3")); 103 assertEquals(processedArgs.get("--crt"), ImmutableList.of("/cert.crt")); 104 assertEquals(processedArgs.get("--metrics"), ImmutableList.of("15014")); 105 assertEquals(ImmutableList.of("192.168.1.10:7072"), processedArgs.get("--forwarding-address")); 106 assertEquals( 107 processedArgs.get("--port"), 108 ImmutableList.of( 109 "\"18080\"", 110 "\"18085\"", 111 "\"18443\"", 112 "\"18081\"", 113 "\"19443\"", 114 "\"18082\"", 115 "\"18084\"", 116 "\"18083\"", 117 "\"8080\"", 118 "\"3333\"")); 119 } 120 121 @Test preprocessArgsPortsTest()122 public void preprocessArgsPortsTest() { 123 String[] splitArgs = TEST_ARGS_PORTS.split(" "); 124 Map<String, List<String>> processedArgs = EchoTestServer.preprocessArgs(splitArgs); 125 126 Set<Integer> ports = EchoTestServer.getPorts(processedArgs, "--port"); 127 assertThat(ports).containsExactly(18080, 8080, 18081, 18082, 19443, 18083, 18084, 18085, 128 3333, 18443); 129 ports = EchoTestServer.getPorts(processedArgs, "--grpc"); 130 assertThat(ports).containsExactly(17070, 17071); 131 ports = EchoTestServer.getPorts(processedArgs, "--tls"); 132 assertThat(ports).containsExactly(18443, 19443); 133 ports = EchoTestServer.getPorts(processedArgs, "--xds-grpc-server"); 134 assertThat(ports).containsExactly(34012, 12034); 135 } 136 137 138 @Test echoTest()139 public void echoTest() throws IOException, InterruptedException { 140 EchoTestServer echoTestServer = new EchoTestServer(); 141 142 echoTestServer.runServers( 143 "test-host", 144 ImmutableList.of(0, 0), 145 ImmutableList.of(), 146 ImmutableList.of(), 147 "0.0.0.0:7072", 148 null); 149 assertEquals(2, echoTestServer.servers.size()); 150 int port = echoTestServer.servers.get(0).getPort(); 151 assertNotEquals(0, port); 152 assertNotEquals(0, echoTestServer.servers.get(1).getPort()); 153 154 ManagedChannelBuilder<?> channelBuilder = 155 Grpc.newChannelBuilderForAddress("localhost", port, InsecureChannelCredentials.create()); 156 ManagedChannel channel = channelBuilder.build(); 157 158 Metadata metadata = new Metadata(); 159 metadata.put(Metadata.Key.of("header1", Metadata.ASCII_STRING_MARSHALLER), "value1"); 160 metadata.put(Metadata.Key.of("header2", Metadata.ASCII_STRING_MARSHALLER), "value2"); 161 162 EchoTestServiceGrpc.EchoTestServiceBlockingStub stub = 163 EchoTestServiceGrpc.newBlockingStub(channel) 164 .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata)); 165 166 EchoRequest echoRequest = EchoRequest.newBuilder() 167 .setMessage("test-message1") 168 .build(); 169 EchoResponse echoResponse = stub.echo(echoRequest); 170 String echoMessage = echoResponse.getMessage(); 171 Set<String> lines = ImmutableSet.copyOf(echoMessage.split("\n")); 172 173 assertThat(lines).contains("RequestHeader=header1:value1"); 174 assertThat(lines).contains("RequestHeader=header2:value2"); 175 assertThat(lines).contains("Echo=test-message1"); 176 assertThat(lines).contains("Hostname=test-host"); 177 assertThat(lines).contains("Host=localhost:" + port); 178 assertThat(lines).contains("StatusCode=200"); 179 180 echoTestServer.stopServers(); 181 echoTestServer.blockUntilShutdown(); 182 } 183 184 static final int COUNT_OF_REQUESTS_TO_FORWARD = 60; 185 186 @Test forwardEchoTest()187 public void forwardEchoTest() throws IOException, InterruptedException { 188 EchoTestServer echoTestServer = new EchoTestServer(); 189 190 echoTestServer.runServers( 191 "test-host", 192 ImmutableList.of(0, 0), 193 ImmutableList.of(), 194 ImmutableList.of(), 195 "0.0.0.0:7072", 196 null); 197 assertEquals(2, echoTestServer.servers.size()); 198 int port1 = echoTestServer.servers.get(0).getPort(); 199 int port2 = echoTestServer.servers.get(1).getPort(); 200 201 ManagedChannelBuilder<?> channelBuilder = 202 Grpc.newChannelBuilderForAddress("localhost", port1, InsecureChannelCredentials.create()); 203 ManagedChannel channel = channelBuilder.build(); 204 205 ForwardEchoRequest forwardEchoRequest = 206 ForwardEchoRequest.newBuilder() 207 .setCount(COUNT_OF_REQUESTS_TO_FORWARD) 208 .setQps(100) 209 .setTimeoutMicros(5000_000L) // 5000 millis 210 .setUrl("grpc://localhost:" + port2) 211 .addHeaders( 212 Header.newBuilder().setKey("test-key1").setValue("test-value1").build()) 213 .addHeaders( 214 Header.newBuilder().setKey("test-key2").setValue("test-value2").build()) 215 .setMessage("forward-echo-test-message") 216 .build(); 217 218 EchoTestServiceGrpc.EchoTestServiceBlockingStub stub = 219 EchoTestServiceGrpc.newBlockingStub(channel); 220 221 Instant start = Instant.now(); 222 ForwardEchoResponse forwardEchoResponse = stub.forwardEcho(forwardEchoRequest); 223 Instant end = Instant.now(); 224 List<String> outputs = forwardEchoResponse.getOutputList(); 225 assertEquals(COUNT_OF_REQUESTS_TO_FORWARD, outputs.size()); 226 for (int i = 0; i < COUNT_OF_REQUESTS_TO_FORWARD; i++) { 227 validateOutput(outputs.get(i), i); 228 } 229 long duration = Duration.between(start, end).toMillis(); 230 assertThat(duration).isAtLeast(COUNT_OF_REQUESTS_TO_FORWARD * 10L); 231 echoTestServer.stopServers(); 232 echoTestServer.blockUntilShutdown(); 233 } 234 validateOutput(String output, int i)235 private static void validateOutput(String output, int i) { 236 List<String> content = Splitter.on('\n').splitToList(output); 237 assertThat(content.size()).isAtLeast(7); // see echo implementation 238 assertThat(content.get(0)) 239 .isEqualTo(String.format("[%d] grpcecho.Echo(forward-echo-test-message)", i)); 240 String prefix = "[" + i + " body] "; 241 assertThat(content).contains(prefix + "RequestHeader=x-request-id:" + i); 242 assertThat(content).contains(prefix + "RequestHeader=test-key1:test-value1"); 243 assertThat(content).contains(prefix + "RequestHeader=test-key2:test-value2"); 244 assertThat(content).contains(prefix + "Hostname=test-host"); 245 assertThat(content).contains(prefix + "StatusCode=200"); 246 } 247 248 @Test nonGrpcForwardEchoTest()249 public void nonGrpcForwardEchoTest() throws IOException, InterruptedException { 250 ForwardServiceForNonGrpcImpl forwardServiceForNonGrpc = new ForwardServiceForNonGrpcImpl(); 251 forwardServiceForNonGrpc.receivedRequests = new ArrayList<>(); 252 forwardServiceForNonGrpc.responsesToReturn = new ArrayList<>(); 253 Server nonGrpcEchoServer = 254 EchoTestServer.runServer( 255 0, forwardServiceForNonGrpc.bindService(), InsecureServerCredentials.create(), 256 "", false); 257 int nonGrpcEchoServerPort = nonGrpcEchoServer.getPort(); 258 259 EchoTestServer echoTestServer = new EchoTestServer(); 260 261 echoTestServer.runServers( 262 "test-host", 263 ImmutableList.of(0), 264 ImmutableList.of(), 265 ImmutableList.of(), 266 "0.0.0.0:" + nonGrpcEchoServerPort, 267 null); 268 assertEquals(1, echoTestServer.servers.size()); 269 int port1 = echoTestServer.servers.get(0).getPort(); 270 271 ManagedChannelBuilder<?> channelBuilder = 272 Grpc.newChannelBuilderForAddress("localhost", port1, InsecureChannelCredentials.create()); 273 ManagedChannel channel = channelBuilder.build(); 274 275 EchoTestServiceGrpc.EchoTestServiceBlockingStub stub = 276 EchoTestServiceGrpc.newBlockingStub(channel); 277 278 forwardServiceForNonGrpc.responsesToReturn.add( 279 ForwardEchoResponse.newBuilder().addOutput("line 1").addOutput("line 2").build()); 280 281 ForwardEchoRequest forwardEchoRequest = 282 ForwardEchoRequest.newBuilder() 283 .setCount(COUNT_OF_REQUESTS_TO_FORWARD) 284 .setQps(100) 285 .setTimeoutMicros(2000_000L) // 2000 millis 286 .setUrl("http://www.example.com") // non grpc protocol 287 .addHeaders( 288 Header.newBuilder().setKey("test-key1").setValue("test-value1").build()) 289 .addHeaders( 290 Header.newBuilder().setKey("test-key2").setValue("test-value2").build()) 291 .setMessage("non-grpc-forward-echo-test-message1") 292 .build(); 293 294 ForwardEchoResponse forwardEchoResponse = stub.forwardEcho(forwardEchoRequest); 295 List<String> outputs = forwardEchoResponse.getOutputList(); 296 assertEquals(2, outputs.size()); 297 assertThat(outputs.get(0)).isEqualTo("line 1"); 298 assertThat(outputs.get(1)).isEqualTo("line 2"); 299 300 assertThat(forwardServiceForNonGrpc.receivedRequests).hasSize(1); 301 ForwardEchoRequest receivedRequest = forwardServiceForNonGrpc.receivedRequests.remove(0); 302 assertThat(receivedRequest.getUrl()).isEqualTo("http://www.example.com"); 303 assertThat(receivedRequest.getMessage()).isEqualTo("non-grpc-forward-echo-test-message1"); 304 assertThat(receivedRequest.getCount()).isEqualTo(COUNT_OF_REQUESTS_TO_FORWARD); 305 assertThat(receivedRequest.getQps()).isEqualTo(100); 306 307 forwardServiceForNonGrpc.responsesToReturn.add( 308 Status.UNIMPLEMENTED.asRuntimeException()); 309 forwardEchoRequest = 310 ForwardEchoRequest.newBuilder() 311 .setCount(1) 312 .setQps(100) 313 .setTimeoutMicros(2000_000L) // 2000 millis 314 .setUrl("redis://192.168.1.1") // non grpc protocol 315 .addHeaders( 316 Header.newBuilder().setKey("test-key1").setValue("test-value1").build()) 317 .setMessage("non-grpc-forward-echo-test-message2") 318 .build(); 319 320 try { 321 ForwardEchoResponse unused = stub.forwardEcho(forwardEchoRequest); 322 fail("exception expected"); 323 } catch (StatusRuntimeException e) { 324 assertThat(e.getStatus()).isEqualTo(Status.UNIMPLEMENTED); 325 } 326 327 assertThat(forwardServiceForNonGrpc.receivedRequests).hasSize(1); 328 receivedRequest = forwardServiceForNonGrpc.receivedRequests.remove(0); 329 assertThat(receivedRequest.getUrl()).isEqualTo("redis://192.168.1.1"); 330 assertThat(receivedRequest.getMessage()).isEqualTo("non-grpc-forward-echo-test-message2"); 331 assertThat(receivedRequest.getCount()).isEqualTo(1); 332 333 forwardServiceForNonGrpc.responsesToReturn.add( 334 ForwardEchoResponse.newBuilder().addOutput("line 3").build()); 335 336 forwardEchoRequest = 337 ForwardEchoRequest.newBuilder() 338 .setCount(1) 339 .setQps(100) 340 .setTimeoutMicros(2000_000L) // 2000 millis 341 .setUrl("http2://192.168.1.1") // non grpc protocol 342 .addHeaders( 343 Header.newBuilder().setKey("test-key3").setValue("test-value3").build()) 344 .setMessage("non-grpc-forward-echo-test-message3") 345 .build(); 346 forwardEchoResponse = stub.forwardEcho(forwardEchoRequest); 347 outputs = forwardEchoResponse.getOutputList(); 348 assertEquals(1, outputs.size()); 349 assertThat(outputs.get(0)).isEqualTo("line 3"); 350 351 assertThat(forwardServiceForNonGrpc.receivedRequests).hasSize(1); 352 receivedRequest = forwardServiceForNonGrpc.receivedRequests.remove(0); 353 assertThat(receivedRequest.getUrl()).isEqualTo("http2://192.168.1.1"); 354 assertThat(receivedRequest.getMessage()).isEqualTo("non-grpc-forward-echo-test-message3"); 355 List<Header> headers = receivedRequest.getHeadersList(); 356 assertThat(headers).hasSize(1); 357 assertThat(headers.get(0).getKey()).isEqualTo("test-key3"); 358 assertThat(headers.get(0).getValue()).isEqualTo("test-value3"); 359 360 echoTestServer.stopServers(); 361 echoTestServer.blockUntilShutdown(); 362 nonGrpcEchoServer.shutdown(); 363 nonGrpcEchoServer.awaitTermination(5, TimeUnit.SECONDS); 364 } 365 366 /** 367 * Emulate the Go Echo server that receives the non-grpc protocol requests. 368 */ 369 private static class ForwardServiceForNonGrpcImpl extends EchoTestServiceImplBase { 370 371 List<ForwardEchoRequest> receivedRequests; 372 List<Object> responsesToReturn; 373 374 @Override forwardEcho(ForwardEchoRequest request, StreamObserver<ForwardEchoResponse> responseObserver)375 public void forwardEcho(ForwardEchoRequest request, 376 StreamObserver<ForwardEchoResponse> responseObserver) { 377 receivedRequests.add(request); 378 Object response = responsesToReturn.remove(0); 379 if (response instanceof Throwable) { 380 responseObserver.onError((Throwable) response); 381 } else if (response instanceof ForwardEchoResponse) { 382 responseObserver.onNext((ForwardEchoResponse) response); 383 responseObserver.onCompleted(); 384 } 385 responseObserver.onError(new IllegalArgumentException("Unknown type in responsesToReturn")); 386 } 387 } 388 } 389