BUG 2429 - not releasing dead connection threads
[openflowplugin.git] / openflowplugin-impl / src / test / java / org / opendaylight / openflowplugin / impl / connection / listener / SystemNotificationsListenerImplTest.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.openflowplugin.impl.connection.listener;
10
11 import static org.junit.Assert.*;
12
13 import org.opendaylight.openflowplugin.impl.connection.HandshakeContextImpl;
14
15 import java.util.concurrent.ArrayBlockingQueue;
16 import java.util.concurrent.TimeUnit;
17 import org.opendaylight.openflowplugin.openflow.md.core.ThreadPoolLoggingExecutor;
18 import com.google.common.util.concurrent.Futures;
19 import com.google.common.util.concurrent.SettableFuture;
20 import java.net.InetSocketAddress;
21 import java.util.List;
22 import org.junit.After;
23 import org.junit.Assert;
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.mockito.ArgumentCaptor;
28 import org.mockito.Captor;
29 import org.mockito.Matchers;
30 import org.mockito.Mock;
31 import org.mockito.Mockito;
32 import org.mockito.runners.MockitoJUnitRunner;
33 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
34 import org.opendaylight.openflowplugin.impl.connection.ConnectionContextImpl;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoInput;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoOutput;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoOutputBuilder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FeaturesReply;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.DisconnectEvent;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.DisconnectEventBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SwitchIdleEvent;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SwitchIdleEventBuilder;
43 import org.opendaylight.yangtools.yang.common.RpcResult;
44 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
45
46 /**
47  * Testing basic bahavior of {@link SystemNotificationsListenerImpl}
48  */
49 @RunWith(MockitoJUnitRunner.class)
50 public class SystemNotificationsListenerImplTest {
51
52     public static final int SAFE_TIMEOUT = 1000;
53     @Mock
54     private org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter connectionAdapter;
55     @Mock
56     private FeaturesReply features;
57     private ConnectionContext connectionContext;
58     @Captor
59     private ArgumentCaptor<ConnectionContext.CONNECTION_STATE> connectionStateArgumentCaptor;
60
61     private SystemNotificationsListenerImpl systemNotificationsListener;
62     private ConnectionContextImpl connectionContextGolem;
63
64     @Before
65     public void setUp() {
66         connectionContextGolem = new ConnectionContextImpl(connectionAdapter);
67         connectionContextGolem.setConnectionState(ConnectionContext.CONNECTION_STATE.WORKING);
68
69         Mockito.when(connectionAdapter.getRemoteAddress()).thenReturn(
70                 InetSocketAddress.createUnresolved("unit-odl.example.org", 4242));
71         connectionContext = Mockito.spy(connectionContextGolem);
72         Mockito.when(features.getAuxiliaryId()).thenReturn((short) 0);
73
74         Mockito.when(connectionContext.getConnectionAdapter()).thenReturn(connectionAdapter);
75         Mockito.when(connectionContext.getFeatures()).thenReturn(features);
76
77         ThreadPoolLoggingExecutor threadPoolLoggingExecutor = new ThreadPoolLoggingExecutor(2000, 2000, 0L, TimeUnit.MILLISECONDS,
78                 new ArrayBlockingQueue<Runnable>(20), "OFHandshake-test identifier");
79
80         systemNotificationsListener = new SystemNotificationsListenerImpl(connectionContext,
81                new HandshakeContextImpl(threadPoolLoggingExecutor, null));
82     }
83
84     @After
85     public void tearDown() throws Exception {
86         Mockito.verifyNoMoreInteractions(connectionContext);
87     }
88
89     /**
90      * successful scenario - connection is on and closes without errors
91      *
92      * @throws Exception
93      */
94     @Test
95     public void testOnDisconnectEvent1() throws Exception {
96         Mockito.when(connectionAdapter.isAlive()).thenReturn(true);
97         Mockito.when(connectionAdapter.disconnect()).thenReturn(Futures.immediateFuture(Boolean.TRUE));
98
99         DisconnectEvent disconnectNotification = new DisconnectEventBuilder().setInfo("testing disconnect").build();
100         systemNotificationsListener.onDisconnectEvent(disconnectNotification);
101
102         verifyCommonInvocations();
103         Mockito.verify(connectionAdapter).disconnect();
104         Mockito.verify(connectionContext).setConnectionState(ConnectionContext.CONNECTION_STATE.RIP);
105         Mockito.verify(connectionContext).propagateClosingConnection();
106         assertTrue(systemNotificationsListener.handshakeContext.getHandshakePool().isTerminated());
107     }
108
109     /**
110      * broken scenario - connection is on but fails to close
111      *
112      * @throws Exception
113      */
114     @Test
115     public void testOnDisconnectEvent2() throws Exception {
116         Mockito.when(connectionAdapter.isAlive()).thenReturn(true);
117         Mockito.when(connectionAdapter.disconnect()).thenReturn(Futures.immediateFuture(Boolean.FALSE));
118
119         DisconnectEvent disconnectNotification = new DisconnectEventBuilder().setInfo("testing disconnect").build();
120         systemNotificationsListener.onDisconnectEvent(disconnectNotification);
121
122         verifyCommonInvocations();
123         Mockito.verify(connectionAdapter).disconnect();
124         Mockito.verify(connectionContext).setConnectionState(ConnectionContext.CONNECTION_STATE.RIP);
125         Mockito.verify(connectionContext).propagateClosingConnection();
126         assertTrue(systemNotificationsListener.handshakeContext.getHandshakePool().isTerminated());
127     }
128
129     /**
130      * successful scenario - connection is already down
131      *
132      * @throws Exception
133      */
134     @Test
135     public void testOnDisconnectEvent3() throws Exception {
136         connectionContextGolem.setConnectionState(ConnectionContext.CONNECTION_STATE.TIMEOUTING);
137
138         Mockito.when(connectionAdapter.isAlive()).thenReturn(true);
139         Mockito.when(connectionAdapter.disconnect()).thenReturn(Futures.<Boolean>immediateFailedFuture(new Exception("unit exception")));
140
141         DisconnectEvent disconnectNotification = new DisconnectEventBuilder().setInfo("testing disconnect").build();
142         systemNotificationsListener.onDisconnectEvent(disconnectNotification);
143
144         verifyCommonInvocations();
145         Mockito.verify(connectionAdapter).disconnect();
146         Mockito.verify(connectionContext).setConnectionState(ConnectionContext.CONNECTION_STATE.RIP);
147         Mockito.verify(connectionContext).propagateClosingConnection();
148         assertTrue(systemNotificationsListener.handshakeContext.getHandshakePool().isTerminated());
149     }
150
151     /**
152      * broken scenario - connection is on but throws error on close
153      *
154      * @throws Exception
155      */
156     @Test
157     public void testOnDisconnectEvent4() throws Exception {
158         connectionContextGolem.setConnectionState(ConnectionContext.CONNECTION_STATE.RIP);
159
160         Mockito.when(connectionAdapter.isAlive()).thenReturn(false);
161
162         DisconnectEvent disconnectNotification = new DisconnectEventBuilder().setInfo("testing disconnect").build();
163         systemNotificationsListener.onDisconnectEvent(disconnectNotification);
164
165         verifyCommonInvocations();
166         Mockito.verify(connectionAdapter, Mockito.never()).disconnect();
167         Mockito.verify(connectionContext).setConnectionState(ConnectionContext.CONNECTION_STATE.RIP);
168         Mockito.verify(connectionContext).propagateClosingConnection();
169         assertTrue(systemNotificationsListener.handshakeContext.getHandshakePool().isTerminated());
170     }
171
172     /**
173      * first encounter of idle event, echo received successfully
174      *
175      * @throws Exception
176      */
177     @Test
178     public void testOnSwitchIdleEvent1() throws Exception {
179         final SettableFuture<RpcResult<EchoOutput>> echoReply = SettableFuture.create();
180         Mockito.when(connectionAdapter.echo(Matchers.any(EchoInput.class))).thenReturn(echoReply);
181
182         SwitchIdleEvent notification = new SwitchIdleEventBuilder().setInfo("wake up, device sleeps").build();
183         systemNotificationsListener.onSwitchIdleEvent(notification);
184
185         // make sure that the idle notification processing thread started
186         Thread.sleep(SAFE_TIMEOUT);
187         EchoOutput echoReplyVal = new EchoOutputBuilder().build();
188         echoReply.set(RpcResultBuilder.success(echoReplyVal).build());
189
190         Mockito.verify(connectionContext, Mockito.timeout(SAFE_TIMEOUT).times(2))
191                 .setConnectionState(connectionStateArgumentCaptor.capture());
192         List<ConnectionContext.CONNECTION_STATE> allStates = connectionStateArgumentCaptor.getAllValues();
193         Assert.assertEquals(2, allStates.size());
194         Assert.assertEquals(ConnectionContext.CONNECTION_STATE.TIMEOUTING, allStates.get(0));
195         Assert.assertEquals(ConnectionContext.CONNECTION_STATE.WORKING, allStates.get(1));
196
197         verifyCommonInvocations();
198         Mockito.verify(connectionAdapter, Mockito.timeout(SAFE_TIMEOUT)).echo(Matchers.any(EchoInput.class));
199         Mockito.verify(connectionContext, Mockito.timeout(SAFE_TIMEOUT)).setConnectionState(ConnectionContext.CONNECTION_STATE.WORKING);
200         Mockito.verify(connectionAdapter, Mockito.never()).disconnect();
201         assertFalse(systemNotificationsListener.handshakeContext.getHandshakePool().isTerminated());
202     }
203
204     /**
205      * first encounter of idle event, echo not receive
206      *
207      * @throws Exception
208      */
209     @Test
210     public void testOnSwitchIdleEvent2() throws Exception {
211         final SettableFuture<RpcResult<EchoOutput>> echoReply = SettableFuture.create();
212         Mockito.when(connectionAdapter.echo(Matchers.any(EchoInput.class))).thenReturn(echoReply);
213         Mockito.when(connectionAdapter.isAlive()).thenReturn(true);
214         Mockito.when(connectionAdapter.disconnect()).thenReturn(Futures.<Boolean>immediateFailedFuture(new Exception("unit exception")));
215
216         SwitchIdleEvent notification = new SwitchIdleEventBuilder().setInfo("wake up, device sleeps").build();
217         systemNotificationsListener.onSwitchIdleEvent(notification);
218
219         Thread.sleep(SystemNotificationsListenerImpl.MAX_ECHO_REPLY_TIMEOUT + SAFE_TIMEOUT);
220
221         verifyCommonInvocations();
222         Mockito.verify(connectionAdapter, Mockito.timeout(SAFE_TIMEOUT)).echo(Matchers.any(EchoInput.class));
223         Mockito.verify(connectionContext, Mockito.timeout(SAFE_TIMEOUT).times(2))
224                 .setConnectionState(connectionStateArgumentCaptor.capture());
225         List<ConnectionContext.CONNECTION_STATE> allStates = connectionStateArgumentCaptor.getAllValues();
226         Assert.assertEquals(2, allStates.size());
227         Assert.assertEquals(ConnectionContext.CONNECTION_STATE.TIMEOUTING, allStates.get(0));
228         Assert.assertEquals(ConnectionContext.CONNECTION_STATE.RIP, allStates.get(1));
229
230         Mockito.verify(connectionAdapter).disconnect();
231         Mockito.verify(connectionContext).propagateClosingConnection();
232         assertTrue(systemNotificationsListener.handshakeContext.getHandshakePool().isTerminated());
233     }
234
235     private void verifyCommonInvocations() {
236         Mockito.verify(connectionContext, Mockito.timeout(SAFE_TIMEOUT).atLeastOnce()).getConnectionState();
237         Mockito.verify(connectionContext, Mockito.timeout(SAFE_TIMEOUT).atLeastOnce()).getFeatures();
238         Mockito.verify(connectionContext, Mockito.timeout(SAFE_TIMEOUT).atLeastOnce()).getConnectionAdapter();
239     }
240 }