Merge "Implement basic ShardTransactionChain#CloseTransactionChain"
[controller.git] / opendaylight / md-sal / sal-netconf-connector / src / test / java / org / opendaylight / controller / sal / connect / netconf / listener / NetconfDeviceCommunicatorTest.java
1 /*
2  * Copyright (c) 2014 Brocade Communications 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.controller.sal.connect.netconf.listener;
10
11 import io.netty.channel.ChannelFuture;
12 import io.netty.util.concurrent.Future;
13 import io.netty.util.concurrent.GenericFutureListener;
14
15 import java.io.ByteArrayInputStream;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.UUID;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21
22 import javax.xml.parsers.DocumentBuilderFactory;
23 import javax.xml.parsers.ParserConfigurationException;
24
25 import static org.mockito.Mockito.doNothing;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.reset;
29 import static org.mockito.Mockito.mock;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Matchers.any;
32 import static org.mockito.Matchers.eq;
33 import static org.mockito.Matchers.same;
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertNotNull;
36 import static org.junit.Assert.assertTrue;
37 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY;
38 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
39
40 import org.apache.commons.lang3.StringUtils;
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.mockito.ArgumentCaptor;
44 import org.mockito.Mock;
45 import org.mockito.MockitoAnnotations;
46 import org.opendaylight.controller.netconf.api.NetconfMessage;
47 import org.opendaylight.controller.netconf.api.NetconfTerminationReason;
48 import org.opendaylight.controller.netconf.client.NetconfClientSession;
49 import org.opendaylight.controller.sal.connect.api.RemoteDevice;
50 import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator;
51 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
52 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
53 import org.opendaylight.yangtools.yang.common.QName;
54 import org.opendaylight.yangtools.yang.common.RpcError;
55 import org.opendaylight.yangtools.yang.common.RpcResult;
56 import org.w3c.dom.Document;
57 import org.w3c.dom.Element;
58
59 import com.google.common.base.Strings;
60 import com.google.common.collect.Sets;
61 import com.google.common.util.concurrent.ListenableFuture;
62
63 public class NetconfDeviceCommunicatorTest {
64
65     @Mock
66     NetconfClientSession mockSession;
67
68     @Mock
69     RemoteDevice<NetconfSessionCapabilities, NetconfMessage> mockDevice;
70
71     NetconfDeviceCommunicator communicator;
72
73     @Before
74     public void setUp() throws Exception {
75         MockitoAnnotations.initMocks( this );
76
77         communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice );
78     }
79
80     @SuppressWarnings("unchecked")
81     void setupSession()
82     {
83         doReturn( Collections.<String>emptySet() ).when( mockSession ).getServerCapabilities();
84         doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ),
85                                                           any( RemoteDeviceCommunicator.class ) );
86         communicator.onSessionUp( mockSession );
87     }
88
89     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
90         return sendRequest( UUID.randomUUID().toString() );
91     }
92
93     @SuppressWarnings("unchecked")
94     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest( String messageID ) throws Exception {
95         Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
96         Element element = doc.createElement( "request" );
97         element.setAttribute( "message-id", messageID );
98         doc.appendChild( element );
99         NetconfMessage message = new NetconfMessage( doc );
100
101         ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
102         doReturn( mockChannelFuture ).when( mockChannelFuture )
103             .addListener( any( (GenericFutureListener.class ) ) );
104         doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
105
106         ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
107                                       communicator.sendRequest( message, QName.create( "mock rpc" ) );
108
109         assertNotNull( "ListenableFuture is null", resultFuture );
110         return resultFuture;
111     }
112
113     @Test
114     public void testOnSessionUp() {
115         String testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
116         Collection<String> serverCapabilities =
117                 Sets.newHashSet( NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
118                                  NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
119                                  testCapability );
120         doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities();
121
122         ArgumentCaptor<NetconfSessionCapabilities> netconfSessionCapabilities =
123                                               ArgumentCaptor.forClass( NetconfSessionCapabilities.class );
124         doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
125
126         communicator.onSessionUp( mockSession );
127
128         verify( mockSession ).getServerCapabilities();
129         verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
130
131         NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue();
132         assertEquals( "containsCapability", true, actualCapabilites.containsCapability(
133                                 NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString() ) );
134         assertEquals( "containsCapability", true, actualCapabilites.containsCapability( testCapability ) );
135         assertEquals( "getModuleBasedCaps", Sets.newHashSet(
136                             QName.create( "urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module" )),
137                       actualCapabilites.getModuleBasedCaps() );
138         assertEquals( "isRollbackSupported", true, actualCapabilites.isRollbackSupported() );
139         assertEquals( "isMonitoringSupported", true, actualCapabilites.isMonitoringSupported() );
140     }
141
142     @SuppressWarnings("unchecked")
143     @Test(timeout=5000)
144     public void testOnSessionDown() throws Exception {
145         setupSession();
146
147         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
148         ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
149
150         doNothing().when( mockDevice ).onRemoteSessionDown();
151
152         communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
153
154         verifyErrorRpcResult( resultFuture1.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
155         verifyErrorRpcResult( resultFuture2.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
156
157         verify( mockDevice ).onRemoteSessionDown();
158
159         reset( mockDevice );
160
161         communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
162
163         verify( mockDevice, never() ).onRemoteSessionDown();
164     }
165
166     @Test
167     public void testOnSessionTerminated() throws Exception {
168         setupSession();
169
170         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
171
172         doNothing().when( mockDevice ).onRemoteSessionDown();
173
174         String reasonText = "testing terminate";
175         NetconfTerminationReason reason = new NetconfTerminationReason( reasonText );
176         communicator.onSessionTerminated( mockSession, reason );
177
178         RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.TRANSPORT,
179                                                   "operation-failed" );
180         assertEquals( "RpcError message", reasonText, rpcError.getMessage() );
181
182         verify( mockDevice ).onRemoteSessionDown();
183     }
184
185     @Test
186     public void testClose() throws Exception {
187         communicator.close();
188         verify( mockDevice, never() ).onRemoteSessionDown();
189     }
190
191     @SuppressWarnings({ "rawtypes", "unchecked" })
192     @Test
193     public void testSendRequest() throws Exception {
194         setupSession();
195
196         NetconfMessage message = new NetconfMessage(
197                               DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
198         QName rpc = QName.create( "mock rpc" );
199
200         ArgumentCaptor<GenericFutureListener> futureListener =
201                                             ArgumentCaptor.forClass( GenericFutureListener.class );
202
203         ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
204         doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
205         doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
206
207         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
208
209         verify( mockSession ).sendMessage( same( message ) );
210
211         assertNotNull( "ListenableFuture is null", resultFuture );
212
213         verify( mockChannelFuture ).addListener( futureListener.capture() );
214         Future<Void> operationFuture = mock( Future.class );
215         doReturn( true ).when( operationFuture ).isSuccess();
216         doReturn( true ).when( operationFuture ).isDone();
217         futureListener.getValue().operationComplete( operationFuture );
218
219         try {
220             resultFuture.get( 1, TimeUnit.MILLISECONDS ); // verify it's not cancelled or has an error set
221         }
222         catch( TimeoutException e ) {} // expected
223     }
224
225     @Test
226     public void testSendRequestWithNoSession() throws Exception {
227         NetconfMessage message = new NetconfMessage(
228                               DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
229         QName rpc = QName.create( "mock rpc" );
230
231         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
232
233         assertNotNull( "ListenableFuture is null", resultFuture );
234
235         // Should have an immediate result
236         RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
237
238         verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
239     }
240
241     @SuppressWarnings({ "rawtypes", "unchecked" })
242     @Test
243     public void testSendRequestWithWithSendFailure() throws Exception {
244         setupSession();
245
246         NetconfMessage message = new NetconfMessage(
247                               DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
248         QName rpc = QName.create( "mock rpc" );
249
250         ArgumentCaptor<GenericFutureListener> futureListener =
251                                             ArgumentCaptor.forClass( GenericFutureListener.class );
252
253         ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
254         doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
255         doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
256
257         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
258
259         assertNotNull( "ListenableFuture is null", resultFuture );
260
261         verify( mockChannelFuture ).addListener( futureListener.capture() );
262
263         Future<Void> operationFuture = mock( Future.class );
264         doReturn( false ).when( operationFuture ).isSuccess();
265         doReturn( true ).when( operationFuture ).isDone();
266         doReturn( new Exception( "mock error" ) ).when( operationFuture ).cause();
267         futureListener.getValue().operationComplete( operationFuture );
268
269         // Should have an immediate result
270         RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
271
272         RpcError rpcError = verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
273         assertEquals( "RpcError message contains \"mock error\"", true,
274                     rpcError.getMessage().contains( "mock error" ) );
275     }
276
277     private NetconfMessage createSuccessResponseMessage( String messageID ) throws ParserConfigurationException {
278         Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
279         Element rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY );
280         rpcReply.setAttribute( "message-id", messageID );
281         Element element = doc.createElementNS( "ns", "data" );
282         element.setTextContent( messageID );
283         rpcReply.appendChild( element );
284         doc.appendChild( rpcReply );
285
286         return new NetconfMessage( doc );
287     }
288
289     @Test
290     public void testOnSuccessfulResponseMessage() throws Exception {
291         setupSession();
292
293         String messageID1 = UUID.randomUUID().toString();
294         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1 );
295
296         String messageID2 = UUID.randomUUID().toString();
297         ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2 );
298
299         communicator.onMessage( mockSession, createSuccessResponseMessage( messageID1 ) );
300         communicator.onMessage( mockSession, createSuccessResponseMessage( messageID2 ) );
301
302         verifyResponseMessage( resultFuture1.get(), messageID1 );
303         verifyResponseMessage( resultFuture2.get(), messageID2 );
304     }
305
306     @Test
307     public void testOnResponseMessageWithError() throws Exception {
308         setupSession();
309
310         String messageID = UUID.randomUUID().toString();
311         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
312
313         communicator.onMessage( mockSession, createErrorResponseMessage( messageID ) );
314
315         RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.RPC,
316                                                   "missing-attribute" );
317         assertEquals( "RpcError message", "Missing attribute", rpcError.getMessage() );
318
319         String errorInfo = rpcError.getInfo();
320         assertNotNull( "RpcError info is null", errorInfo );
321         assertEquals( "Error info contains \"foo\"", true,
322                       errorInfo.contains( "<bad-attribute>foo</bad-attribute>" ) );
323         assertEquals( "Error info contains \"bar\"", true,
324                       errorInfo.contains( "<bad-element>bar</bad-element>" ) );
325     }
326
327     @Test
328     public void testOnResponseMessageWithWrongMessageID() throws Exception {
329         setupSession();
330
331         String messageID = UUID.randomUUID().toString();
332         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
333
334         communicator.onMessage( mockSession, createSuccessResponseMessage( UUID.randomUUID().toString() ) );
335
336         RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.PROTOCOL,
337                                                   "bad-attribute" );
338         assertEquals( "RpcError message non-empty", true,
339                       !Strings.isNullOrEmpty( rpcError.getMessage() ) );
340
341         String errorInfo = rpcError.getInfo();
342         assertNotNull( "RpcError info is null", errorInfo );
343         assertEquals( "Error info contains \"actual-message-id\"", true,
344                       errorInfo.contains( "actual-message-id" ) );
345         assertEquals( "Error info contains \"expected-message-id\"", true,
346                       errorInfo.contains( "expected-message-id" ) );
347     }
348
349     private NetconfMessage createErrorResponseMessage( String messageID ) throws Exception {
350         String xmlStr =
351             "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" +
352             "           message-id=\"" + messageID + "\">" +
353             "  <rpc-error>" +
354             "    <error-type>rpc</error-type>" +
355             "    <error-tag>missing-attribute</error-tag>" +
356             "    <error-severity>error</error-severity>" +
357             "    <error-message>Missing attribute</error-message>" +
358             "    <error-info>" +
359             "      <bad-attribute>foo</bad-attribute>" +
360             "      <bad-element>bar</bad-element>" +
361             "    </error-info>" +
362             "  </rpc-error>" +
363             "</rpc-reply>";
364
365         ByteArrayInputStream bis = new ByteArrayInputStream( xmlStr.getBytes() );
366         Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( bis );
367         return new NetconfMessage( doc );
368     }
369
370     private void verifyResponseMessage( RpcResult<NetconfMessage> rpcResult, String dataText ) {
371         assertNotNull( "RpcResult is null", rpcResult );
372         assertEquals( "isSuccessful", true, rpcResult.isSuccessful() );
373         NetconfMessage messageResult = rpcResult.getResult();
374         assertNotNull( "getResult", messageResult );
375 //        List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
376 //                                         QName.create( URI.create( "ns" ), null, "data" ) );
377 //        assertNotNull( "getSimpleNodesByName", nodes );
378 //        assertEquals( "List<SimpleNode<?>> size", 1, nodes.size() );
379 //        assertEquals( "SimpleNode value", dataText, nodes.iterator().next().getValue() );
380     }
381
382     private RpcError verifyErrorRpcResult( RpcResult<NetconfMessage> rpcResult,
383                                            RpcError.ErrorType expErrorType, String expErrorTag ) {
384         assertNotNull( "RpcResult is null", rpcResult );
385         assertEquals( "isSuccessful", false, rpcResult.isSuccessful() );
386         assertNotNull( "RpcResult errors is null", rpcResult.getErrors() );
387         assertEquals( "Errors size", 1, rpcResult.getErrors().size() );
388         RpcError rpcError = rpcResult.getErrors().iterator().next();
389         assertEquals( "getErrorSeverity", RpcError.ErrorSeverity.ERROR, rpcError.getSeverity() );
390         assertEquals( "getErrorType", expErrorType, rpcError.getErrorType() );
391         assertEquals( "getErrorTag", expErrorTag, rpcError.getTag() );
392         assertTrue( "getMessage is empty", StringUtils.isNotEmpty( rpcError.getMessage() ) );
393         return rpcError;
394     }
395 }