• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.http.nio.netty.internal;
17 
18 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
19 import static org.mockito.ArgumentMatchers.any;
20 import static org.mockito.ArgumentMatchers.eq;
21 import static org.mockito.Mockito.mock;
22 import static org.mockito.Mockito.never;
23 import static org.mockito.Mockito.times;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when;
26 import static software.amazon.awssdk.http.nio.netty.internal.Http1TunnelConnectionPool.TUNNEL_ESTABLISHED_KEY;
27 
28 import io.netty.buffer.ByteBufAllocator;
29 import io.netty.channel.Channel;
30 import io.netty.channel.ChannelHandler;
31 import io.netty.channel.ChannelHandlerContext;
32 import io.netty.channel.ChannelId;
33 import io.netty.channel.ChannelPipeline;
34 import io.netty.channel.nio.NioEventLoopGroup;
35 import io.netty.channel.pool.ChannelPool;
36 import io.netty.channel.pool.ChannelPoolHandler;
37 import io.netty.handler.ssl.ApplicationProtocolNegotiator;
38 import io.netty.handler.ssl.SslContext;
39 import io.netty.handler.ssl.SslHandler;
40 import io.netty.util.Attribute;
41 import io.netty.util.concurrent.Future;
42 import io.netty.util.concurrent.Promise;
43 import java.io.IOException;
44 import java.net.URI;
45 import java.util.List;
46 import java.util.concurrent.CountDownLatch;
47 import javax.net.ssl.SSLEngine;
48 import javax.net.ssl.SSLParameters;
49 import javax.net.ssl.SSLSessionContext;
50 import org.junit.AfterClass;
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.ArgumentCaptor;
55 import org.mockito.Mock;
56 import org.mockito.junit.MockitoJUnitRunner;
57 import software.amazon.awssdk.http.SdkHttpConfigurationOption;
58 
59 /**
60  * Unit tests for {@link Http1TunnelConnectionPool}.
61  */
62 @RunWith(MockitoJUnitRunner.class)
63 public class Http1TunnelConnectionPoolTest {
64     private static final NioEventLoopGroup GROUP = new NioEventLoopGroup(1);
65 
66     private static final URI HTTP_PROXY_ADDRESS = URI.create("http://localhost:1234");
67 
68     private static final URI HTTPS_PROXY_ADDRESS = URI.create("https://localhost:5678");
69 
70     private static final URI REMOTE_ADDRESS = URI.create("https://s3.amazonaws.com:5678");
71 
72     private static final String PROXY_USER = "myuser";
73 
74     private static final String PROXY_PASSWORD = "mypassword";
75 
76     @Mock
77     private ChannelPool delegatePool;
78 
79     @Mock
80     private ChannelPoolHandler mockHandler;
81 
82     @Mock
83     public Channel mockChannel;
84 
85     @Mock
86     public ChannelPipeline mockPipeline;
87 
88     @Mock
89     public Attribute mockAttr;
90 
91     @Mock
92     public ChannelHandlerContext mockCtx;
93 
94     @Mock
95     public ChannelId mockId;
96 
97     public NettyConfiguration configuration;
98 
99     @Before
methodSetup()100     public void methodSetup() {
101         Future<Channel> channelFuture = GROUP.next().newSucceededFuture(mockChannel);
102         when(delegatePool.acquire(any(Promise.class))).thenReturn(channelFuture);
103 
104         when(mockChannel.attr(eq(TUNNEL_ESTABLISHED_KEY))).thenReturn(mockAttr);
105         when(mockChannel.id()).thenReturn(mockId);
106         when(mockChannel.pipeline()).thenReturn(mockPipeline);
107         configuration = new NettyConfiguration(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS);
108     }
109 
110     @AfterClass
teardown()111     public static void teardown() {
112         GROUP.shutdownGracefully().awaitUninterruptibly();
113     }
114 
115     @Test
tunnelAlreadyEstablished_doesNotAddInitHandler()116     public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
117         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
118                 HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
119 
120         when(mockAttr.get()).thenReturn(true);
121 
122         tunnelPool.acquire().awaitUninterruptibly();
123 
124         verify(mockPipeline, never()).addLast(any());
125     }
126 
127     @Test(timeout = 1000)
tunnelNotEstablished_addsInitHandler()128     public void tunnelNotEstablished_addsInitHandler() throws InterruptedException {
129         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
130                 HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
131 
132         when(mockAttr.get()).thenReturn(false);
133 
134         CountDownLatch latch = new CountDownLatch(1);
135         when(mockPipeline.addLast(any(ChannelHandler.class))).thenAnswer(i -> {
136            latch.countDown();
137            return mockPipeline;
138         });
139         tunnelPool.acquire();
140         latch.await();
141         verify(mockPipeline, times(1)).addLast(any(ProxyTunnelInitHandler.class));
142     }
143 
144     @Test
tunnelInitFails_acquireFutureFails()145     public void tunnelInitFails_acquireFutureFails() {
146         Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
147             initFuture.setFailure(new IOException("boom"));
148             return mock(ChannelHandler.class);
149         };
150 
151         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
152                 HTTP_PROXY_ADDRESS,null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
153 
154         Future<Channel> acquireFuture = tunnelPool.acquire();
155 
156         assertThat(acquireFuture.awaitUninterruptibly().cause()).hasMessage("boom");
157     }
158 
159     @Test
tunnelInitSucceeds_acquireFutureSucceeds()160     public void tunnelInitSucceeds_acquireFutureSucceeds() {
161         Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
162             initFuture.setSuccess(mockChannel);
163             return mock(ChannelHandler.class);
164         };
165 
166         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
167                 HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
168 
169         Future<Channel> acquireFuture = tunnelPool.acquire();
170 
171         assertThat(acquireFuture.awaitUninterruptibly().getNow()).isEqualTo(mockChannel);
172     }
173 
174     @Test
acquireFromDelegatePoolFails_failsFuture()175     public void acquireFromDelegatePoolFails_failsFuture() {
176         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
177                 HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
178 
179         when(delegatePool.acquire(any(Promise.class))).thenReturn(GROUP.next().newFailedFuture(new IOException("boom")));
180 
181         Future<Channel> acquireFuture = tunnelPool.acquire();
182 
183         assertThat(acquireFuture.awaitUninterruptibly().cause()).hasMessage("boom");
184     }
185 
186     @Test
sslContextProvided_andProxyUsingHttps_addsSslHandler()187     public void sslContextProvided_andProxyUsingHttps_addsSslHandler() {
188         SslHandler mockSslHandler = mock(SslHandler.class);
189         SSLEngine mockSslEngine = mock(SSLEngine.class);
190         when(mockSslHandler.engine()).thenReturn(mockSslEngine);
191         when(mockSslEngine.getSSLParameters()).thenReturn(mock(SSLParameters.class));
192         TestSslContext mockSslCtx = new TestSslContext(mockSslHandler);
193 
194         Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
195             initFuture.setSuccess(mockChannel);
196             return mock(ChannelHandler.class);
197         };
198 
199         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
200                 HTTPS_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
201 
202         tunnelPool.acquire().awaitUninterruptibly();
203 
204         ArgumentCaptor<ChannelHandler> handlersCaptor = ArgumentCaptor.forClass(ChannelHandler.class);
205         verify(mockPipeline, times(2)).addLast(handlersCaptor.capture());
206 
207         assertThat(handlersCaptor.getAllValues().get(0)).isEqualTo(mockSslHandler);
208     }
209 
210     @Test
sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler()211     public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
212         SslHandler mockSslHandler = mock(SslHandler.class);
213         TestSslContext mockSslCtx = new TestSslContext(mockSslHandler);
214 
215         Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
216             initFuture.setSuccess(mockChannel);
217             return mock(ChannelHandler.class);
218         };
219 
220         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
221                 HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
222 
223         tunnelPool.acquire().awaitUninterruptibly();
224 
225         ArgumentCaptor<ChannelHandler> handlersCaptor = ArgumentCaptor.forClass(ChannelHandler.class);
226         verify(mockPipeline).addLast(handlersCaptor.capture());
227 
228         assertThat(handlersCaptor.getAllValues().get(0)).isNotInstanceOf(SslHandler.class);
229     }
230 
231     @Test
release_releasedToDelegatePool()232     public void release_releasedToDelegatePool() {
233         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
234                 HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
235         tunnelPool.release(mockChannel);
236         verify(delegatePool).release(eq(mockChannel), any(Promise.class));
237     }
238 
239     @Test
release_withGivenPromise_releasedToDelegatePool()240     public void release_withGivenPromise_releasedToDelegatePool() {
241         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
242                 HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
243         Promise mockPromise = mock(Promise.class);
244         tunnelPool.release(mockChannel, mockPromise);
245         verify(delegatePool).release(eq(mockChannel), eq(mockPromise));
246     }
247 
248     @Test
close_closesDelegatePool()249     public void close_closesDelegatePool() {
250         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
251                 HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
252         tunnelPool.close();
253         verify(delegatePool).close();
254     }
255 
256     @Test
proxyAuthProvided_addInitHandler_withAuth()257     public void proxyAuthProvided_addInitHandler_withAuth(){
258         TestInitHandlerData data = new TestInitHandlerData();
259 
260         Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
261             initFuture.setSuccess(mockChannel);
262             data.proxyUser(proxyUser);
263             data.proxyPassword(proxyPassword);
264             return mock(ChannelHandler.class);
265         };
266 
267         Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
268                 HTTP_PROXY_ADDRESS, PROXY_USER, PROXY_PASSWORD, REMOTE_ADDRESS, mockHandler, supplier, configuration);
269 
270         tunnelPool.acquire().awaitUninterruptibly();
271 
272         assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
273         assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
274 
275     }
276 
277     private static class TestInitHandlerData {
278 
279         private String proxyUser;
280         private String proxyPassword;
281 
proxyUser(String proxyUser)282         public void proxyUser(String proxyUser) {
283             this.proxyUser = proxyUser;
284         }
285 
proxyUser()286         public String proxyUser() {
287             return this.proxyUser;
288         }
289 
proxyPassword(String proxyPassword)290         public void proxyPassword(String proxyPassword) {
291             this.proxyPassword = proxyPassword;
292         }
293 
proxyPassword()294         public String proxyPassword(){
295             return this.proxyPassword;
296         }
297 
298     }
299 
300     private static class TestSslContext extends SslContext {
301         private final SslHandler handler;
302 
TestSslContext(SslHandler handler)303         protected TestSslContext(SslHandler handler) {
304             this.handler = handler;
305         }
306 
307         @Override
isClient()308         public boolean isClient() {
309             return false;
310         }
311 
312         @Override
cipherSuites()313         public List<String> cipherSuites() {
314             return null;
315         }
316 
317         @Override
sessionCacheSize()318         public long sessionCacheSize() {
319             return 0;
320         }
321 
322         @Override
sessionTimeout()323         public long sessionTimeout() {
324             return 0;
325         }
326 
327         @Override
applicationProtocolNegotiator()328         public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
329             return null;
330         }
331 
332         @Override
newEngine(ByteBufAllocator alloc)333         public SSLEngine newEngine(ByteBufAllocator alloc) {
334             return null;
335         }
336 
337         @Override
newEngine(ByteBufAllocator alloc, String peerHost, int peerPort)338         public SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
339             return null;
340         }
341 
342         @Override
sessionContext()343         public SSLSessionContext sessionContext() {
344             return null;
345         }
346 
347         @Override
newHandler(ByteBufAllocator alloc, String host, int port, boolean startTls)348         public SslHandler newHandler(ByteBufAllocator alloc, String host, int port, boolean startTls) {
349             return handler;
350         }
351     }
352 }
353