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
8 package org.opendaylight.netconf.client.mdsal;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertTrue;
14 import static org.mockito.ArgumentMatchers.any;
15 import static org.mockito.ArgumentMatchers.eq;
16 import static org.mockito.ArgumentMatchers.same;
17 import static org.mockito.Mockito.CALLS_REAL_METHODS;
18 import static org.mockito.Mockito.doNothing;
19 import static org.mockito.Mockito.doReturn;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.never;
22 import static org.mockito.Mockito.reset;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.withSettings;
26 import com.google.common.base.CharMatcher;
27 import com.google.common.base.Strings;
28 import com.google.common.util.concurrent.ListenableFuture;
29 import io.netty.channel.Channel;
30 import io.netty.channel.ChannelFuture;
31 import io.netty.util.concurrent.Future;
32 import io.netty.util.concurrent.GenericFutureListener;
33 import java.io.ByteArrayInputStream;
34 import java.net.InetSocketAddress;
35 import java.util.ArrayList;
37 import java.util.UUID;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.TimeoutException;
40 import javax.xml.parsers.ParserConfigurationException;
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.mockito.ArgumentCaptor;
45 import org.mockito.Mock;
46 import org.mockito.MockMakers;
47 import org.mockito.junit.MockitoJUnitRunner;
48 import org.opendaylight.netconf.api.CapabilityURN;
49 import org.opendaylight.netconf.api.NamespaceURN;
50 import org.opendaylight.netconf.api.NetconfTerminationReason;
51 import org.opendaylight.netconf.api.messages.NetconfMessage;
52 import org.opendaylight.netconf.api.messages.RpcReplyMessage;
53 import org.opendaylight.netconf.client.NetconfClientSession;
54 import org.opendaylight.netconf.client.NetconfClientSessionListener;
55 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
56 import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
57 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
58 import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.SessionIdType;
60 import org.opendaylight.yangtools.util.xml.UntrustedXML;
61 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
62 import org.opendaylight.yangtools.yang.common.ErrorTag;
63 import org.opendaylight.yangtools.yang.common.ErrorType;
64 import org.opendaylight.yangtools.yang.common.QName;
65 import org.opendaylight.yangtools.yang.common.RpcError;
66 import org.opendaylight.yangtools.yang.common.RpcResult;
67 import org.opendaylight.yangtools.yang.common.Uint32;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70 import org.w3c.dom.Document;
71 import org.w3c.dom.Element;
73 @RunWith(MockitoJUnitRunner.StrictStubs.class)
74 public class NetconfDeviceCommunicatorTest {
75 private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceCommunicatorTest.class);
76 private static final SessionIdType SESSION_ID = new SessionIdType(Uint32.ONE);
79 RemoteDevice<NetconfDeviceCommunicator> mockDevice;
81 private NetconfClientSession spySession;
82 private NetconfDeviceCommunicator communicator;
85 public void setUp() throws Exception {
86 communicator = new NetconfDeviceCommunicator(
87 new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice, 10);
88 // FIXME: spy() except we override the MockMaker in use
89 spySession = mock(NetconfClientSession.class, withSettings()
90 .spiedInstance(new NetconfClientSession(mock(NetconfClientSessionListener.class), mock(Channel.class),
91 SESSION_ID, Set.of()))
92 .defaultAnswer(CALLS_REAL_METHODS)
93 .mockMaker(MockMakers.SUBCLASS));
97 doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class),
98 any(NetconfDeviceCommunicator.class));
99 communicator.onSessionUp(spySession);
102 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
103 return sendRequest(UUID.randomUUID().toString(), true);
106 @SuppressWarnings("unchecked")
107 private ListenableFuture<RpcResult<NetconfMessage>> sendRequest(final String messageID,
108 final boolean doLastTest) throws Exception {
109 Document doc = UntrustedXML.newDocumentBuilder().newDocument();
110 Element element = doc.createElement("request");
111 element.setAttribute("message-id", messageID);
112 doc.appendChild(element);
113 NetconfMessage message = new NetconfMessage(doc);
115 ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
116 doReturn(mockChannelFuture).when(mockChannelFuture)
117 .addListener(any(GenericFutureListener.class));
118 doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
120 ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
121 communicator.sendRequest(message, QName.create("", "mockRpc"));
123 assertNotNull("ListenableFuture is null", resultFuture);
129 public void testOnSessionUp() {
130 final var testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
131 final var serverCapabilities = Set.of(
132 CapabilityURN.ROLLBACK_ON_ERROR,
133 NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
135 doReturn(serverCapabilities).when(spySession).getServerCapabilities();
137 final var netconfSessionPreferences = ArgumentCaptor.forClass(NetconfSessionPreferences.class);
138 doNothing().when(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
140 communicator.onSessionUp(spySession);
142 verify(spySession).getServerCapabilities();
143 verify(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
145 NetconfSessionPreferences actualCapabilites = netconfSessionPreferences.getValue();
146 assertTrue(actualCapabilites.containsNonModuleCapability(
147 "urn:ietf:params:netconf:capability:rollback-on-error:1.0"));
148 assertFalse(actualCapabilites.containsNonModuleCapability(testCapability));
149 assertEquals(Set.of(QName.create("urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module")),
150 actualCapabilites.moduleBasedCaps().keySet());
151 assertTrue(actualCapabilites.isRollbackSupported());
152 assertTrue(actualCapabilites.isMonitoringSupported());
153 assertEquals(SESSION_ID, actualCapabilites.sessionId());
156 @SuppressWarnings("unchecked")
157 @Test(timeout = 5000)
158 public void testOnSessionDown() throws Exception {
161 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
162 final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
164 doNothing().when(mockDevice).onRemoteSessionDown();
166 communicator.onSessionDown(spySession, new Exception("mock ex"));
168 verifyErrorRpcResult(resultFuture1.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
169 verifyErrorRpcResult(resultFuture2.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
171 verify(mockDevice).onRemoteSessionDown();
175 communicator.onSessionDown(spySession, new Exception("mock ex"));
177 verify(mockDevice, never()).onRemoteSessionDown();
181 public void testOnSessionTerminated() throws Exception {
184 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
186 doNothing().when(mockDevice).onRemoteSessionDown();
188 String reasonText = "testing terminate";
189 NetconfTerminationReason reason = new NetconfTerminationReason(reasonText);
190 communicator.onSessionTerminated(spySession, reason);
192 RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
193 assertEquals("RpcError message", reasonText, rpcError.getMessage());
195 verify(mockDevice).onRemoteSessionDown();
199 public void testClose() throws Exception {
200 communicator.close();
201 verify(mockDevice, never()).onRemoteSessionDown();
204 @SuppressWarnings({"rawtypes", "unchecked"})
206 public void testSendRequest() throws Exception {
209 NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
210 QName rpc = QName.create("", "mockRpc");
212 ArgumentCaptor<GenericFutureListener> futureListener =
213 ArgumentCaptor.forClass(GenericFutureListener.class);
215 ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
216 doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
217 doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
219 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
221 verify(spySession).sendMessage(same(message));
223 assertNotNull("ListenableFuture is null", resultFuture);
225 verify(mockChannelFuture).addListener(futureListener.capture());
226 Future<Void> operationFuture = mock(Future.class);
227 doReturn(true).when(operationFuture).isSuccess();
228 futureListener.getValue().operationComplete(operationFuture);
231 resultFuture.get(1, TimeUnit.MILLISECONDS); // verify it's not cancelled or has an error set
232 } catch (TimeoutException e) {
233 LOG.info("Operation failed due timeout.");
238 public void testSendRequestWithNoSession() throws Exception {
239 NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
240 QName rpc = QName.create("", "mockRpc");
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, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
252 private static NetconfMessage createSuccessResponseMessage(final String messageID)
253 throws ParserConfigurationException {
254 Document doc = UntrustedXML.newDocumentBuilder().newDocument();
255 Element rpcReply = doc.createElementNS(NamespaceURN.BASE, RpcReplyMessage.ELEMENT_NAME);
256 rpcReply.setAttribute("message-id", messageID);
257 Element element = doc.createElementNS("ns", "data");
258 element.setTextContent(messageID);
259 rpcReply.appendChild(element);
260 doc.appendChild(rpcReply);
262 return new NetconfMessage(doc);
265 @SuppressWarnings({ "rawtypes", "unchecked" })
267 public void testSendRequestWithWithSendFailure() throws Exception {
270 NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
271 QName rpc = QName.create("", "mockRpc");
273 ArgumentCaptor<GenericFutureListener> futureListener =
274 ArgumentCaptor.forClass(GenericFutureListener.class);
276 ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
277 doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
278 doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
280 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
282 assertNotNull("ListenableFuture is null", resultFuture);
284 verify(mockChannelFuture).addListener(futureListener.capture());
286 Future<Void> operationFuture = mock(Future.class);
287 doReturn(false).when(operationFuture).isSuccess();
288 doReturn(new Exception("mock error")).when(operationFuture).cause();
289 futureListener.getValue().operationComplete(operationFuture);
291 // Should have an immediate result
292 RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
294 RpcError rpcError = verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
295 assertEquals("RpcError message contains \"mock error\"", true,
296 rpcError.getMessage().contains("mock error"));
299 //Test scenario verifying whether missing message is handled
301 public void testOnMissingResponseMessage() throws Exception {
305 String messageID1 = UUID.randomUUID().toString();
306 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
308 String messageID2 = UUID.randomUUID().toString();
309 ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
311 String messageID3 = UUID.randomUUID().toString();
312 ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest(messageID3, true);
314 //response messages 1,2 are omitted
315 communicator.onMessage(spySession, createSuccessResponseMessage(messageID3));
317 verifyResponseMessage(resultFuture3.get(), messageID3);
321 public void testOnSuccessfulResponseMessage() throws Exception {
324 String messageID1 = UUID.randomUUID().toString();
325 ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
327 String messageID2 = UUID.randomUUID().toString();
328 final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
330 communicator.onMessage(spySession, createSuccessResponseMessage(messageID1));
331 communicator.onMessage(spySession, createSuccessResponseMessage(messageID2));
333 verifyResponseMessage(resultFuture1.get(), messageID1);
334 verifyResponseMessage(resultFuture2.get(), messageID2);
338 public void testOnResponseMessageWithError() throws Exception {
341 String messageID = UUID.randomUUID().toString();
342 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
344 communicator.onMessage(spySession, createErrorResponseMessage(messageID));
346 RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE);
347 assertEquals("RpcError message", "Missing attribute", rpcError.getMessage());
349 String errorInfo = rpcError.getInfo();
350 assertNotNull("RpcError info is null", errorInfo);
351 assertTrue("Error info contains \"foo\"", errorInfo.contains("<bad-attribute>foo</bad-attribute>"));
352 assertTrue("Error info contains \"bar\"", errorInfo.contains("<bad-element>bar</bad-element>"));
356 public void testOnResponseMessageWithMultipleErrors() throws Exception {
359 String messageID = UUID.randomUUID().toString();
360 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
362 communicator.onMessage(spySession, createMultiErrorResponseMessage(messageID));
364 RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED);
366 String errorInfo = rpcError.getInfo();
367 assertNotNull("RpcError info is null", errorInfo);
369 String errorInfoMessages = rpcError.getInfo();
370 String errMsg1 = "Number of member links configured, i.e [1], "
371 + "for interface [ae0]is lesser than the required minimum [2].";
372 String errMsg2 = "configuration check-out failed";
373 assertTrue(String.format("Error info contains \"%s\" or \"%s\'", errMsg1, errMsg2),
374 errorInfoMessages.contains(errMsg1) && errorInfoMessages.contains(errMsg2));
378 public void testOnResponseMessageWithWrongMessageID() throws Exception {
381 String messageID = UUID.randomUUID().toString();
382 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
384 communicator.onMessage(spySession, createSuccessResponseMessage(UUID.randomUUID().toString()));
386 RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
387 assertFalse("RpcError message non-empty", Strings.isNullOrEmpty(rpcError.getMessage()));
389 String errorInfo = rpcError.getInfo();
390 assertNotNull("RpcError info is null", errorInfo);
391 assertTrue("Error info contains \"actual-message-id\"", errorInfo.contains("actual-message-id"));
392 assertTrue("Error info contains \"expected-message-id\"", errorInfo.contains("expected-message-id"));
396 public void testConcurrentMessageLimit() throws Exception {
398 ArrayList<String> messageID = new ArrayList<>();
400 for (int i = 0; i < 10; i++) {
401 messageID.add(UUID.randomUUID().toString());
402 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID.get(i), false);
403 assertEquals("ListenableFuture is null", true, resultFuture instanceof UncancellableFuture);
406 final String notWorkingMessageID = UUID.randomUUID().toString();
407 ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(notWorkingMessageID, false);
408 assertEquals("ListenableFuture is null", false, resultFuture instanceof UncancellableFuture);
410 communicator.onMessage(spySession, createSuccessResponseMessage(messageID.get(0)));
412 resultFuture = sendRequest(messageID.get(0), false);
413 assertNotNull("ListenableFuture is null", resultFuture);
416 private static NetconfMessage createMultiErrorResponseMessage(final String messageID) throws Exception {
417 // multiple rpc-errors which simulate actual response like in NETCONF-666
418 String xmlStr = "<nc:rpc-reply xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
419 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
420 + "xmlns:junos=\"http://xml.juniper.net/junos/18.4R1/junos\" message-id=\"" + messageID + "\">"
422 + "<nc:error-type>protocol</nc:error-type>\n"
423 + "<nc:error-tag>operation-failed</nc:error-tag>\n"
424 + "<nc:error-severity>error</nc:error-severity>\n"
425 + "<source-daemon>\n"
427 + "</source-daemon>\n"
428 + "<nc:error-message>\n"
429 + "Number of member links configured, i.e [1], "
430 + "for interface [ae0]is lesser than the required minimum [2].\n"
431 + "</nc:error-message>\n"
432 + "</nc:rpc-error>\n"
434 + "<nc:error-type>protocol</nc:error-type>\n"
435 + "<nc:error-tag>operation-failed</nc:error-tag>\n"
436 + "<nc:error-severity>error</nc:error-severity>\n"
437 + "<nc:error-message>\n"
438 + "configuration check-out failed\n"
439 + "</nc:error-message>\n"
440 + "</nc:rpc-error>\n"
443 ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
444 Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
445 return new NetconfMessage(doc);
448 private static NetconfMessage createErrorResponseMessage(final String messageID) throws Exception {
449 String xmlStr = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\""
450 + " message-id=\"" + messageID + "\">"
452 + " <error-type>rpc</error-type>"
453 + " <error-tag>missing-attribute</error-tag>"
454 + " <error-severity>error</error-severity>"
455 + " <error-message>Missing attribute</error-message>"
457 + " <bad-attribute>foo</bad-attribute>"
458 + " <bad-element>bar</bad-element>"
463 ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
464 Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
465 return new NetconfMessage(doc);
468 private static void verifyResponseMessage(final RpcResult<NetconfMessage> rpcResult, final String dataText) {
469 assertNotNull("RpcResult is null", rpcResult);
470 assertTrue("isSuccessful", rpcResult.isSuccessful());
471 NetconfMessage messageResult = rpcResult.getResult();
472 assertNotNull("getResult", messageResult);
473 // List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
474 // QName.create( URI.create( "ns" ), null, "data" ) );
475 // assertNotNull( "getSimpleNodesByName", nodes );
476 // assertEquals( "List<SimpleNode<?>> size", 1, nodes.size() );
477 // assertEquals( "SimpleNode value", dataText, nodes.iterator().next().getValue() );
480 private static RpcError verifyErrorRpcResult(final RpcResult<NetconfMessage> rpcResult,
481 final ErrorType expErrorType, final ErrorTag expErrorTag) {
482 assertNotNull("RpcResult is null", rpcResult);
483 assertFalse("isSuccessful", rpcResult.isSuccessful());
484 assertNotNull("RpcResult errors is null", rpcResult.getErrors());
485 assertEquals("Errors size", 1, rpcResult.getErrors().size());
486 RpcError rpcError = rpcResult.getErrors().iterator().next();
487 assertEquals("getErrorSeverity", ErrorSeverity.ERROR, rpcError.getSeverity());
488 assertEquals("getErrorType", expErrorType, rpcError.getErrorType());
489 assertEquals("getErrorTag", expErrorTag, rpcError.getTag());
491 final String msg = rpcError.getMessage();
492 assertNotNull("getMessage is null", msg);
493 assertFalse("getMessage is empty", msg.isEmpty());
494 assertFalse("getMessage is blank", CharMatcher.whitespace().matchesAllOf(msg));