1 /* 2 * Copyright 2019 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.xds; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.fail; 21 import static org.mockito.Mockito.any; 22 import static org.mockito.Mockito.mock; 23 import static org.mockito.Mockito.never; 24 import static org.mockito.Mockito.reset; 25 import static org.mockito.Mockito.times; 26 import static org.mockito.Mockito.verify; 27 import static org.mockito.Mockito.when; 28 29 import com.google.common.util.concurrent.SettableFuture; 30 import io.grpc.BindableService; 31 import io.grpc.InsecureServerCredentials; 32 import io.grpc.ServerServiceDefinition; 33 import io.grpc.Status; 34 import io.grpc.StatusException; 35 import io.grpc.testing.GrpcCleanupRule; 36 import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; 37 import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; 38 import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; 39 import java.io.IOException; 40 import java.net.InetSocketAddress; 41 import java.net.ServerSocket; 42 import java.net.SocketAddress; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.concurrent.ExecutionException; 47 import java.util.concurrent.Executors; 48 import java.util.concurrent.Future; 49 import java.util.concurrent.TimeUnit; 50 import java.util.concurrent.TimeoutException; 51 import org.junit.Rule; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 import org.junit.runners.JUnit4; 55 import org.mockito.ArgumentCaptor; 56 57 // TODO (zivy@): move certain tests down to XdsServerWrapperTest or to XdsSecurityClientServerTest 58 /** 59 * Unit tests for {@link XdsServerBuilder}. 60 */ 61 @RunWith(JUnit4.class) 62 public class XdsServerBuilderTest { 63 64 @Rule public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); 65 private XdsServerBuilder builder; 66 private XdsServerWrapper xdsServer; 67 private int port; 68 private TlsContextManager tlsContextManager; 69 private FakeXdsClient xdsClient = new FakeXdsClient(); 70 private FakeXdsClientPoolFactory xdsClientPoolFactory = new FakeXdsClientPoolFactory(xdsClient); 71 buildServer(XdsServerBuilder.XdsServingStatusListener xdsServingStatusListener)72 private void buildServer(XdsServerBuilder.XdsServingStatusListener xdsServingStatusListener) 73 throws IOException { 74 buildBuilder(xdsServingStatusListener); 75 xdsServer = cleanupRule.register((XdsServerWrapper) builder.build()); 76 } 77 buildBuilder(XdsServerBuilder.XdsServingStatusListener xdsServingStatusListener)78 private void buildBuilder(XdsServerBuilder.XdsServingStatusListener xdsServingStatusListener) 79 throws IOException { 80 builder = 81 XdsServerBuilder.forPort( 82 port, XdsServerCredentials.create(InsecureServerCredentials.create())); 83 builder.xdsClientPoolFactory(xdsClientPoolFactory); 84 if (xdsServingStatusListener != null) { 85 builder.xdsServingStatusListener(xdsServingStatusListener); 86 } 87 tlsContextManager = mock(TlsContextManager.class); 88 } 89 verifyServer( Future<Throwable> future, XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener, Status notServingStatus)90 private void verifyServer( 91 Future<Throwable> future, 92 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener, 93 Status notServingStatus) 94 throws InterruptedException, ExecutionException, TimeoutException { 95 if (future != null) { 96 Throwable exception = future.get(5, TimeUnit.SECONDS); 97 assertThat(exception).isNull(); 98 } 99 List<? extends SocketAddress> list = xdsServer.getListenSockets(); 100 assertThat(list).hasSize(1); 101 InetSocketAddress socketAddress = (InetSocketAddress) list.get(0); 102 assertThat(socketAddress.getAddress().isAnyLocalAddress()).isTrue(); 103 assertThat(socketAddress.getPort()).isGreaterThan(-1); 104 if (mockXdsServingStatusListener != null) { 105 if (notServingStatus != null) { 106 ArgumentCaptor<Throwable> argCaptor = ArgumentCaptor.forClass(Throwable.class); 107 verify(mockXdsServingStatusListener, times(1)).onNotServing(argCaptor.capture()); 108 Throwable throwable = argCaptor.getValue(); 109 assertThat(throwable).isInstanceOf(StatusException.class); 110 assertThat(((StatusException) throwable).getStatus()).isEqualTo(notServingStatus); 111 } else { 112 verify(mockXdsServingStatusListener, never()).onNotServing(any(Throwable.class)); 113 verify(mockXdsServingStatusListener, times(1)).onServing(); 114 } 115 } 116 } 117 verifyShutdown()118 private void verifyShutdown() throws InterruptedException { 119 xdsServer.shutdown(); 120 xdsServer.awaitTermination(500L, TimeUnit.MILLISECONDS); 121 assertThat(xdsClient.isShutDown()).isTrue(); 122 } 123 startServerAsync()124 private Future<Throwable> startServerAsync() throws 125 InterruptedException, TimeoutException, ExecutionException { 126 final SettableFuture<Throwable> settableFuture = SettableFuture.create(); 127 Executors.newSingleThreadExecutor().execute(new Runnable() { 128 @Override 129 public void run() { 130 try { 131 xdsServer.start(); 132 settableFuture.set(null); 133 } catch (Throwable e) { 134 settableFuture.set(e); 135 } 136 } 137 }); 138 xdsClient.ldsResource.get(5000, TimeUnit.MILLISECONDS); 139 return settableFuture; 140 } 141 142 @Test xdsServerStartAndShutdown()143 public void xdsServerStartAndShutdown() 144 throws IOException, InterruptedException, TimeoutException, ExecutionException { 145 buildServer(null); 146 Future<Throwable> future = startServerAsync(); 147 XdsServerTestHelper.generateListenerUpdate( 148 xdsClient, 149 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 150 tlsContextManager); 151 verifyServer(future, null, null); 152 verifyShutdown(); 153 } 154 155 @Test xdsServerRestartAfterListenerUpdate()156 public void xdsServerRestartAfterListenerUpdate() 157 throws IOException, InterruptedException, TimeoutException, ExecutionException { 158 buildServer(null); 159 Future<Throwable> future = startServerAsync(); 160 XdsServerTestHelper.generateListenerUpdate( 161 xdsClient, 162 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 163 tlsContextManager); 164 try { 165 xdsServer.start(); 166 fail("expected exception"); 167 } catch (IllegalStateException expected) { 168 assertThat(expected).hasMessageThat().contains("Already started"); 169 } 170 verifyServer(future,null, null); 171 } 172 173 @Test xdsServerStartAndShutdownWithXdsServingStatusListener()174 public void xdsServerStartAndShutdownWithXdsServingStatusListener() 175 throws IOException, InterruptedException, TimeoutException, ExecutionException { 176 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener = 177 mock(XdsServerBuilder.XdsServingStatusListener.class); 178 buildServer(mockXdsServingStatusListener); 179 Future<Throwable> future = startServerAsync(); 180 XdsServerTestHelper.generateListenerUpdate( 181 xdsClient, 182 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 183 tlsContextManager); 184 verifyServer(future, mockXdsServingStatusListener, null); 185 } 186 187 @Test xdsServer_discoverState()188 public void xdsServer_discoverState() throws Exception { 189 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener = 190 mock(XdsServerBuilder.XdsServingStatusListener.class); 191 buildServer(mockXdsServingStatusListener); 192 Future<Throwable> future = startServerAsync(); 193 XdsServerTestHelper.generateListenerUpdate( 194 xdsClient, 195 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 196 tlsContextManager); 197 future.get(5000, TimeUnit.MILLISECONDS); 198 xdsClient.ldsWatcher.onError(Status.ABORTED); 199 verify(mockXdsServingStatusListener, never()).onNotServing(any(StatusException.class)); 200 reset(mockXdsServingStatusListener); 201 xdsClient.ldsWatcher.onError(Status.CANCELLED); 202 verify(mockXdsServingStatusListener, never()).onNotServing(any(StatusException.class)); 203 reset(mockXdsServingStatusListener); 204 xdsClient.ldsWatcher.onResourceDoesNotExist("not found error"); 205 verify(mockXdsServingStatusListener).onNotServing(any(StatusException.class)); 206 reset(mockXdsServingStatusListener); 207 XdsServerTestHelper.generateListenerUpdate( 208 xdsClient, 209 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 210 tlsContextManager); 211 verifyServer(null, mockXdsServingStatusListener, null); 212 } 213 214 @Test xdsServer_startError()215 public void xdsServer_startError() 216 throws IOException, InterruptedException, TimeoutException, ExecutionException { 217 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener = 218 mock(XdsServerBuilder.XdsServingStatusListener.class); 219 ServerSocket serverSocket = new ServerSocket(0); 220 port = serverSocket.getLocalPort(); 221 buildServer(mockXdsServingStatusListener); 222 Future<Throwable> future = startServerAsync(); 223 // create port conflict for start to fail 224 XdsServerTestHelper.generateListenerUpdate( 225 xdsClient, 226 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 227 tlsContextManager); 228 Throwable exception = future.get(5, TimeUnit.SECONDS); 229 assertThat(exception).isInstanceOf(IOException.class); 230 assertThat(exception).hasMessageThat().contains("Failed to bind"); 231 verify(mockXdsServingStatusListener, never()).onNotServing(any(Throwable.class)); 232 serverSocket.close(); 233 } 234 235 @Test xdsServerStartSecondUpdateAndError()236 public void xdsServerStartSecondUpdateAndError() 237 throws IOException, InterruptedException, TimeoutException, ExecutionException { 238 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener = 239 mock(XdsServerBuilder.XdsServingStatusListener.class); 240 buildServer(mockXdsServingStatusListener); 241 Future<Throwable> future = startServerAsync(); 242 XdsServerTestHelper.generateListenerUpdate( 243 xdsClient, 244 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 245 tlsContextManager); 246 XdsServerTestHelper.generateListenerUpdate( 247 xdsClient, 248 CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"), 249 tlsContextManager); 250 verify(mockXdsServingStatusListener, never()).onNotServing(any(Throwable.class)); 251 verifyServer(future, mockXdsServingStatusListener, null); 252 xdsClient.ldsWatcher.onError(Status.ABORTED); 253 verifyServer(null, mockXdsServingStatusListener, null); 254 } 255 256 @Test xdsServer_2ndBuild_expectException()257 public void xdsServer_2ndBuild_expectException() throws IOException { 258 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener = 259 mock(XdsServerBuilder.XdsServingStatusListener.class); 260 buildServer(mockXdsServingStatusListener); 261 try { 262 builder.build(); 263 fail("exception expected"); 264 } catch (IllegalStateException expected) { 265 assertThat(expected).hasMessageThat().contains("Server already built!"); 266 } 267 } 268 269 @Test xdsServer_2ndSetter_expectException()270 public void xdsServer_2ndSetter_expectException() throws IOException { 271 XdsServerBuilder.XdsServingStatusListener mockXdsServingStatusListener = 272 mock(XdsServerBuilder.XdsServingStatusListener.class); 273 buildBuilder(mockXdsServingStatusListener); 274 BindableService mockBindableService = mock(BindableService.class); 275 ServerServiceDefinition serverServiceDefinition = io.grpc.ServerServiceDefinition 276 .builder("mock").build(); 277 when(mockBindableService.bindService()).thenReturn(serverServiceDefinition); 278 builder.addService(mockBindableService); 279 xdsServer = cleanupRule.register((XdsServerWrapper) builder.build()); 280 try { 281 builder.addService(mock(BindableService.class)); 282 fail("exception expected"); 283 } catch (IllegalStateException expected) { 284 assertThat(expected).hasMessageThat().contains("Server already built!"); 285 } 286 } 287 288 @Test drainGraceTime_negativeThrows()289 public void drainGraceTime_negativeThrows() throws IOException { 290 buildBuilder(null); 291 try { 292 builder.drainGraceTime(-1, TimeUnit.SECONDS); 293 fail("exception expected"); 294 } catch (IllegalArgumentException expected) { 295 assertThat(expected).hasMessageThat().contains("drain grace time"); 296 } 297 } 298 299 @Test testOverrideBootstrap()300 public void testOverrideBootstrap() throws Exception { 301 Map<String, Object> b = new HashMap<>(); 302 buildBuilder(null); 303 builder.overrideBootstrapForTest(b); 304 assertThat(xdsClientPoolFactory.savedBootstrap).isEqualTo(b); 305 } 306 } 307