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