2 * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.transport.tcp;
10 import static org.hamcrest.CoreMatchers.allOf;
11 import static org.hamcrest.CoreMatchers.containsString;
12 import static org.hamcrest.CoreMatchers.endsWith;
13 import static org.hamcrest.CoreMatchers.startsWith;
14 import static org.hamcrest.MatcherAssert.assertThat;
15 import static org.junit.jupiter.api.Assertions.assertEquals;
16 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
17 import static org.mockito.ArgumentMatchers.any;
18 import static org.mockito.Mockito.doCallRealMethod;
19 import static org.mockito.Mockito.doNothing;
20 import static org.mockito.Mockito.doReturn;
21 import static org.mockito.Mockito.timeout;
22 import static org.mockito.Mockito.verify;
24 import io.netty.channel.EventLoopGroup;
25 import io.netty.channel.socket.ServerSocketChannel;
26 import java.net.InetAddress;
27 import java.util.concurrent.TimeUnit;
28 import org.junit.jupiter.api.AfterAll;
29 import org.junit.jupiter.api.BeforeAll;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.ExtendWith;
32 import org.mockito.ArgumentCaptor;
33 import org.mockito.Mock;
34 import org.mockito.junit.jupiter.MockitoExtension;
35 import org.opendaylight.netconf.transport.api.TransportChannel;
36 import org.opendaylight.netconf.transport.api.TransportChannelListener;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev230417.TcpClientGrouping;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev230417.TcpServerGrouping;
42 import org.opendaylight.yangtools.yang.common.Uint16;
44 @ExtendWith(MockitoExtension.class)
45 public class TCPClientServerTest {
47 private TcpClientGrouping clientGrouping;
49 private TransportChannelListener clientListener;
51 private TcpServerGrouping serverGrouping;
53 private TransportChannelListener serverListener;
55 private static EventLoopGroup group;
58 public static void beforeAll() {
59 group = NettyTransportSupport.newEventLoopGroup("IntegrationTest");
63 public static void afterAll() {
64 group.shutdownGracefully();
69 public void integrationTest() throws Exception {
70 // localhost address, so we do not leak things around
71 final var loopbackAddr = IetfInetUtil.ipAddressFor(InetAddress.getLoopbackAddress());
74 doReturn(loopbackAddr).when(serverGrouping).getLocalAddress();
75 doCallRealMethod().when(serverGrouping).requireLocalAddress();
76 // note: this lets the server pick any port, we do not care
77 doReturn(new PortNumber(Uint16.ZERO)).when(serverGrouping).getLocalPort();
78 doCallRealMethod().when(serverGrouping).requireLocalPort();
80 // Spin up the server and acquire its port
81 final var server = TCPServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
82 serverGrouping).get(2, TimeUnit.SECONDS);
84 assertEquals("TCPServer{listener=serverListener}", server.toString());
86 final var serverChannel = server.listenChannel();
87 assertInstanceOf(ServerSocketChannel.class, serverChannel);
88 final var serverPort = new PortNumber(
89 Uint16.valueOf(((ServerSocketChannel) serverChannel).localAddress().getPort()));
92 doReturn(new Host(loopbackAddr)).when(clientGrouping).getRemoteAddress();
93 doCallRealMethod().when(clientGrouping).requireRemoteAddress();
94 doReturn(serverPort).when(clientGrouping).getRemotePort();
95 doCallRealMethod().when(clientGrouping).requireRemotePort();
97 // Capture client and server channels
98 final var serverCaptor = ArgumentCaptor.forClass(TransportChannel.class);
99 doNothing().when(serverListener).onTransportChannelEstablished(serverCaptor.capture());
100 final var clientCaptor = ArgumentCaptor.forClass(TransportChannel.class);
101 doNothing().when(clientListener).onTransportChannelEstablished(clientCaptor.capture());
103 final var client = TCPClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
104 clientGrouping).get(2, TimeUnit.SECONDS);
106 verify(serverListener, timeout(500)).onTransportChannelEstablished(any());
107 final var serverTransports = serverCaptor.getAllValues();
108 assertEquals(1, serverTransports.size());
109 assertThat(serverTransports.get(0).toString(), allOf(
110 startsWith("TCPTransportChannel{channel=[id: "),
111 containsString(":" + serverPort.getValue() + " - R:")));
113 verify(clientListener, timeout(500)).onTransportChannelEstablished(any());
114 final var clientTransports = clientCaptor.getAllValues();
115 assertEquals(1, clientTransports.size());
116 assertThat(clientTransports.get(0).toString(), allOf(
117 startsWith("TCPTransportChannel{channel=[id: "),
118 endsWith(":" + serverPort.getValue() + "]}")));
120 assertThat(client.toString(), allOf(
121 startsWith("TCPClient{listener=clientListener, state=TCPTransportChannel{channel=[id: 0x"),
122 endsWith(":" + serverPort.getValue() + "]}}")));
124 client.shutdown().get(2, TimeUnit.SECONDS);
127 server.shutdown().get(2, TimeUnit.SECONDS);