Bump upstreams
[netconf.git] / plugins / sal-netconf-connector / src / test / java / org / opendaylight / netconf / sal / connect / netconf / listener / 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.sal.connect.netconf.listener;
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.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.spy;
24 import static org.mockito.Mockito.timeout;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.withSettings;
27 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
28
29 import com.google.common.base.CharMatcher;
30 import com.google.common.base.Strings;
31 import com.google.common.util.concurrent.ListenableFuture;
32 import io.netty.channel.Channel;
33 import io.netty.channel.ChannelFuture;
34 import io.netty.channel.EventLoopGroup;
35 import io.netty.channel.nio.NioEventLoopGroup;
36 import io.netty.util.HashedWheelTimer;
37 import io.netty.util.Timer;
38 import io.netty.util.concurrent.Future;
39 import io.netty.util.concurrent.GenericFutureListener;
40 import io.netty.util.concurrent.GlobalEventExecutor;
41 import java.io.ByteArrayInputStream;
42 import java.net.InetSocketAddress;
43 import java.util.ArrayList;
44 import java.util.Set;
45 import java.util.UUID;
46 import java.util.concurrent.TimeUnit;
47 import java.util.concurrent.TimeoutException;
48 import javax.xml.parsers.ParserConfigurationException;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.ArgumentCaptor;
53 import org.mockito.Mock;
54 import org.mockito.MockMakers;
55 import org.mockito.junit.MockitoJUnitRunner;
56 import org.opendaylight.netconf.api.NetconfMessage;
57 import org.opendaylight.netconf.api.NetconfTerminationReason;
58 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
59 import org.opendaylight.netconf.client.NetconfClientDispatcherImpl;
60 import org.opendaylight.netconf.client.NetconfClientSession;
61 import org.opendaylight.netconf.client.NetconfClientSessionListener;
62 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
63 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
64 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
65 import org.opendaylight.netconf.nettyutil.ReconnectStrategy;
66 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategy;
67 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
68 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
69 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceId;
70 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
71 import org.opendaylight.yangtools.util.xml.UntrustedXML;
72 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
73 import org.opendaylight.yangtools.yang.common.ErrorTag;
74 import org.opendaylight.yangtools.yang.common.ErrorType;
75 import org.opendaylight.yangtools.yang.common.QName;
76 import org.opendaylight.yangtools.yang.common.RpcError;
77 import org.opendaylight.yangtools.yang.common.RpcResult;
78 import org.opendaylight.yangtools.yang.common.Uint32;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81 import org.w3c.dom.Document;
82 import org.w3c.dom.Element;
83
84 @RunWith(MockitoJUnitRunner.StrictStubs.class)
85 public class NetconfDeviceCommunicatorTest {
86
87     private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceCommunicatorTest.class);
88     private static final Uint32 SESSION_ID = Uint32.ONE;
89
90     @Mock
91     RemoteDevice<NetconfDeviceCommunicator> mockDevice;
92
93     private NetconfClientSession spySession;
94     private NetconfDeviceCommunicator communicator;
95
96     @Before
97     public void setUp() throws Exception {
98         communicator = new NetconfDeviceCommunicator(
99                 new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice, 10);
100         // FIXME: spy() except we override the MockMaker in use
101         spySession = mock(NetconfClientSession.class, withSettings()
102             .spiedInstance(new NetconfClientSession(mock(NetconfClientSessionListener.class), mock(Channel.class),
103                 SESSION_ID.toJava(), Set.of()))
104             .defaultAnswer(CALLS_REAL_METHODS)
105             .mockMaker(MockMakers.SUBCLASS));
106     }
107
108     void setupSession() {
109         doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class),
110                 any(NetconfDeviceCommunicator.class));
111         communicator.onSessionUp(spySession);
112     }
113
114     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
115         return sendRequest(UUID.randomUUID().toString(), true);
116     }
117
118     @SuppressWarnings("unchecked")
119     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest(final String messageID,
120                                                                     final boolean doLastTest) throws Exception {
121         Document doc = UntrustedXML.newDocumentBuilder().newDocument();
122         Element element = doc.createElement("request");
123         element.setAttribute("message-id", messageID);
124         doc.appendChild(element);
125         NetconfMessage message = new NetconfMessage(doc);
126
127         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
128         doReturn(mockChannelFuture).when(mockChannelFuture)
129                 .addListener(any(GenericFutureListener.class));
130         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
131
132         ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
133                 communicator.sendRequest(message, QName.create("", "mockRpc"));
134         if (doLastTest) {
135             assertNotNull("ListenableFuture is null", resultFuture);
136         }
137         return resultFuture;
138     }
139
140     @Test
141     public void testOnSessionUp() {
142         final var testCapability = "urn:opendaylight:params:xml:ns:test?module=test-module&revision=2014-06-02";
143         final var serverCapabilities = Set.of(
144             NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
145             NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
146             testCapability);
147         doReturn(serverCapabilities).when(spySession).getServerCapabilities();
148
149         final var netconfSessionPreferences = ArgumentCaptor.forClass(NetconfSessionPreferences.class);
150         doNothing().when(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
151
152         communicator.onSessionUp(spySession);
153
154         verify(spySession).getServerCapabilities();
155         verify(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
156
157         NetconfSessionPreferences actualCapabilites = netconfSessionPreferences.getValue();
158         assertTrue(actualCapabilites.containsNonModuleCapability(
159                 NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()));
160         assertFalse(actualCapabilites.containsNonModuleCapability(testCapability));
161         assertEquals(Set.of(QName.create("urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module")),
162                 actualCapabilites.moduleBasedCaps().keySet());
163         assertTrue(actualCapabilites.isRollbackSupported());
164         assertTrue(actualCapabilites.isMonitoringSupported());
165         assertEquals(SESSION_ID, actualCapabilites.sessionId());
166     }
167
168     @SuppressWarnings("unchecked")
169     @Test(timeout = 5000)
170     public void testOnSessionDown() throws Exception {
171         setupSession();
172
173         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
174         final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
175
176         doNothing().when(mockDevice).onRemoteSessionDown();
177
178         communicator.onSessionDown(spySession, new Exception("mock ex"));
179
180         verifyErrorRpcResult(resultFuture1.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
181         verifyErrorRpcResult(resultFuture2.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
182
183         verify(mockDevice).onRemoteSessionDown();
184
185         reset(mockDevice);
186
187         communicator.onSessionDown(spySession, new Exception("mock ex"));
188
189         verify(mockDevice, never()).onRemoteSessionDown();
190     }
191
192     @Test
193     public void testOnSessionTerminated() throws Exception {
194         setupSession();
195
196         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
197
198         doNothing().when(mockDevice).onRemoteSessionDown();
199
200         String reasonText = "testing terminate";
201         NetconfTerminationReason reason = new NetconfTerminationReason(reasonText);
202         communicator.onSessionTerminated(spySession, reason);
203
204         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
205         assertEquals("RpcError message", reasonText, rpcError.getMessage());
206
207         verify(mockDevice).onRemoteSessionDown();
208     }
209
210     @Test
211     public void testClose() throws Exception {
212         communicator.close();
213         verify(mockDevice, never()).onRemoteSessionDown();
214     }
215
216     @SuppressWarnings({"rawtypes", "unchecked"})
217     @Test
218     public void testSendRequest() throws Exception {
219         setupSession();
220
221         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
222         QName rpc = QName.create("", "mockRpc");
223
224         ArgumentCaptor<GenericFutureListener> futureListener =
225                 ArgumentCaptor.forClass(GenericFutureListener.class);
226
227         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
228         doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
229         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
230
231         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
232
233         verify(spySession).sendMessage(same(message));
234
235         assertNotNull("ListenableFuture is null", resultFuture);
236
237         verify(mockChannelFuture).addListener(futureListener.capture());
238         Future<Void> operationFuture = mock(Future.class);
239         doReturn(true).when(operationFuture).isSuccess();
240         futureListener.getValue().operationComplete(operationFuture);
241
242         try {
243             resultFuture.get(1, TimeUnit.MILLISECONDS); // verify it's not cancelled or has an error set
244         } catch (TimeoutException e) {
245             LOG.info("Operation failed due timeout.");
246         } // expected
247     }
248
249     @Test
250     public void testSendRequestWithNoSession() throws Exception {
251         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
252         QName rpc = QName.create("", "mockRpc");
253
254         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
255
256         assertNotNull("ListenableFuture is null", resultFuture);
257
258         // Should have an immediate result
259         RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
260
261         verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
262     }
263
264     private static NetconfMessage createSuccessResponseMessage(final String messageID)
265             throws ParserConfigurationException {
266         Document doc = UntrustedXML.newDocumentBuilder().newDocument();
267         Element rpcReply =
268                 doc.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlNetconfConstants.RPC_REPLY_KEY);
269         rpcReply.setAttribute("message-id", messageID);
270         Element element = doc.createElementNS("ns", "data");
271         element.setTextContent(messageID);
272         rpcReply.appendChild(element);
273         doc.appendChild(rpcReply);
274
275         return new NetconfMessage(doc);
276     }
277
278     @SuppressWarnings({ "rawtypes", "unchecked" })
279     @Test
280     public void testSendRequestWithWithSendFailure() throws Exception {
281         setupSession();
282
283         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
284         QName rpc = QName.create("", "mockRpc");
285
286         ArgumentCaptor<GenericFutureListener> futureListener =
287                 ArgumentCaptor.forClass(GenericFutureListener.class);
288
289         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
290         doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
291         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
292
293         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
294
295         assertNotNull("ListenableFuture is null", resultFuture);
296
297         verify(mockChannelFuture).addListener(futureListener.capture());
298
299         Future<Void> operationFuture = mock(Future.class);
300         doReturn(false).when(operationFuture).isSuccess();
301         doReturn(new Exception("mock error")).when(operationFuture).cause();
302         futureListener.getValue().operationComplete(operationFuture);
303
304         // Should have an immediate result
305         RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
306
307         RpcError rpcError = verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
308         assertEquals("RpcError message contains \"mock error\"", true,
309                 rpcError.getMessage().contains("mock error"));
310     }
311
312     //Test scenario verifying whether missing message is handled
313     @Test
314     public void testOnMissingResponseMessage() throws Exception {
315
316         setupSession();
317
318         String messageID1 = UUID.randomUUID().toString();
319         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
320
321         String messageID2 = UUID.randomUUID().toString();
322         ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
323
324         String messageID3 = UUID.randomUUID().toString();
325         ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest(messageID3, true);
326
327         //response messages 1,2 are omitted
328         communicator.onMessage(spySession, createSuccessResponseMessage(messageID3));
329
330         verifyResponseMessage(resultFuture3.get(), messageID3);
331     }
332
333     @Test
334     public void testOnSuccessfulResponseMessage() throws Exception {
335         setupSession();
336
337         String messageID1 = UUID.randomUUID().toString();
338         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
339
340         String messageID2 = UUID.randomUUID().toString();
341         final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
342
343         communicator.onMessage(spySession, createSuccessResponseMessage(messageID1));
344         communicator.onMessage(spySession, createSuccessResponseMessage(messageID2));
345
346         verifyResponseMessage(resultFuture1.get(), messageID1);
347         verifyResponseMessage(resultFuture2.get(), messageID2);
348     }
349
350     @Test
351     public void testOnResponseMessageWithError() throws Exception {
352         setupSession();
353
354         String messageID = UUID.randomUUID().toString();
355         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
356
357         communicator.onMessage(spySession, createErrorResponseMessage(messageID));
358
359         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE);
360         assertEquals("RpcError message", "Missing attribute", rpcError.getMessage());
361
362         String errorInfo = rpcError.getInfo();
363         assertNotNull("RpcError info is null", errorInfo);
364         assertTrue("Error info contains \"foo\"", errorInfo.contains("<bad-attribute>foo</bad-attribute>"));
365         assertTrue("Error info contains \"bar\"", errorInfo.contains("<bad-element>bar</bad-element>"));
366     }
367
368     @Test
369     public void testOnResponseMessageWithMultipleErrors() throws Exception {
370         setupSession();
371
372         String messageID = UUID.randomUUID().toString();
373         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
374
375         communicator.onMessage(spySession, createMultiErrorResponseMessage(messageID));
376
377         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED);
378
379         String errorInfo = rpcError.getInfo();
380         assertNotNull("RpcError info is null", errorInfo);
381
382         String errorInfoMessages = rpcError.getInfo();
383         String errMsg1 = "Number of member links configured, i.e [1], "
384                 + "for interface [ae0]is lesser than the required minimum [2].";
385         String errMsg2 = "configuration check-out failed";
386         assertTrue(String.format("Error info contains \"%s\" or \"%s\'", errMsg1, errMsg2),
387                 errorInfoMessages.contains(errMsg1) && errorInfoMessages.contains(errMsg2));
388     }
389
390     /**
391      * Test whether reconnect is scheduled properly.
392      */
393     @Test
394     public void testNetconfDeviceReconnectInCommunicator() {
395         final RemoteDevice<NetconfDeviceCommunicator> device = mock(RemoteDevice.class);
396
397         final TimedReconnectStrategy timedReconnectStrategy =
398                 new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, 10000, 0, 1.0, null, 100L, null);
399         final ReconnectStrategy reconnectStrategy = spy(new ReconnectStrategy() {
400             @Override
401             @Deprecated
402             public int getConnectTimeout() throws Exception {
403                 return timedReconnectStrategy.getConnectTimeout();
404             }
405
406             @Override
407             @Deprecated
408             public Future<Void> scheduleReconnect(final Throwable cause) {
409                 return timedReconnectStrategy.scheduleReconnect(cause);
410             }
411
412             @Override
413             @Deprecated
414             public void reconnectSuccessful() {
415                 timedReconnectStrategy.reconnectSuccessful();
416             }
417         });
418
419         final EventLoopGroup group = new NioEventLoopGroup();
420         final Timer time = new HashedWheelTimer();
421         try {
422             final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(
423                     new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), device, 10);
424             final NetconfReconnectingClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create()
425                     .withAddress(new InetSocketAddress("localhost", 65000))
426                     .withReconnectStrategy(reconnectStrategy)
427                     .withConnectStrategyFactory(() -> reconnectStrategy)
428                     .withAuthHandler(new LoginPasswordHandler("admin", "admin"))
429                     .withConnectionTimeoutMillis(10000)
430                     .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
431                     .withSessionListener(listener)
432                     .build();
433
434             listener.initializeRemoteConnection(new NetconfClientDispatcherImpl(group, group, time), cfg);
435
436             verify(reconnectStrategy,
437                     timeout(TimeUnit.MINUTES.toMillis(4)).times(101)).scheduleReconnect(any(Throwable.class));
438         } finally {
439             time.stop();
440             group.shutdownGracefully();
441         }
442     }
443
444     @Test
445     public void testOnResponseMessageWithWrongMessageID() throws Exception {
446         setupSession();
447
448         String messageID = UUID.randomUUID().toString();
449         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
450
451         communicator.onMessage(spySession, createSuccessResponseMessage(UUID.randomUUID().toString()));
452
453         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
454         assertFalse("RpcError message non-empty", Strings.isNullOrEmpty(rpcError.getMessage()));
455
456         String errorInfo = rpcError.getInfo();
457         assertNotNull("RpcError info is null", errorInfo);
458         assertTrue("Error info contains \"actual-message-id\"", errorInfo.contains("actual-message-id"));
459         assertTrue("Error info contains \"expected-message-id\"", errorInfo.contains("expected-message-id"));
460     }
461
462     @Test
463     public void testConcurrentMessageLimit() throws Exception {
464         setupSession();
465         ArrayList<String> messageID = new ArrayList<>();
466
467         for (int i = 0; i < 10; i++) {
468             messageID.add(UUID.randomUUID().toString());
469             ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID.get(i), false);
470             assertEquals("ListenableFuture is null", true, resultFuture instanceof UncancellableFuture);
471         }
472
473         final String notWorkingMessageID = UUID.randomUUID().toString();
474         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(notWorkingMessageID, false);
475         assertEquals("ListenableFuture is null", false, resultFuture instanceof UncancellableFuture);
476
477         communicator.onMessage(spySession, createSuccessResponseMessage(messageID.get(0)));
478
479         resultFuture = sendRequest(messageID.get(0), false);
480         assertNotNull("ListenableFuture is null", resultFuture);
481     }
482
483     private static NetconfMessage createMultiErrorResponseMessage(final String messageID) throws Exception {
484         // multiple rpc-errors which simulate actual response like in NETCONF-666
485         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\""
486                 + "           message-id=\"" + messageID + "\">"
487                 + "<nc:rpc-error>\n"
488                 + "<nc:error-type>protocol</nc:error-type>\n"
489                 + "<nc:error-tag>operation-failed</nc:error-tag>\n"
490                 + "<nc:error-severity>error</nc:error-severity>\n"
491                 + "<source-daemon>\n"
492                 + "dcd\n"
493                 + "</source-daemon>\n"
494                 + "<nc:error-message>\n"
495                 + "Number of member links configured, i.e [1], "
496                 + "for interface [ae0]is lesser than the required minimum [2].\n"
497                 + "</nc:error-message>\n"
498                 + "</nc:rpc-error>\n"
499                 + "<nc:rpc-error>\n"
500                 + "<nc:error-type>protocol</nc:error-type>\n"
501                 + "<nc:error-tag>operation-failed</nc:error-tag>\n"
502                 + "<nc:error-severity>error</nc:error-severity>\n"
503                 + "<nc:error-message>\n"
504                 + "configuration check-out failed\n"
505                 + "</nc:error-message>\n"
506                 + "</nc:rpc-error>\n"
507                 + "</nc:rpc-reply>";
508
509         ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
510         Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
511         return new NetconfMessage(doc);
512     }
513
514     private static NetconfMessage createErrorResponseMessage(final String messageID) throws Exception {
515         String xmlStr = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\""
516                 + "           message-id=\"" + messageID + "\">"
517                 + "  <rpc-error>"
518                 + "    <error-type>rpc</error-type>"
519                 + "    <error-tag>missing-attribute</error-tag>"
520                 + "    <error-severity>error</error-severity>"
521                 + "    <error-message>Missing attribute</error-message>"
522                 + "    <error-info>"
523                 + "      <bad-attribute>foo</bad-attribute>"
524                 + "      <bad-element>bar</bad-element>"
525                 + "    </error-info>"
526                 + "  </rpc-error>"
527                 + "</rpc-reply>";
528
529         ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
530         Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
531         return new NetconfMessage(doc);
532     }
533
534     private static void verifyResponseMessage(final RpcResult<NetconfMessage> rpcResult, final String dataText) {
535         assertNotNull("RpcResult is null", rpcResult);
536         assertTrue("isSuccessful", rpcResult.isSuccessful());
537         NetconfMessage messageResult = rpcResult.getResult();
538         assertNotNull("getResult", messageResult);
539 //        List<SimpleNode<?>> nodes = messageResult.getSimpleNodesByName(
540 //                                         QName.create( URI.create( "ns" ), null, "data" ) );
541 //        assertNotNull( "getSimpleNodesByName", nodes );
542 //        assertEquals( "List<SimpleNode<?>> size", 1, nodes.size() );
543 //        assertEquals( "SimpleNode value", dataText, nodes.iterator().next().getValue() );
544     }
545
546     private static RpcError verifyErrorRpcResult(final RpcResult<NetconfMessage> rpcResult,
547                                                  final ErrorType expErrorType, final ErrorTag expErrorTag) {
548         assertNotNull("RpcResult is null", rpcResult);
549         assertFalse("isSuccessful", rpcResult.isSuccessful());
550         assertNotNull("RpcResult errors is null", rpcResult.getErrors());
551         assertEquals("Errors size", 1, rpcResult.getErrors().size());
552         RpcError rpcError = rpcResult.getErrors().iterator().next();
553         assertEquals("getErrorSeverity", ErrorSeverity.ERROR, rpcError.getSeverity());
554         assertEquals("getErrorType", expErrorType, rpcError.getErrorType());
555         assertEquals("getErrorTag", expErrorTag, rpcError.getTag());
556
557         final String msg = rpcError.getMessage();
558         assertNotNull("getMessage is null", msg);
559         assertFalse("getMessage is empty", msg.isEmpty());
560         assertFalse("getMessage is blank", CharMatcher.whitespace().matchesAllOf(msg));
561         return rpcError;
562     }
563 }