NetconfSessionPreferences is a record
[netconf.git] / netconf / sal-netconf-connector / src / test / java / org / opendaylight / netconf / sal / connect / netconf / listener / NetconfDeviceCommunicatorTest.java
index f9724cf74ee12e2e48e85880e273401e75ef2505..ac68f007d2dd73735cfd4f5c5f418aa98e417d7a 100644 (file)
@@ -5,15 +5,15 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.netconf.sal.connect.netconf.listener;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -26,7 +26,6 @@ import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARA
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.ListenableFuture;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.EventLoopGroup;
@@ -39,32 +38,36 @@ import io.netty.util.concurrent.GlobalEventExecutor;
 import java.io.ByteArrayInputStream;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import javax.xml.parsers.ParserConfigurationException;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.opendaylight.controller.config.util.xml.XmlMappingConstants;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.netconf.api.NetconfMessage;
 import org.opendaylight.netconf.api.NetconfTerminationReason;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
 import org.opendaylight.netconf.client.NetconfClientDispatcherImpl;
 import org.opendaylight.netconf.client.NetconfClientSession;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
+import org.opendaylight.netconf.nettyutil.ReconnectStrategy;
+import org.opendaylight.netconf.nettyutil.TimedReconnectStrategy;
 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.protocol.framework.ReconnectStrategy;
-import org.opendaylight.protocol.framework.TimedReconnectStrategy;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
@@ -73,6 +76,7 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class NetconfDeviceCommunicatorTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceCommunicatorTest.class);
@@ -81,14 +85,12 @@ public class NetconfDeviceCommunicatorTest {
     NetconfClientSession mockSession;
 
     @Mock
-    RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> mockDevice;
+    RemoteDevice<NetconfDeviceCommunicator> mockDevice;
 
     NetconfDeviceCommunicator communicator;
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
         communicator = new NetconfDeviceCommunicator(
                 new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice, 10);
     }
