Migrate Collections references
[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.verify;
24 import static org.mockito.Mockito.withSettings;
25
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;
36 import java.util.Set;
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;
72
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);
77
78     @Mock
79     RemoteDevice<NetconfDeviceCommunicator> mockDevice;
80
81     private NetconfClientSession spySession;
82     private NetconfDeviceCommunicator communicator;
83
84     @Before
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));
94     }
95
96     void setupSession() {
97         doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class),
98                 any(NetconfDeviceCommunicator.class));
99         communicator.onSessionUp(spySession);
100     }
101
102     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
103         return sendRequest(UUID.randomUUID().toString(), true);
104     }
105
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);
114
115         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
116         doReturn(mockChannelFuture).when(mockChannelFuture)
117                 .addListener(any(GenericFutureListener.class));
118         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
119
120         ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
121                 communicator.sendRequest(message, QName.create("", "mockRpc"));
122         if (doLastTest) {
123             assertNotNull("ListenableFuture is null", resultFuture);
124         }
125         return resultFuture;
126     }
127
128     @Test
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(),
134             testCapability);
135         doReturn(serverCapabilities).when(spySession).getServerCapabilities();
136
137         final var netconfSessionPreferences = ArgumentCaptor.forClass(NetconfSessionPreferences.class);
138         doNothing().when(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
139
140         communicator.onSessionUp(spySession);
141
142         verify(spySession).getServerCapabilities();
143         verify(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
144
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());
154     }
155
156     @SuppressWarnings("unchecked")
157     @Test(timeout = 5000)
158     public void testOnSessionDown() throws Exception {
159         setupSession();
160
161         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest();
162         final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest();
163
164         doNothing().when(mockDevice).onRemoteSessionDown();
165
166         communicator.onSessionDown(spySession, new Exception("mock ex"));
167
168         verifyErrorRpcResult(resultFuture1.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
169         verifyErrorRpcResult(resultFuture2.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
170
171         verify(mockDevice).onRemoteSessionDown();
172
173         reset(mockDevice);
174
175         communicator.onSessionDown(spySession, new Exception("mock ex"));
176
177         verify(mockDevice, never()).onRemoteSessionDown();
178     }
179
180     @Test
181     public void testOnSessionTerminated() throws Exception {
182         setupSession();
183
184         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest();
185
186         doNothing().when(mockDevice).onRemoteSessionDown();
187
188         String reasonText = "testing terminate";
189         NetconfTerminationReason reason = new NetconfTerminationReason(reasonText);
190         communicator.onSessionTerminated(spySession, reason);
191
192         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
193         assertEquals("RpcError message", reasonText, rpcError.getMessage());
194
195         verify(mockDevice).onRemoteSessionDown();
196     }
197
198     @Test
199     public void testClose() throws Exception {
200         communicator.close();
201         verify(mockDevice, never()).onRemoteSessionDown();
202     }
203
204     @SuppressWarnings({"rawtypes", "unchecked"})
205     @Test
206     public void testSendRequest() throws Exception {
207         setupSession();
208
209         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
210         QName rpc = QName.create("", "mockRpc");
211
212         ArgumentCaptor<GenericFutureListener> futureListener =
213                 ArgumentCaptor.forClass(GenericFutureListener.class);
214
215         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
216         doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
217         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
218
219         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
220
221         verify(spySession).sendMessage(same(message));
222
223         assertNotNull("ListenableFuture is null", resultFuture);
224
225         verify(mockChannelFuture).addListener(futureListener.capture());
226         Future<Void> operationFuture = mock(Future.class);
227         doReturn(true).when(operationFuture).isSuccess();
228         futureListener.getValue().operationComplete(operationFuture);
229
230         try {
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.");
234         } // expected
235     }
236
237     @Test
238     public void testSendRequestWithNoSession() throws Exception {
239         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
240         QName rpc = QName.create("", "mockRpc");
241
242         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
243
244         assertNotNull("ListenableFuture is null", resultFuture);
245
246         // Should have an immediate result
247         RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
248
249         verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
250     }
251
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);
261
262         return new NetconfMessage(doc);
263     }
264
265     @SuppressWarnings({ "rawtypes", "unchecked" })
266     @Test
267     public void testSendRequestWithWithSendFailure() throws Exception {
268         setupSession();
269
270         NetconfMessage message = new NetconfMessage(UntrustedXML.newDocumentBuilder().newDocument());
271         QName rpc = QName.create("", "mockRpc");
272
273         ArgumentCaptor<GenericFutureListener> futureListener =
274                 ArgumentCaptor.forClass(GenericFutureListener.class);
275
276         ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
277         doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
278         doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
279
280         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
281
282         assertNotNull("ListenableFuture is null", resultFuture);
283
284         verify(mockChannelFuture).addListener(futureListener.capture());
285
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);
290
291         // Should have an immediate result
292         RpcResult<NetconfMessage> rpcResult = resultFuture.get(3, TimeUnit.MILLISECONDS);
293
294         RpcError rpcError = verifyErrorRpcResult(rpcResult, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
295         assertEquals("RpcError message contains \"mock error\"", true,
296                 rpcError.getMessage().contains("mock error"));
297     }
298
299     //Test scenario verifying whether missing message is handled
300     @Test
301     public void testOnMissingResponseMessage() throws Exception {
302
303         setupSession();
304
305         String messageID1 = UUID.randomUUID().toString();
306         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
307
308         String messageID2 = UUID.randomUUID().toString();
309         ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
310
311         String messageID3 = UUID.randomUUID().toString();
312         ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest(messageID3, true);
313
314         //response messages 1,2 are omitted
315         communicator.onMessage(spySession, createSuccessResponseMessage(messageID3));
316
317         verifyResponseMessage(resultFuture3.get(), messageID3);
318     }
319
320     @Test
321     public void testOnSuccessfulResponseMessage() throws Exception {
322         setupSession();
323
324         String messageID1 = UUID.randomUUID().toString();
325         ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest(messageID1, true);
326
327         String messageID2 = UUID.randomUUID().toString();
328         final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
329
330         communicator.onMessage(spySession, createSuccessResponseMessage(messageID1));
331         communicator.onMessage(spySession, createSuccessResponseMessage(messageID2));
332
333         verifyResponseMessage(resultFuture1.get(), messageID1);
334         verifyResponseMessage(resultFuture2.get(), messageID2);
335     }
336
337     @Test
338     public void testOnResponseMessageWithError() throws Exception {
339         setupSession();
340
341         String messageID = UUID.randomUUID().toString();
342         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
343
344         communicator.onMessage(spySession, createErrorResponseMessage(messageID));
345
346         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE);
347         assertEquals("RpcError message", "Missing attribute", rpcError.getMessage());
348
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>"));
353     }
354
355     @Test
356     public void testOnResponseMessageWithMultipleErrors() throws Exception {
357         setupSession();
358
359         String messageID = UUID.randomUUID().toString();
360         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
361
362         communicator.onMessage(spySession, createMultiErrorResponseMessage(messageID));
363
364         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED);
365
366         String errorInfo = rpcError.getInfo();
367         assertNotNull("RpcError info is null", errorInfo);
368
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));
375     }
376
377     @Test
378     public void testOnResponseMessageWithWrongMessageID() throws Exception {
379         setupSession();
380
381         String messageID = UUID.randomUUID().toString();
382         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
383
384         communicator.onMessage(spySession, createSuccessResponseMessage(UUID.randomUUID().toString()));
385
386         RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
387         assertFalse("RpcError message non-empty", Strings.isNullOrEmpty(rpcError.getMessage()));
388
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"));
393     }
394
395     @Test
396     public void testConcurrentMessageLimit() throws Exception {
397         setupSession();
398         ArrayList<String> messageID = new ArrayList<>();
399
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);
404         }
405
406         final String notWorkingMessageID = UUID.randomUUID().toString();
407         ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(notWorkingMessageID, false);
408         assertEquals("ListenableFuture is null", false, resultFuture instanceof UncancellableFuture);
409
410         communicator.onMessage(spySession, createSuccessResponseMessage(messageID.get(0)));
411
412         resultFuture = sendRequest(messageID.get(0), false);
413         assertNotNull("ListenableFuture is null", resultFuture);
414     }
415
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 + "\">"
421                 + "<nc:rpc-error>\n"
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"
426                 + "dcd\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"
433                 + "<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"
441                 + "</nc:rpc-reply>";
442
443         ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
444         Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
445         return new NetconfMessage(doc);
446     }
447
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 + "\">"
451                 + "  <rpc-error>"
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>"
456                 + "    <error-info>"
457                 + "      <bad-attribute>foo</bad-attribute>"
458                 + "      <bad-element>bar</bad-element>"
459                 + "    </error-info>"
460                 + "  </rpc-error>"
461                 + "</rpc-reply>";
462
463         ByteArrayInputStream bis = new ByteArrayInputStream(xmlStr.getBytes());
464         Document doc = UntrustedXML.newDocumentBuilder().parse(bis);
465         return new NetconfMessage(doc);
466     }
467
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() );
478     }
479
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());
490
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));
495         return rpcError;
496     }
497 }