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 io.netty.channel.ChannelFuture;
12 import io.netty.util.concurrent.Future;
13 import io.netty.util.concurrent.GenericFutureListener;
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;
22 import javax.xml.parsers.DocumentBuilderFactory;
23 import javax.xml.parsers.ParserConfigurationException;
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;
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 import com.google.common.base.Strings;
60 import com.google.common.collect.Sets;
61 import com.google.common.util.concurrent.ListenableFuture;
63 public class NetconfDeviceCommunicatorTest {
66 NetconfClientSession mockSession;
69 RemoteDevice<NetconfSessionCapabilities, NetconfMessage> mockDevice;
71 NetconfDeviceCommunicator communicator;
74 public void setUp() throws Exception {
75 MockitoAnnotations.initMocks( this );
77 communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice );
80 @SuppressWarnings("unchecked")
83 doReturn( Collections.<String>emptySet() ).when( mockSession ).getServerCapabilities();
84 doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ),
85 any( RemoteDeviceCommunicator.class ) );
86 communicator.onSessionUp( mockSession );
89 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
90 return sendRequest( UUID.randomUUID().toString() );
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 );
101 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
102 doReturn( mockChannelFuture ).when( mockChannelFuture )
103 .addListener( any( (GenericFutureListener.class ) ) );
104 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
106 ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
107 communicator.sendRequest( message, QName.create( "mock rpc" ) );
109 assertNotNull( "ListenableFuture is null", resultFuture );
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(),
120 doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities();
122 ArgumentCaptor<NetconfSessionCapabilities> netconfSessionCapabilities =
123 ArgumentCaptor.forClass( NetconfSessionCapabilities.class );
124 doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
126 communicator.onSessionUp( mockSession );
128 verify( mockSession ).getServerCapabilities();
129 verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
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() );
142 @SuppressWarnings("unchecked")
144 public void testOnSessionDown() throws Exception {
147 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
148 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
150 doNothing().when( mockDevice ).onRemoteSessionDown();
152 communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
154 verifyErrorRpcResult( resultFuture1.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
155 verifyErrorRpcResult( resultFuture2.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
157 verify( mockDevice ).onRemoteSessionDown();
161 communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
163 verify( mockDevice, never() ).onRemoteSessionDown();
167 public void testOnSessionTerminated() throws Exception {
170 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
172 doNothing().when( mockDevice ).onRemoteSessionDown();
174 String reasonText = "testing terminate";
175 NetconfTerminationReason reason = new NetconfTerminationReason( reasonText );
176 communicator.onSessionTerminated( mockSession, reason );
178 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.TRANSPORT,
179 "operation-failed" );
180 assertEquals( "RpcError message", reasonText, rpcError.getMessage() );
182 verify( mockDevice ).onRemoteSessionDown();
186 public void testClose() throws Exception {
187 communicator.close();
188 verify( mockDevice, never() ).onRemoteSessionDown();
191 @SuppressWarnings({ "rawtypes", "unchecked" })
193 public void testSendRequest() throws Exception {
196 NetconfMessage message = new NetconfMessage(
197 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
198 QName rpc = QName.create( "mock rpc" );
200 ArgumentCaptor<GenericFutureListener> futureListener =
201 ArgumentCaptor.forClass( GenericFutureListener.class );
203 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
204 doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
205 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
207 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
209 verify( mockSession ).sendMessage( same( message ) );
211 assertNotNull( "ListenableFuture is null", resultFuture );
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 );
220 resultFuture.get( 1, TimeUnit.MILLISECONDS ); // verify it's not cancelled or has an error set
222 catch( TimeoutException e ) {} // expected
226 public void testSendRequestWithNoSession() throws Exception {
227 NetconfMessage message = new NetconfMessage(
228 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
229 QName rpc = QName.create( "mock rpc" );
231 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
233 assertNotNull( "ListenableFuture is null", resultFuture );
235 // Should have an immediate result
236 RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
238 verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
241 @SuppressWarnings({ "rawtypes", "unchecked" })
243 public void testSendRequestWithWithSendFailure() throws Exception {
246 NetconfMessage message = new NetconfMessage(
247 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
248 QName rpc = QName.create( "mock rpc" );
250 ArgumentCaptor<GenericFutureListener> futureListener =
251 ArgumentCaptor.forClass( GenericFutureListener.class );
253 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
254 doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
255 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
257 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
259 assertNotNull( "ListenableFuture is null", resultFuture );
261 verify( mockChannelFuture ).addListener( futureListener.capture() );
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 );
269 // Should have an immediate result
270 RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
272 RpcError rpcError = verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
273 assertEquals( "RpcError message contains \"mock error\"", true,
274 rpcError.getMessage().contains( "mock error" ) );
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 );
286 return new NetconfMessage( doc );
290 public void testOnSuccessfulResponseMessage() throws Exception {
293 String messageID1 = UUID.randomUUID().toString();
294 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1 );
296 String messageID2 = UUID.randomUUID().toString();
297 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2 );
299 communicator.onMessage( mockSession, createSuccessResponseMessage( messageID1 ) );
300 communicator.onMessage( mockSession, createSuccessResponseMessage( messageID2 ) );
302 verifyResponseMessage( resultFuture1.get(), messageID1 );
303 verifyResponseMessage( resultFuture2.get(), messageID2 );
307 public void testOnResponseMessageWithError() throws Exception {
310 String messageID = UUID.randomUUID().toString();
311 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
313 communicator.onMessage( mockSession, createErrorResponseMessage( messageID ) );
315 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.RPC,
316 "missing-attribute" );
317 assertEquals( "RpcError message", "Missing attribute", rpcError.getMessage() );
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>" ) );
328 public void testOnResponseMessageWithWrongMessageID() throws Exception {
331 String messageID = UUID.randomUUID().toString();
332 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
334 communicator.onMessage( mockSession, createSuccessResponseMessage( UUID.randomUUID().toString() ) );
336 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.PROTOCOL,
338 assertEquals( "RpcError message non-empty", true,
339 !Strings.isNullOrEmpty( rpcError.getMessage() ) );
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" ) );
349 private NetconfMessage createErrorResponseMessage( String messageID ) throws Exception {
351 "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" +
352 " message-id=\"" + messageID + "\">" +
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>" +
359 " <bad-attribute>foo</bad-attribute>" +
360 " <bad-element>bar</bad-element>" +
365 ByteArrayInputStream bis = new ByteArrayInputStream( xmlStr.getBytes() );
366 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( bis );
367 return new NetconfMessage( doc );
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() );
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() ) );