@@ -115,11 +117,11 @@ public class NetconfDeviceCommunicatorTest {
 
         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
         doReturn(mockChannelFuture).when(mockChannelFuture)
-                .addListener(any((GenericFutureListener.class)));
+                .addListener(any(GenericFutureListener.class));
         doReturn(mockChannelFuture).when(mockSession).sendMessage(same(message));
 
         ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
-                communicator.sendRequest(message, QName.create("mock rpc"));
+                communicator.sendRequest(message, QName.create("", "mockRpc"));
         if (doLastTest) {
             assertNotNull("ListenableFuture is null", resultFuture);
         }
@@ -128,15 +130,14 @@ public class NetconfDeviceCommunicatorTest {
 
     @Test
     public void testOnSessionUp() {
-        String testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
-        Collection<String> serverCapabilities =
-                Sets.newHashSet(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
-                        NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
-                        testCapability);
+        final var testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
+        final var serverCapabilities = Set.of(
+            NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
+            NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
+            testCapability);
         doReturn(serverCapabilities).when(mockSession).getServerCapabilities();
 
-        ArgumentCaptor<NetconfSessionPreferences> netconfSessionPreferences =
-                ArgumentCaptor.forClass(NetconfSessionPreferences.class);
+        final var netconfSessionPreferences = ArgumentCaptor.forClass(NetconfSessionPreferences.class);
         doNothing().when(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
 
         communicator.onSessionUp(mockSession);
@@ -145,14 +146,13 @@ public class NetconfDeviceCommunicatorTest {
         verify(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
 
         NetconfSessionPreferences actualCapabilites = netconfSessionPreferences.getValue();
-        assertEquals("containsModuleCapability", true, actualCapabilites.containsNonModuleCapability(
+        assertTrue(actualCapabilites.containsNonModuleCapability(
                 NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()));
-        assertEquals("containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability));
-        assertEquals("getModuleBasedCaps", Sets.newHashSet(
-                QName.create("urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module")),
-                actualCapabilites.getModuleBasedCaps());
-        assertEquals("isRollbackSupported", true, actualCapabilites.isRollbackSupported());
-        assertEquals("isMonitoringSupported", true, actualCapabilites.isMonitoringSupported());
+        assertFalse(actualCapabilites.containsNonModuleCapability(testCapability));
+        assertEquals(Set.of(QName.create("urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module")),
+                actualCapabilites.moduleBasedCaps().keySet());
+        assertTrue(actualCapabilites.isRollbackSupported());
+        assertTrue(actualCapabilites.isMonitoringSupported());
     }
 
     @SuppressWarnings("unchecked")
@@ -167,8 +167,8 @@ public class NetconfDeviceCommunicatorTest {
 
         communicator.onSessionDown(mockSession, new Exception("mock ex"));
 
-        verifyErrorRpcResult(resultFuture1.get(), RpcError.ErrorType.TRANSPORT, "operation-failed");
-        verifyErrorRpcResult(resultFuture2.get(), RpcError.ErrorType.TRANSPORT, "operation-failed");
+        verifyErrorRpcResult(resultFuture1.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
+        verifyErrorRpcResult(resultFuture2.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
 
         verify(mockDevice).onRemoteSessionDown();
 
@@ -191,8 +191,7 @@ public class NetconfDeviceCommunicatorTest {
         NetconfTerminationReason reason = new NetconfTerminationReason(reasonText);
         communicator.onSessionTerminated(mockSession, reason);
 
-        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), RpcError.ErrorType.TRANSPORT,
-                "operation-failed");
+        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
         assertEquals("RpcError message", reasonText, rpcError.getMessage());
 
         verify(mockDevice).onRemoteSessionDown();
@@ -210,7 +209,7 @@ public class NetconfDeviceCommunicatorTest {
         setupSession();
 
         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
-        QName rpc = QName.create("mock rpc");
+        QName rpc = QName.create("", "mockRpc");
 
         ArgumentCaptor<GenericFutureListener> futureListener =
                 ArgumentCaptor.forClass(GenericFutureListener.class);
@@ -228,7 +227,6 @@ public class NetconfDeviceCommunicatorTest {
         verify(mockChannelFuture).addListener(futureListener.capture());
         Future<Void> operationFuture = mock(Future.class);
         doReturn(true).when(operationFuture).isSuccess();
-        doReturn(true).when(operationFuture).isDone();
         futureListener.getValue().operationComplete(operationFuture);
 
         try {
@@ -241,7 +239,7 @@ public class NetconfDeviceCommunicatorTest {
     @Test
     public void testSendRequestWithNoSession() throws Exception {
         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
-        QName rpc = QName.create("mock rpc");
+        QName rpc = QName.create("", "mockRpc");
 
         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
 
@@ -250,14 +248,14 @@ public class NetconfDeviceCommunicatorTest {
         // Should have an immediate result
         RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
 
-        verifyErrorRpcResult(rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed");
+        verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
     }
 
     private static NetconfMessage createSuccessResponseMessage(final String messageID)
             throws ParserConfigurationException {
         Document doc = UntrustedXML.newDocumentBuilder().newDocument();
         Element rpcReply =
-                doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlMappingConstants.RPC_REPLY_KEY);
+                doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlNetconfConstants.RPC_REPLY_KEY);
         rpcReply.setAttribute("message-id", messageID);
         Element element = doc.createElementNS("ns", "data");
         element.setTextContent(messageID);
@@ -273,7 +271,7 @@ public class NetconfDeviceCommunicatorTest {
         setupSession();
 
         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
-        QName rpc = QName.create("mock rpc");
+        QName rpc = QName.create("", "mockRpc");
 
         ArgumentCaptor<GenericFutureListener> futureListener =
                 ArgumentCaptor.forClass(GenericFutureListener.class);
@@ -290,14 +288,13 @@ public class NetconfDeviceCommunicatorTest {
 
         Future<Void> operationFuture = mock(Future.class);
         doReturn(false).when(operationFuture).isSuccess();
-        doReturn(true).when(operationFuture).isDone();
         doReturn(new Exception("mock error")).when(operationFuture).cause();
         futureListener.getValue().operationComplete(operationFuture);
 
         // Should have an immediate result
         RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
 
-        RpcError rpcError = verifyErrorRpcResult(rpcResult, RpcError.ErrorType.TRANSPORT, "operation-failed");
+        RpcError rpcError = verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
         assertEquals("RpcError message contains \"mock error\"", true,
                 rpcError.getMessage().contains("mock error"));
     }
@@ -349,40 +346,61 @@ public class NetconfDeviceCommunicatorTest {
 
         communicator.onMessage(mockSession, createErrorResponseMessage(messageID));
 
-        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), RpcError.ErrorType.RPC,
-                "missing-attribute");
+        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE);
         assertEquals("RpcError message", "Missing attribute", rpcError.getMessage());
 
         String errorInfo = rpcError.getInfo();
         assertNotNull("RpcError info is null", errorInfo);
-        assertEquals("Error info contains \"foo\"", true,
-                errorInfo.contains("<bad-attribute>foo</bad-attribute>"));
-        assertEquals("Error info contains \"bar\"", true,
-                errorInfo.contains("<bad-element>bar</bad-element>"));
+        assertTrue("Error info contains \"foo\"", errorInfo.contains("<bad-attribute>foo</bad-attribute>"));
+        assertTrue("Error info contains \"bar\"", errorInfo.contains("<bad-element>bar</bad-element>"));
+    }
+
+    @Test
+    public void testOnResponseMessageWithMultipleErrors() throws Exception {
+        setupSession();
+
+        String messageID = UUID.randomUUID().toString();
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
+
+        communicator.onMessage(mockSession, createMultiErrorResponseMessage(messageID));
+
+        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED);
+
+        String errorInfo = rpcError.getInfo();
+        assertNotNull("RpcError info is null", errorInfo);
+
+        String errorInfoMessages = rpcError.getInfo();
+        String errMsg1 = "Number of member links configured, i.e [1], "
+                + "for interface [ae0]is lesser than the required minimum [2].";
+        String errMsg2 = "configuration check-out failed";
+        assertTrue(String.format("Error info contains \"%s\" or \"%s\'", errMsg1, errMsg2),
+                errorInfoMessages.contains(errMsg1) && errorInfoMessages.contains(errMsg2));
     }
 
     /**
      * Test whether reconnect is scheduled properly.
      */
     @Test
-    public void testNetconfDeviceReconnectInCommunicator() throws Exception {
-        final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device =
-                mock(RemoteDevice.class);
+    public void testNetconfDeviceReconnectInCommunicator() {
+        final RemoteDevice<NetconfDeviceCommunicator> device = mock(RemoteDevice.class);
 
         final TimedReconnectStrategy timedReconnectStrategy =
                 new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, 10000, 0, 1.0, null, 100L, null);
         final ReconnectStrategy reconnectStrategy = spy(new ReconnectStrategy() {
             @Override
+            @Deprecated
             public int getConnectTimeout() throws Exception {
                 return timedReconnectStrategy.getConnectTimeout();
             }
 
             @Override
+            @Deprecated
             public Future<Void> scheduleReconnect(final Throwable cause) {
                 return timedReconnectStrategy.scheduleReconnect(cause);
             }
 
             @Override
+            @Deprecated
             public void reconnectSuccessful() {
                 timedReconnectStrategy.reconnectSuccessful();
             }
@@ -406,7 +424,7 @@ public class NetconfDeviceCommunicatorTest {
             listener.initializeRemoteConnection(new NetconfClientDispatcherImpl(group, group, time), cfg);
 
             verify(reconnectStrategy,
-                    timeout((int) TimeUnit.MINUTES.toMillis(3)).times(101)).scheduleReconnect(any(Throwable.class));
+                    timeout(TimeUnit.MINUTES.toMillis(4)).times(101)).scheduleReconnect(any(Throwable.class));
         } finally {
             time.stop();
             group.shutdownGracefully();
@@ -422,17 +440,13 @@ public class NetconfDeviceCommunicatorTest {
 
         communicator.onMessage(mockSession, createSuccessResponseMessage(UUID.randomUUID().toString()));
 
-        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), RpcError.ErrorType.PROTOCOL,
-                "bad-attribute");
-        assertEquals("RpcError message non-empty", true,
-                !Strings.isNullOrEmpty(rpcError.getMessage()));
+        RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
+        assertFalse("RpcError message non-empty", Strings.isNullOrEmpty(rpcError.getMessage()));
 
         String errorInfo = rpcError.getInfo();
         assertNotNull("RpcError info is null", errorInfo);
-        assertEquals("Error info contains \"actual-message-id\"", true,
-                errorInfo.contains("actual-message-id"));
-        assertEquals("Error info contains \"expected-message-id\"", true,
-                errorInfo.contains("expected-message-id"));
+        assertTrue("Error info contains \"actual-message-id\"", errorInfo.contains("actual-message-id"));
+        assertTrue("Error info contains \"expected-message-id\"", errorInfo.contains("expected-message-id"));
     }
 
     @Test
@@ -456,6 +470,37 @@ public class NetconfDeviceCommunicatorTest {
         assertNotNull("ListenableFuture is null", resultFuture);
     }
 
+    private static NetconfMessage createMultiErrorResponseMessage(final String messageID) throws Exception {
+        // multiple rpc-errors which simulate actual response like in NETCONF-666
+        String xmlStr = "<nc:rpc-reply xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xmlns:junos=\"http://xml.juniper.net/junos/18.4R1/junos\""
+                + "           message-id=\"" + messageID + "\">"
+                + "<nc:rpc-error>\n"
+                + "<nc:error-type>protocol</nc:error-type>\n"
+                + "<nc:error-tag>operation-failed</nc:error-tag>\n"
+                + "<nc:error-severity>error</nc:error-severity>\n"
+                + "<source-daemon>\n"
+                + "dcd\n"
+                + "</source-daemon>\n"
+                + "<nc:error-message>\n"
+                + "Number of member links configured, i.e [1], "
+                + "for interface [ae0]is lesser than the required minimum [2].\n"
+                + "</nc:error-message>\n"
+                + "</nc:rpc-error>\n"
+                + "<nc:rpc-error>\n"
+                + "<nc:error-type>protocol</nc:error-type>\n"
+                + "<nc:error-tag>operation-failed</nc:error-tag>\n"
+                + "<nc:error-severity>error</nc:error-severity>\n"
+                + "<nc:error-message>\n"
+                + "configuration check-out failed\n"
+                + "</nc:error-message>\n"
+                + "</nc:rpc-error>\n"
+                + "</nc:rpc-reply>";
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
+        Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
+        return new NetconfMessage(doc);
+    }
+
     private static NetconfMessage createErrorResponseMessage(final String messageID) throws Exception {
         String xmlStr = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\""
                 + "           message-id=\"" + messageID + "\">"
@@ -478,7 +523,7 @@ public class NetconfDeviceCommunicatorTest {
 
     private static void verifyResponseMessage(final RpcResult<NetconfMessage> rpcResult, final String dataText) {
         assertNotNull("RpcResult is null", rpcResult);
-        assertEquals("isSuccessful", true, rpcResult.isSuccessful());
+        assertTrue("isSuccessful", rpcResult.isSuccessful());
         NetconfMessage messageResult = rpcResult.getResult();
         assertNotNull("getResult", messageResult);
 //        List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
@@ -489,20 +534,20 @@ public class NetconfDeviceCommunicatorTest {
     }
 
     private static RpcError verifyErrorRpcResult(final RpcResult<NetconfMessage> rpcResult,
-                                                 final RpcError.ErrorType expErrorType, final String expErrorTag) {
+                                                 final ErrorType expErrorType, final ErrorTag expErrorTag) {
         assertNotNull("RpcResult is null", rpcResult);
-        assertEquals("isSuccessful", false, rpcResult.isSuccessful());
+        assertFalse("isSuccessful", rpcResult.isSuccessful());
         assertNotNull("RpcResult errors is null", rpcResult.getErrors());
         assertEquals("Errors size", 1, rpcResult.getErrors().size());
         RpcError rpcError = rpcResult.getErrors().iterator().next();
-        assertEquals("getErrorSeverity", RpcError.ErrorSeverity.ERROR, rpcError.getSeverity());
+        assertEquals("getErrorSeverity", ErrorSeverity.ERROR, rpcError.getSeverity());
         assertEquals("getErrorType", expErrorType, rpcError.getErrorType());
         assertEquals("getErrorTag", expErrorTag, rpcError.getTag());
 
         final String msg = rpcError.getMessage();
         assertNotNull("getMessage is null", msg);
         assertFalse("getMessage is empty", msg.isEmpty());
-        assertFalse("getMessage is blank", CharMatcher.WHITESPACE.matchesAllOf(msg));
+        assertFalse("getMessage is blank", CharMatcher.whitespace().matchesAllOf(msg));
         return rpcError;
     }
 }