Migrate netconf-client-mdsal/impl tests to JUnit5
[netconf.git] / plugins / netconf-client-mdsal / src / test / java / org / opendaylight / netconf / client / mdsal / NetconfDeviceCommunicatorTest.java
1 /*
2  * Copyright (c) 2014 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.client.mdsal;
9
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.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.verify;
24
25 import com.google.common.base.CharMatcher;
26 import com.google.common.base.Strings;
27 import com.google.common.util.concurrent.Futures;
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;
36 import java.util.Set;
37 import java.util.UUID;
38 import javax.xml.parsers.ParserConfigurationException;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.mockito.ArgumentCaptor;
43 import org.mockito.Mock;
44 import org.mockito.junit.MockitoJUnitRunner;
45 import org.opendaylight.netconf.api.CapabilityURN;
46 import org.opendaylight.netconf.api.NamespaceURN;
47 import org.opendaylight.netconf.api.NetconfTerminationReason;
48 import org.opendaylight.netconf.api.messages.NetconfMessage;
49 import org.opendaylight.netconf.api.messages.RpcReplyMessage;
50 import org.opendaylight.netconf.client.NetconfClientSession;
51 import org.opendaylight.netconf.client.NetconfClientSessionListener;
52 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
53 import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
54 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.SessionIdType;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
57 import org.opendaylight.yangtools.util.xml.UntrustedXML;
58 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
59 import org.opendaylight.yangtools.yang.common.ErrorTag;
60 import org.opendaylight.yangtools.yang.common.ErrorType;
61 import org.opendaylight.yangtools.yang.common.QName;
62 import org.opendaylight.yangtools.yang.common.RpcError;
63 import org.opendaylight.yangtools.yang.common.RpcResult;
64 import org.opendaylight.yangtools.yang.common.Uint32;
65 import org.w3c.dom.Document;
66 import org.w3c.dom.Element;
67
68 @RunWith(MockitoJUnitRunner.StrictStubs.class)
69 public class NetconfDeviceCommunicatorTest {
70     private static final SessionIdType SESSION_ID = new SessionIdType(Uint32.ONE);
71
72     @Mock
73     private RemoteDevice<NetconfDeviceCommunicator> mockDevice;
74
75     private NetconfClientSession spySession;
76     private NetconfDeviceCommunicator communicator;
77
78     @Before
79     public void setUp() throws Exception {
80         communicator = new NetconfDeviceCommunicator(
81                 new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice, 10);
82         spySession = spy(new NetconfClientSession(mock(NetconfClientSessionListener.class), mock(Channel.class),
83             SESSION_ID, Set.of()));
84     }
85
86     void setupSession() {
87         doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class),
88                 any(NetconfDeviceCommunicator.class));
89         communicator.onSessionUp(spySession);
90     }
91
92     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
93         return sendRequest(UUID.randomUUID().toString(), true);
94     }
95
96     @SuppressWarnings("unchecked")
97     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest(final String messageID,
98                                                                     final boolean doLastTest) throws Exception {
99         Document doc = UntrustedXML.newDocumentBuilder().newDocument();
100         Element element = doc.createElement("request");
101         element.setAttribute("message-id", messageID);
102         doc.appendChild(element);
103         NetconfMessage message = new NetconfMessage(doc);
104
105         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
106         doReturn(mockChannelFuture).when(mockChannelFuture)
107                 .addListener(any(GenericFutureListener.class));
108         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
109
110         ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
111                 communicator.sendRequest(message, QName.create("", "mockRpc"));
112         if (doLastTest) {
113             assertNotNull("ListenableFuture is null", resultFuture);
114         }
115         return resultFuture;
116     }
117
118     @Test
119     public void testOnSessionUp() {
120         final var testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
121         final var serverCapabilities = Set.of(
122             CapabilityURN.ROLLBACK_ON_ERROR,
123             NetconfState.QNAME.getNamespace().toString(),
124             testCapability);
125         doReturn(serverCapabilities).when(spySession).getServerCapabilities();
126
127         final var netconfSessionPreferences = ArgumentCaptor.forClass(NetconfSessionPreferences.class);
128         doNothing().when(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
129
130         communicator.onSessionUp(spySession);
131
132         verify(spySession).getServerCapabilities();
133         verify(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
134
135         NetconfSessionPreferences actualCapabilites = netconfSessionPreferences.getValue();
136         assertTrue(actualCapabilites.containsNonModuleCapability(
137                 "urn:ietf:params:netconf:capability:rollback-on-error:1.0"));
138         assertFalse(actualCapabilites.containsNonModuleCapability(testCapability));
139         assertEquals(Set.of(QName.create("urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module")),
140                 actualCapabilites.moduleBasedCaps().keySet());
141         assertTrue(actualCapabilites.isRollbackSupported());
142         assertTrue(actualCapabilites.isMonitoringSupported());
143         assertEquals(SESSION_ID, actualCapabilites.sessionId());
144     }
145
146     @SuppressWarnings("unchecked")
147     @Test(timeout = 5000)
148     public void testOnSessionDown() throws Exception {
149         setupSession();
150
151         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
152         final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
153
154         doNothing().when(mockDevice).onRemoteSessionDown();
155
156         communicator.onSessionDown(spySession, new Exception("mock ex"));
157
158         verifyErrorRpcResult(resultFuture1.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
159         verifyErrorRpcResult(resultFuture2.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
160
161         verify(mockDevice).onRemoteSessionDown();
162
163         reset(mockDevice);
164
165         communicator.onSessionDown(spySession, new Exception("mock ex"));
166
167         verify(mockDevice, never()).onRemoteSessionDown();
168     }
169
170     @Test
171     public void testOnSessionTerminated() throws Exception {
172         setupSession();
173
174         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
175
176         doNothing().when(mockDevice).onRemoteSessionDown();
177
178         String reasonText = "testing terminate";
179         NetconfTerminationReason reason = new NetconfTerminationReason(reasonText);
180         communicator.onSessionTerminated(spySession, reason);
181
182         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
183         assertEquals("RpcError message", reasonText, rpcError.getMessage());
184
185         verify(mockDevice).onRemoteSessionDown();
186     }
187
188     @Test
189     public void testClose() throws Exception {
190         communicator.close();
191         verify(mockDevice, never()).onRemoteSessionDown();
192     }
193
194     @SuppressWarnings("unchecked")
195     @Test
196     public void testSendRequest() throws Exception {
197         setupSession();
198
199         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
200         QName rpc = QName.create("", "mockRpc");
201
202         final var futureListener = ArgumentCaptor.forClass(GenericFutureListener.class);
203
204         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
205         doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
206         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
207
208         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
209
210         verify(spySession).sendMessage(same(message));
211
212         assertNotNull("ListenableFuture is null", resultFuture);
213
214         verify(mockChannelFuture).addListener(futureListener.capture());
215         Future<Void> operationFuture = mock(Future.class);
216         doReturn(null).when(operationFuture).cause();
217         futureListener.getValue().operationComplete(operationFuture);
218
219         // verify it is not cancelled nor has an error set
220         assertFalse(resultFuture.isDone());
221     }
222
223     @Test
224     public void testSendRequestWithNoSession() throws Exception {
225         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
226         QName rpc = QName.create("", "mockRpc");
227
228         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
229
230         assertNotNull("ListenableFuture is null", resultFuture);
231
232         // Should have an immediate result
233         RpcResult<NetconfMessage> rpcResult = Futures.getDone(resultFuture);
234
235         verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
236     }
237
238     private static NetconfMessage createSuccessResponseMessage(final String messageID)
239             throws ParserConfigurationException {
240         Document doc = UntrustedXML.newDocumentBuilder().newDocument();
241         Element rpcReply = doc.createElementNS(NamespaceURN.BASE, RpcReplyMessage.ELEMENT_NAME);
242         rpcReply.setAttribute("message-id", messageID);
243         Element element = doc.createElementNS("ns", "data");
244         element.setTextContent(messageID);
245         rpcReply.appendChild(element);
246         doc.appendChild(rpcReply);
247
248         return new NetconfMessage(doc);
249     }
250
251     @SuppressWarnings("unchecked")
252     @Test
253     public void testSendRequestWithWithSendFailure() throws Exception {
254         setupSession();
255
256         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
257         QName rpc = QName.create("", "mockRpc");
258
259         final var futureListener = ArgumentCaptor.forClass(GenericFutureListener.class);
260
261         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
262         doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
263         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
264
265         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
266
267         assertNotNull("ListenableFuture is null", resultFuture);
268
269         verify(mockChannelFuture).addListener(futureListener.capture());
270
271         Future<Void> operationFuture = mock(Future.class);
272         doReturn(new Exception("mock error")).when(operationFuture).cause();
273         futureListener.getValue().operationComplete(operationFuture);
274
275         // Should have an immediate result
276         RpcResult<NetconfMessage> rpcResult = Futures.getDone(resultFuture);
277
278         RpcError rpcError = verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
279         assertEquals("RpcError message contains \"mock error\"", true,
280                 rpcError.getMessage().contains("mock error"));
281     }
282
283     //Test scenario verifying whether missing message is handled
284     @Test
285     public void testOnMissingResponseMessage() throws Exception {
286
287         setupSession();
288
289         String messageID1 = UUID.randomUUID().toString();
290         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
291
292         String messageID2 = UUID.randomUUID().toString();
293         ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
294
295         String messageID3 = UUID.randomUUID().toString();
296         ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest(messageID3, true);
297
298         //response messages 1,2 are omitted
299         communicator.onMessage(spySession, createSuccessResponseMessage(messageID3));
300
301         verifyResponseMessage(resultFuture3.get(), messageID3);
302     }
303
304     @Test
305     public void testOnSuccessfulResponseMessage() throws Exception {
306         setupSession();
307
308         String messageID1 = UUID.randomUUID().toString();
309         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
310
311         String messageID2 = UUID.randomUUID().toString();
312         final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
313
314         communicator.onMessage(spySession, createSuccessResponseMessage(messageID1));
315         communicator.onMessage(spySession, createSuccessResponseMessage(messageID2));
316
317         verifyResponseMessage(resultFuture1.get(), messageID1);
318         verifyResponseMessage(resultFuture2.get(), messageID2);
319     }
320
321     @Test
322     public void testOnResponseMessageWithError() throws Exception {
323         setupSession();
324
325         String messageID = UUID.randomUUID().toString();
326         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
327
328         communicator.onMessage(spySession, createErrorResponseMessage(messageID));
329
330         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE);
331         assertEquals("RpcError message", "Missing attribute", rpcError.getMessage());
332
333         String errorInfo = rpcError.getInfo();
334         assertNotNull("RpcError info is null", errorInfo);
335         assertTrue("Error info contains \"foo\"", errorInfo.contains("<bad-attribute>foo</bad-attribute>"));
336         assertTrue("Error info contains \"bar\"", errorInfo.contains("<bad-element>bar</bad-element>"));
337     }
338
339     @Test
340     public void testOnResponseMessageWithMultipleErrors() throws Exception {
341         setupSession();
342
343         String messageID = UUID.randomUUID().toString();
344         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
345
346         communicator.onMessage(spySession, createMultiErrorResponseMessage(messageID));
347
348         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED);
349
350         String errorInfo = rpcError.getInfo();
351         assertNotNull("RpcError info is null", errorInfo);
352
353         String errorInfoMessages = rpcError.getInfo();
354         String errMsg1 = "Number of member links configured, i.e [1], "
355                 + "for interface [ae0]is lesser than the required minimum [2].";
356         String errMsg2 = "configuration check-out failed";
357         assertTrue(String.format("Error info contains \"%s\" or \"%s\'", errMsg1, errMsg2),
358                 errorInfoMessages.contains(errMsg1) && errorInfoMessages.contains(errMsg2));
359     }
360
361     @Test
362     public void testOnResponseMessageWithWrongMessageID() throws Exception {
363         setupSession();
364
365         String messageID = UUID.randomUUID().toString();
366         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
367
368         communicator.onMessage(spySession, createSuccessResponseMessage(UUID.randomUUID().toString()));
369
370         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
371         assertFalse("RpcError message non-empty", Strings.isNullOrEmpty(rpcError.getMessage()));
372
373         String errorInfo = rpcError.getInfo();
374         assertNotNull("RpcError info is null", errorInfo);
375         assertTrue("Error info contains \"actual-message-id\"", errorInfo.contains("actual-message-id"));
376         assertTrue("Error info contains \"expected-message-id\"", errorInfo.contains("expected-message-id"));
377     }
378
379     @Test
380     public void testConcurrentMessageLimit() throws Exception {
381         setupSession();
382         ArrayList<String> messageID = new ArrayList<>();
383
384         for (int i = 0; i < 10; i++) {
385             messageID.add(UUID.randomUUID().toString());
386             ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID.get(i), false);
387             assertEquals("ListenableFuture is null", true, resultFuture instanceof UncancellableFuture);
388         }
389
390         final String notWorkingMessageID = UUID.randomUUID().toString();
391         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(notWorkingMessageID, false);
392         assertEquals("ListenableFuture is null", false, resultFuture instanceof UncancellableFuture);
393
394         communicator.onMessage(spySession, createSuccessResponseMessage(messageID.get(0)));
395
396         resultFuture = sendRequest(messageID.get(0), false);
397         assertNotNull("ListenableFuture is null", resultFuture);
398     }
399
400     private static NetconfMessage createMultiErrorResponseMessage(final String messageID) throws Exception {
401         // multiple rpc-errors which simulate actual response like in NETCONF-666
402         String xmlStr = "<nc:rpc-reply xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
403                 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
404                 + "xmlns:junos=\"http://xml.juniper.net/junos/18.4R1/junos\" message-id=\"" + messageID + "\">"
405                 + "<nc:rpc-error>\n"
406                 + "<nc:error-type>protocol</nc:error-type>\n"
407                 + "<nc:error-tag>operation-failed</nc:error-tag>\n"
408                 + "<nc:error-severity>error</nc:error-severity>\n"
409                 + "<source-daemon>\n"
410                 + "dcd\n"
411                 + "</source-daemon>\n"
412                 + "<nc:error-message>\n"
413                 + "Number of member links configured, i.e [1], "
414                 + "for interface [ae0]is lesser than the required minimum [2].\n"
415                 + "</nc:error-message>\n"
416                 + "</nc:rpc-error>\n"
417                 + "<nc:rpc-error>\n"
418                 + "<nc:error-type>protocol</nc:error-type>\n"
419                 + "<nc:error-tag>operation-failed</nc:error-tag>\n"
420                 + "<nc:error-severity>error</nc:error-severity>\n"
421                 + "<nc:error-message>\n"
422                 + "configuration check-out failed\n"
423                 + "</nc:error-message>\n"
424                 + "</nc:rpc-error>\n"
425                 + "</nc:rpc-reply>";
426
427         ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
428         Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
429         return new NetconfMessage(doc);
430     }
431
432     private static NetconfMessage createErrorResponseMessage(final String messageID) throws Exception {
433         String xmlStr = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\""
434                 + "           message-id=\"" + messageID + "\">"
435                 + "  <rpc-error>"
436                 + "    <error-type>rpc</error-type>"
437                 + "    <error-tag>missing-attribute</error-tag>"
438                 + "    <error-severity>error</error-severity>"
439                 + "    <error-message>Missing attribute</error-message>"
440                 + "    <error-info>"
441                 + "      <bad-attribute>foo</bad-attribute>"
442                 + "      <bad-element>bar</bad-element>"
443                 + "    </error-info>"
444                 + "  </rpc-error>"
445                 + "</rpc-reply>";
446
447         ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
448         Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
449         return new NetconfMessage(doc);
450     }
451
452     private static void verifyResponseMessage(final RpcResult<NetconfMessage> rpcResult, final String dataText) {
453         assertNotNull("RpcResult is null", rpcResult);
454         assertTrue("isSuccessful", rpcResult.isSuccessful());
455         NetconfMessage messageResult = rpcResult.getResult();
456         assertNotNull("getResult", messageResult);
457 //        List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
458 //                                         QName.create( URI.create( "ns" ), null, "data" ) );
459 //        assertNotNull( "getSimpleNodesByName", nodes );
460 //        assertEquals( "List<SimpleNode<?>> size", 1, nodes.size() );
461 //        assertEquals( "SimpleNode value", dataText, nodes.iterator().next().getValue() );
462     }
463
464     private static RpcError verifyErrorRpcResult(final RpcResult<NetconfMessage> rpcResult,
465                                                  final ErrorType expErrorType, final ErrorTag expErrorTag) {
466         assertNotNull("RpcResult is null", rpcResult);
467         assertFalse("isSuccessful", rpcResult.isSuccessful());
468         assertNotNull("RpcResult errors is null", rpcResult.getErrors());
469         assertEquals("Errors size", 1, rpcResult.getErrors().size());
470         RpcError rpcError = rpcResult.getErrors().iterator().next();
471         assertEquals("getErrorSeverity", ErrorSeverity.ERROR, rpcError.getSeverity());
472         assertEquals("getErrorType", expErrorType, rpcError.getErrorType());
473         assertEquals("getErrorTag", expErrorTag, rpcError.getTag());
474
475         final String msg = rpcError.getMessage();
476         assertNotNull("getMessage is null", msg);
477         assertFalse("getMessage is empty", msg.isEmpty());
478         assertFalse("getMessage is blank", CharMatcher.whitespace().matchesAllOf(msg));
479         return rpcError;
480     }
481 }