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.spy;
23 import static org.mockito.Mockito.timeout;
24 import static org.mockito.Mockito.verify;
25 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY;
26 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
28 import com.google.common.base.Strings;
29 import com.google.common.collect.Sets;
30 import com.google.common.util.concurrent.ListenableFuture;
31 import io.netty.channel.ChannelFuture;
32 import io.netty.channel.EventLoopGroup;
33 import io.netty.channel.nio.NioEventLoopGroup;
34 import io.netty.util.HashedWheelTimer;
35 import io.netty.util.Timer;
36 import io.netty.util.concurrent.Future;
37 import io.netty.util.concurrent.GenericFutureListener;
38 import io.netty.util.concurrent.GlobalEventExecutor;
39 import java.io.ByteArrayInputStream;
40 import java.net.InetSocketAddress;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.UUID;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.TimeoutException;
46 import javax.xml.parsers.DocumentBuilderFactory;
47 import javax.xml.parsers.ParserConfigurationException;
48 import org.apache.commons.lang3.StringUtils;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.mockito.ArgumentCaptor;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 import org.opendaylight.controller.netconf.api.NetconfMessage;
55 import org.opendaylight.controller.netconf.api.NetconfTerminationReason;
56 import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl;
57 import org.opendaylight.controller.netconf.client.NetconfClientSession;
58 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
59 import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
60 import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
61 import org.opendaylight.controller.sal.connect.api.RemoteDevice;
62 import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator;
63 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
64 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
65 import org.opendaylight.protocol.framework.ReconnectStrategy;
66 import org.opendaylight.protocol.framework.ReconnectStrategyFactory;
67 import org.opendaylight.protocol.framework.TimedReconnectStrategy;
68 import org.opendaylight.yangtools.yang.common.QName;
69 import org.opendaylight.yangtools.yang.common.RpcError;
70 import org.opendaylight.yangtools.yang.common.RpcResult;
71 import org.w3c.dom.Document;
72 import org.w3c.dom.Element;
74 public class NetconfDeviceCommunicatorTest {
77 NetconfClientSession mockSession;
80 RemoteDevice<NetconfSessionCapabilities, NetconfMessage> mockDevice;
82 NetconfDeviceCommunicator communicator;
85 public void setUp() throws Exception {
86 MockitoAnnotations.initMocks( this );
88 communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice );
91 @SuppressWarnings("unchecked")
94 doReturn( Collections.<String>emptySet() ).when( mockSession ).getServerCapabilities();
95 doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ),
96 any( RemoteDeviceCommunicator.class ) );
97 communicator.onSessionUp( mockSession );
100 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
101 return sendRequest( UUID.randomUUID().toString() );
104 @SuppressWarnings("unchecked")
105 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest( String messageID ) throws Exception {
106 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
107 Element element = doc.createElement( "request" );
108 element.setAttribute( "message-id", messageID );
109 doc.appendChild( element );
110 NetconfMessage message = new NetconfMessage( doc );
112 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
113 doReturn( mockChannelFuture ).when( mockChannelFuture )
114 .addListener( any( (GenericFutureListener.class ) ) );
115 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
117 ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
118 communicator.sendRequest( message, QName.create( "mock rpc" ) );
120 assertNotNull( "ListenableFuture is null", resultFuture );
125 public void testOnSessionUp() {
126 String testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
127 Collection<String> serverCapabilities =
128 Sets.newHashSet( NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
129 NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
131 doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities();
133 ArgumentCaptor<NetconfSessionCapabilities> netconfSessionCapabilities =
134 ArgumentCaptor.forClass( NetconfSessionCapabilities.class );
135 doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
137 communicator.onSessionUp( mockSession );
139 verify( mockSession ).getServerCapabilities();
140 verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
142 NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue();
143 assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability(
144 NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) );
145 assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) );
146 assertEquals( "getModuleBasedCaps", Sets.newHashSet(
147 QName.create( "urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module" )),
148 actualCapabilites.getModuleBasedCaps() );
149 assertEquals( "isRollbackSupported", true, actualCapabilites.isRollbackSupported() );
150 assertEquals( "isMonitoringSupported", true, actualCapabilites.isMonitoringSupported() );
153 @SuppressWarnings("unchecked")
155 public void testOnSessionDown() throws Exception {
158 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
159 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
161 doNothing().when( mockDevice ).onRemoteSessionDown();
163 communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
165 verifyErrorRpcResult( resultFuture1.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
166 verifyErrorRpcResult( resultFuture2.get(), RpcError.ErrorType.TRANSPORT, "operation-failed" );
168 verify( mockDevice ).onRemoteSessionDown();
172 communicator.onSessionDown( mockSession, new Exception( "mock ex" ) );
174 verify( mockDevice, never() ).onRemoteSessionDown();
178 public void testOnSessionTerminated() throws Exception {
181 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
183 doNothing().when( mockDevice ).onRemoteSessionDown();
185 String reasonText = "testing terminate";
186 NetconfTerminationReason reason = new NetconfTerminationReason( reasonText );
187 communicator.onSessionTerminated( mockSession, reason );
189 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.TRANSPORT,
190 "operation-failed" );
191 assertEquals( "RpcError message", reasonText, rpcError.getMessage() );
193 verify( mockDevice ).onRemoteSessionDown();
197 public void testClose() throws Exception {
198 communicator.close();
199 verify( mockDevice, never() ).onRemoteSessionDown();
202 @SuppressWarnings({ "rawtypes", "unchecked" })
204 public void testSendRequest() throws Exception {
207 NetconfMessage message = new NetconfMessage(
208 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
209 QName rpc = QName.create( "mock rpc" );
211 ArgumentCaptor<GenericFutureListener> futureListener =
212 ArgumentCaptor.forClass( GenericFutureListener.class );
214 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
215 doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
216 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
218 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
220 verify( mockSession ).sendMessage( same( message ) );
222 assertNotNull( "ListenableFuture is null", resultFuture );
224 verify( mockChannelFuture ).addListener( futureListener.capture() );
225 Future<Void> operationFuture = mock( Future.class );
226 doReturn( true ).when( operationFuture ).isSuccess();
227 doReturn( true ).when( operationFuture ).isDone();
228 futureListener.getValue().operationComplete( operationFuture );
231 resultFuture.get( 1, TimeUnit.MILLISECONDS ); // verify it's not cancelled or has an error set
233 catch( TimeoutException e ) {} // expected
237 public void testSendRequestWithNoSession() throws Exception {
238 NetconfMessage message = new NetconfMessage(
239 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
240 QName rpc = QName.create( "mock rpc" );
242 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
244 assertNotNull( "ListenableFuture is null", resultFuture );
246 // Should have an immediate result
247 RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
249 verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
252 @SuppressWarnings({ "rawtypes", "unchecked" })
254 public void testSendRequestWithWithSendFailure() throws Exception {
257 NetconfMessage message = new NetconfMessage(
258 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );
259 QName rpc = QName.create( "mock rpc" );
261 ArgumentCaptor<GenericFutureListener> futureListener =
262 ArgumentCaptor.forClass( GenericFutureListener.class );
264 ChannelFuture mockChannelFuture = mock( ChannelFuture.class );
265 doReturn( mockChannelFuture ).when( mockChannelFuture ).addListener( futureListener.capture() );
266 doReturn( mockChannelFuture ).when( mockSession ).sendMessage( same( message ) );
268 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest( message, rpc );
270 assertNotNull( "ListenableFuture is null", resultFuture );
272 verify( mockChannelFuture ).addListener( futureListener.capture() );
274 Future<Void> operationFuture = mock( Future.class );
275 doReturn( false ).when( operationFuture ).isSuccess();
276 doReturn( true ).when( operationFuture ).isDone();
277 doReturn( new Exception( "mock error" ) ).when( operationFuture ).cause();
278 futureListener.getValue().operationComplete( operationFuture );
280 // Should have an immediate result
281 RpcResult<NetconfMessage> rpcResult = resultFuture.get( 3, TimeUnit.MILLISECONDS );
283 RpcError rpcError = verifyErrorRpcResult( rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed" );
284 assertEquals( "RpcError message contains \"mock error\"", true,
285 rpcError.getMessage().contains( "mock error" ) );
288 private NetconfMessage createSuccessResponseMessage( String messageID ) throws ParserConfigurationException {
289 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
290 Element rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY );
291 rpcReply.setAttribute( "message-id", messageID );
292 Element element = doc.createElementNS( "ns", "data" );
293 element.setTextContent( messageID );
294 rpcReply.appendChild( element );
295 doc.appendChild( rpcReply );
297 return new NetconfMessage( doc );
301 public void testOnSuccessfulResponseMessage() throws Exception {
304 String messageID1 = UUID.randomUUID().toString();
305 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1 );
307 String messageID2 = UUID.randomUUID().toString();
308 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2 );
310 communicator.onMessage( mockSession, createSuccessResponseMessage( messageID1 ) );
311 communicator.onMessage( mockSession, createSuccessResponseMessage( messageID2 ) );
313 verifyResponseMessage( resultFuture1.get(), messageID1 );
314 verifyResponseMessage( resultFuture2.get(), messageID2 );
318 public void testOnResponseMessageWithError() throws Exception {
321 String messageID = UUID.randomUUID().toString();
322 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
324 communicator.onMessage( mockSession, createErrorResponseMessage( messageID ) );
326 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.RPC,
327 "missing-attribute" );
328 assertEquals( "RpcError message", "Missing attribute", rpcError.getMessage() );
330 String errorInfo = rpcError.getInfo();
331 assertNotNull( "RpcError info is null", errorInfo );
332 assertEquals( "Error info contains \"foo\"", true,
333 errorInfo.contains( "<bad-attribute>foo</bad-attribute>" ) );
334 assertEquals( "Error info contains \"bar\"", true,
335 errorInfo.contains( "<bad-element>bar</bad-element>" ) );
339 * Test whether reconnect is scheduled properly
342 public void testNetconfDeviceReconnectInCommunicator() throws Exception {
343 final RemoteDevice<NetconfSessionCapabilities, NetconfMessage> device = mock(RemoteDevice.class);
345 final TimedReconnectStrategy timedReconnectStrategy = new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, 10000, 0, 1.0, null, 100L, null);
346 final ReconnectStrategy reconnectStrategy = spy(new ReconnectStrategy() {
348 public int getConnectTimeout() throws Exception {
349 return timedReconnectStrategy.getConnectTimeout();
353 public Future<Void> scheduleReconnect(final Throwable cause) {
354 return timedReconnectStrategy.scheduleReconnect(cause);
358 public void reconnectSuccessful() {
359 timedReconnectStrategy.reconnectSuccessful();
363 final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test"), device);
364 final EventLoopGroup group = new NioEventLoopGroup();
365 final Timer time = new HashedWheelTimer();
367 final NetconfClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create()
368 .withAddress(new InetSocketAddress("localhost", 65000))
369 .withReconnectStrategy(reconnectStrategy)
370 .withConnectStrategyFactory(new ReconnectStrategyFactory() {
372 public ReconnectStrategy createReconnectStrategy() {
373 return reconnectStrategy;
376 .withAuthHandler(new LoginPassword("admin", "admin"))
377 .withConnectionTimeoutMillis(10000)
378 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
379 .withSessionListener(listener)
383 listener.initializeRemoteConnection(new NetconfClientDispatcherImpl(group, group, time), cfg);
385 verify(reconnectStrategy, timeout((int) TimeUnit.MINUTES.toMillis(3)).times(101)).scheduleReconnect(any(Throwable.class));
388 group.shutdownGracefully();
393 public void testOnResponseMessageWithWrongMessageID() throws Exception {
396 String messageID = UUID.randomUUID().toString();
397 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
399 communicator.onMessage( mockSession, createSuccessResponseMessage( UUID.randomUUID().toString() ) );
401 RpcError rpcError = verifyErrorRpcResult( resultFuture.get(), RpcError.ErrorType.PROTOCOL,
403 assertEquals( "RpcError message non-empty", true,
404 !Strings.isNullOrEmpty( rpcError.getMessage() ) );
406 String errorInfo = rpcError.getInfo();
407 assertNotNull( "RpcError info is null", errorInfo );
408 assertEquals( "Error info contains \"actual-message-id\"", true,
409 errorInfo.contains( "actual-message-id" ) );
410 assertEquals( "Error info contains \"expected-message-id\"", true,
411 errorInfo.contains( "expected-message-id" ) );
414 private NetconfMessage createErrorResponseMessage( String messageID ) throws Exception {
416 "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" +
417 " message-id=\"" + messageID + "\">" +
419 " <error-type>rpc</error-type>" +
420 " <error-tag>missing-attribute</error-tag>" +
421 " <error-severity>error</error-severity>" +
422 " <error-message>Missing attribute</error-message>" +
424 " <bad-attribute>foo</bad-attribute>" +
425 " <bad-element>bar</bad-element>" +
430 ByteArrayInputStream bis = new ByteArrayInputStream( xmlStr.getBytes() );
431 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( bis );
432 return new NetconfMessage( doc );
435 private void verifyResponseMessage( RpcResult<NetconfMessage> rpcResult, String dataText ) {
436 assertNotNull( "RpcResult is null", rpcResult );
437 assertEquals( "isSuccessful", true, rpcResult.isSuccessful() );
438 NetconfMessage messageResult = rpcResult.getResult();
439 assertNotNull( "getResult", messageResult );
440 // List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
441 // QName.create( URI.create( "ns" ), null, "data" ) );
442 // assertNotNull( "getSimpleNodesByName", nodes );
443 // assertEquals( "List<SimpleNode<?>> size", 1, nodes.size() );
444 // assertEquals( "SimpleNode value", dataText, nodes.iterator().next().getValue() );
447 private RpcError verifyErrorRpcResult( RpcResult<NetconfMessage> rpcResult,
448 RpcError.ErrorType expErrorType, String expErrorTag ) {
449 assertNotNull( "RpcResult is null", rpcResult );
450 assertEquals( "isSuccessful", false, rpcResult.isSuccessful() );
451 assertNotNull( "RpcResult errors is null", rpcResult.getErrors() );
452 assertEquals( "Errors size", 1, rpcResult.getErrors().size() );
453 RpcError rpcError = rpcResult.getErrors().iterator().next();
454 assertEquals( "getErrorSeverity", RpcError.ErrorSeverity.ERROR, rpcError.getSeverity() );
455 assertEquals( "getErrorType", expErrorType, rpcError.getErrorType() );
456 assertEquals( "getErrorTag", expErrorTag, rpcError.getTag() );
457 assertTrue( "getMessage is empty", StringUtils.isNotEmpty( rpcError.getMessage() ) );