2 * Copyright (c) 2014 Brocade Communications Systems, Inc. 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
9 package org.opendaylight.controller.sal.connect.netconf.listener;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertTrue;
14 import static org.mockito.Matchers.any;
15 import static org.mockito.Matchers.eq;
16 import static org.mockito.Matchers.same;
17 import static org.mockito.Mockito.doNothing;
18 import static org.mockito.Mockito.doReturn;
19 import static org.mockito.Mockito.mock;
20 import static org.mockito.Mockito.never;
21 import static org.mockito.Mockito.reset;
22 import static org.mockito.Mockito.verify;
23 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY;
24 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
26 import com.google.common.base.Strings;
27 import com.google.common.collect.Sets;
28 import com.google.common.util.concurrent.ListenableFuture;
29 import io.netty.channel.ChannelFuture;
30 import io.netty.util.concurrent.Future;
31 import io.netty.util.concurrent.GenericFutureListener;
32 import java.io.ByteArrayInputStream;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.UUID;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.TimeoutException;
38 import javax.xml.parsers.DocumentBuilderFactory;
39 import javax.xml.parsers.ParserConfigurationException;
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;
59 public class NetconfDeviceCommunicatorTest {
62 NetconfClientSession mockSession;
65 RemoteDevice<NetconfSessionCapabilities, NetconfMessage> mockDevice;
67 NetconfDeviceCommunicator communicator;
70 public void setUp() throws Exception {
71 MockitoAnnotations.initMocks( this );
73 communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice );
76 @SuppressWarnings("unchecked")
79 doReturn( Collections.<String>emptySet() ).when( mockSession ).getServerCapabilities();
80 doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ),
81 any( RemoteDeviceCommunicator.class ) );
82 communicator.onSessionUp( mockSession );
85 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
86 return sendRequest( UUID.randomUUID().toString() );
89 @SuppressWarnings("unchecked")
90 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest( String messageID ) throws Exception {
91 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
92 Element element = doc.createElement( "request" );
93 element.setAttribute( "message-id", messageID );
94 doc.appendChild( element );
95 NetconfMessage message = new NetconfMessage( doc );
97 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
98 doReturn( mockChannelFuture ).when( mockChannelFuture )
99 .addListener( any( (GenericFutureListener.class ) ) );
100 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
102 ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
103 communicator.sendRequest( message, QName.create( "mock rpc" ) );
105 assertNotNull( "ListenableFuture is null", resultFuture );
110 public void testOnSessionUp() {
111 String testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
112 Collection<String> serverCapabilities =
113 Sets.newHashSet( NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
114 NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
116 doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities();
118 ArgumentCaptor<NetconfSessionCapabilities> netconfSessionCapabilities =
119 ArgumentCaptor.forClass( NetconfSessionCapabilities.class );
120 doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
122 communicator.onSessionUp( mockSession );
124 verify( mockSession ).getServerCapabilities();
125 verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
127 NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue();
128 assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability(
129 NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) );
130 assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) );
131 assertEquals( "getModuleBasedCaps", Sets.newHashSet(
132 QName.create( "urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module" )),
133 actualCapabilites.getModuleBasedCaps() );
134 assertEquals( "isRollbackSupported", true, actualCapabilites.isRollbackSupported() );
135 assertEquals( "isMonitoringSupported", true, actualCapabilites.isMonitoringSupported() );
138 @SuppressWarnings("unchecked")
140 public void testOnSessionDown() throws Exception {
143 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
144 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
146 doNothing().when( mockDevice ).onRemoteSessionDown();
148 communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
150 verifyErrorRpcResult( resultFuture1.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
151 verifyErrorRpcResult( resultFuture2.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
153 verify( mockDevice ).onRemoteSessionDown();
157 communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
159 verify( mockDevice, never() ).onRemoteSessionDown();
163 public void testOnSessionTerminated() throws Exception {
166 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
168 doNothing().when( mockDevice ).onRemoteSessionDown();
170 String reasonText = "testing terminate";
171 NetconfTerminationReason reason = new NetconfTerminationReason( reasonText );
172 communicator.onSessionTerminated( mockSession, reason );
174 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.TRANSPORT,
175 "operation-failed" );
176 assertEquals( "RpcError message", reasonText, rpcError.getMessage() );
178 verify( mockDevice ).onRemoteSessionDown();
182 public void testClose() throws Exception {
183 communicator.close();
184 verify( mockDevice, never() ).onRemoteSessionDown();
187 @SuppressWarnings({ "rawtypes", "unchecked" })
189 public void testSendRequest() throws Exception {
192 NetconfMessage message = new NetconfMessage(
193 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
194 QName rpc = QName.create( "mock rpc" );
196 ArgumentCaptor<GenericFutureListener> futureListener =
197 ArgumentCaptor.forClass( GenericFutureListener.class );
199 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
200 doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
201 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
203 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
205 verify( mockSession ).sendMessage( same( message ) );
207 assertNotNull( "ListenableFuture is null", resultFuture );
209 verify( mockChannelFuture ).addListener( futureListener.capture() );
210 Future<Void> operationFuture = mock( Future.class );
211 doReturn( true ).when( operationFuture ).isSuccess();
212 doReturn( true ).when( operationFuture ).isDone();
213 futureListener.getValue().operationComplete( operationFuture );
216 resultFuture.get( 1, TimeUnit.MILLISECONDS ); // verify it's not cancelled or has an error set
218 catch( TimeoutException e ) {} // expected
222 public void testSendRequestWithNoSession() throws Exception {
223 NetconfMessage message = new NetconfMessage(
224 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
225 QName rpc = QName.create( "mock rpc" );
227 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
229 assertNotNull( "ListenableFuture is null", resultFuture );
231 // Should have an immediate result
232 RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
234 verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
237 @SuppressWarnings({ "rawtypes", "unchecked" })
239 public void testSendRequestWithWithSendFailure() throws Exception {
242 NetconfMessage message = new NetconfMessage(
243 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
244 QName rpc = QName.create( "mock rpc" );
246 ArgumentCaptor<GenericFutureListener> futureListener =
247 ArgumentCaptor.forClass( GenericFutureListener.class );
249 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
250 doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
251 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
253 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
255 assertNotNull( "ListenableFuture is null", resultFuture );
257 verify( mockChannelFuture ).addListener( futureListener.capture() );
259 Future<Void> operationFuture = mock( Future.class );
260 doReturn( false ).when( operationFuture ).isSuccess();
261 doReturn( true ).when( operationFuture ).isDone();
262 doReturn( new Exception( "mock error" ) ).when( operationFuture ).cause();
263 futureListener.getValue().operationComplete( operationFuture );
265 // Should have an immediate result
266 RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
268 RpcError rpcError = verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
269 assertEquals( "RpcError message contains \"mock error\"", true,
270 rpcError.getMessage().contains( "mock error" ) );
273 private NetconfMessage createSuccessResponseMessage( String messageID ) throws ParserConfigurationException {
274 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
275 Element rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY );
276 rpcReply.setAttribute( "message-id", messageID );
277 Element element = doc.createElementNS( "ns", "data" );
278 element.setTextContent( messageID );
279 rpcReply.appendChild( element );
280 doc.appendChild( rpcReply );
282 return new NetconfMessage( doc );
286 public void testOnSuccessfulResponseMessage() throws Exception {
289 String messageID1 = UUID.randomUUID().toString();
290 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1 );
292 String messageID2 = UUID.randomUUID().toString();
293 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2 );
295 communicator.onMessage( mockSession, createSuccessResponseMessage( messageID1 ) );
296 communicator.onMessage( mockSession, createSuccessResponseMessage( messageID2 ) );
298 verifyResponseMessage( resultFuture1.get(), messageID1 );
299 verifyResponseMessage( resultFuture2.get(), messageID2 );
303 public void testOnResponseMessageWithError() throws Exception {
306 String messageID = UUID.randomUUID().toString();
307 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
309 communicator.onMessage( mockSession, createErrorResponseMessage( messageID ) );
311 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.RPC,
312 "missing-attribute" );
313 assertEquals( "RpcError message", "Missing attribute", rpcError.getMessage() );
315 String errorInfo = rpcError.getInfo();
316 assertNotNull( "RpcError info is null", errorInfo );
317 assertEquals( "Error info contains \"foo\"", true,
318 errorInfo.contains( "<bad-attribute>foo</bad-attribute>" ) );
319 assertEquals( "Error info contains \"bar\"", true,
320 errorInfo.contains( "<bad-element>bar</bad-element>" ) );
324 public void testOnResponseMessageWithWrongMessageID() throws Exception {
327 String messageID = UUID.randomUUID().toString();
328 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
330 communicator.onMessage( mockSession, createSuccessResponseMessage( UUID.randomUUID().toString() ) );
332 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.PROTOCOL,
334 assertEquals( "RpcError message non-empty", true,
335 !Strings.isNullOrEmpty( rpcError.getMessage() ) );
337 String errorInfo = rpcError.getInfo();
338 assertNotNull( "RpcError info is null", errorInfo );
339 assertEquals( "Error info contains \"actual-message-id\"", true,
340 errorInfo.contains( "actual-message-id" ) );
341 assertEquals( "Error info contains \"expected-message-id\"", true,
342 errorInfo.contains( "expected-message-id" ) );
345 private NetconfMessage createErrorResponseMessage( String messageID ) throws Exception {
347 "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" +
348 " message-id=\"" + messageID + "\">" +
350 " <error-type>rpc</error-type>" +
351 " <error-tag>missing-attribute</error-tag>" +
352 " <error-severity>error</error-severity>" +
353 " <error-message>Missing attribute</error-message>" +
355 " <bad-attribute>foo</bad-attribute>" +
356 " <bad-element>bar</bad-element>" +
361 ByteArrayInputStream bis = new ByteArrayInputStream( xmlStr.getBytes() );
362 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( bis );
363 return new NetconfMessage( doc );
366 private void verifyResponseMessage( RpcResult<NetconfMessage> rpcResult, String dataText ) {
367 assertNotNull( "RpcResult is null", rpcResult );
368 assertEquals( "isSuccessful", true, rpcResult.isSuccessful() );
369 NetconfMessage messageResult = rpcResult.getResult();
370 assertNotNull( "getResult", messageResult );
371 // List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
372 // QName.create( URI.create( "ns" ), null, "data" ) );
373 // assertNotNull( "getSimpleNodesByName", nodes );
374 // assertEquals( "List<SimpleNode<?>> size", 1, nodes.size() );
375 // assertEquals( "SimpleNode value", dataText, nodes.iterator().next().getValue() );
378 private RpcError verifyErrorRpcResult( RpcResult<NetconfMessage> rpcResult,
379 RpcError.ErrorType expErrorType, String expErrorTag ) {
380 assertNotNull( "RpcResult is null", rpcResult );
381 assertEquals( "isSuccessful", false, rpcResult.isSuccessful() );
382 assertNotNull( "RpcResult errors is null", rpcResult.getErrors() );
383 assertEquals( "Errors size", 1, rpcResult.getErrors().size() );
384 RpcError rpcError = rpcResult.getErrors().iterator().next();
385 assertEquals( "getErrorSeverity", RpcError.ErrorSeverity.ERROR, rpcError.getSeverity() );
386 assertEquals( "getErrorType", expErrorType, rpcError.getErrorType() );
387 assertEquals( "getErrorTag", expErrorTag, rpcError.getTag() );
388 assertTrue( "getMessage is empty", StringUtils.isNotEmpty( rpcError.getMessage() ) );