Bug 4940 - correctly implement default-request-timeout-millis 28/32428/7
authoradetalhouet <adetalhouet@inocybe.com>
Mon, 18 Jan 2016 21:12:14 +0000 (16:12 -0500)
committeradetalhouet <adetalhouet@inocybe.com>
Fri, 22 Jan 2016 13:56:04 +0000 (08:56 -0500)
Change-Id: I6922839d08459e3fd5225ff08213daf646608eaf
Signed-off-by: adetalhouet <adetalhouet@inocybe.com>
opendaylight/md-sal/sal-netconf-connector/pom.xml
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java
opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java

index b60b9674141c46482aef489368e321cd9accbb73..d4195f4e3061251d04f5adbdb60fcd66497b4c81 100644 (file)
       <artifactId>mockito-all</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <version>1.5.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <version>1.5.2</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
index 20221d788c345df7e695626b6297571c46ac6029..be59580cb39fc60741196eafd23ed4fd23ec3275 100644 (file)
@@ -96,8 +96,8 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
         checkNotNull(getConnectionTimeoutMillis(), connectionTimeoutMillisJmxAttribute);
         checkCondition(getConnectionTimeoutMillis() > 0, "must be > 0", connectionTimeoutMillisJmxAttribute);
 
-        checkNotNull(getConnectionTimeoutMillis(), defaultRequestTimeoutMillisJmxAttribute);
-        checkCondition(getConnectionTimeoutMillis() > 0, "must be > 0", defaultRequestTimeoutMillisJmxAttribute);
+        checkNotNull(getDefaultRequestTimeoutMillis(), defaultRequestTimeoutMillisJmxAttribute);
+        checkCondition(getDefaultRequestTimeoutMillis() > 0, "must be > 0", defaultRequestTimeoutMillisJmxAttribute);
 
         checkNotNull(getBetweenAttemptsTimeoutMillis(), betweenAttemptsTimeoutMillisJmxAttribute);
         checkCondition(getBetweenAttemptsTimeoutMillis() > 0, "must be > 0", betweenAttemptsTimeoutMillisJmxAttribute);
index bc6adc6a1f8301188f9d6a16b4038e2da6b504b3..1c32bbaa4b890e1e62b1c2f6bd03dfa8b907d223 100644 (file)
@@ -54,7 +54,7 @@ final class NetconfDeviceDataBroker implements DOMDataBroker {
 
     @Override
     public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
-        return new ReadOnlyTx(netconfOps, id);
+        return new ReadOnlyTx(netconfOps, id, requestTimeoutMillis);
     }
 
     @Override
index 93efc18b21e077e81312017502396d9725477e96..cdc7a532535747b0b2214c72f9c38c358639ba38 100644 (file)
@@ -3,10 +3,13 @@ package org.opendaylight.controller.sal.connect.netconf.sal.tx;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
@@ -20,6 +23,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -27,7 +31,7 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
 
     private static final Logger LOG  = LoggerFactory.getLogger(AbstractWriteTx.class);
 
-    private final long defaultRequestTimeoutMillis;
+    protected final long defaultRequestTimeoutMillis;
     protected final RemoteDeviceId id;
     protected final NetconfBaseOps netOps;
     protected final boolean rollbackSupport;
@@ -56,7 +60,7 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
 
     protected void invokeBlocking(final String msg, final Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>> op) throws NetconfDocumentedException {
         try {
-            final DOMRpcResult compositeNodeRpcResult = op.apply(netOps).get(defaultRequestTimeoutMillis, TimeUnit.MILLISECONDS);
+            final DOMRpcResult compositeNodeRpcResult = op.apply(netOps).get();
             if(isSuccess(compositeNodeRpcResult) == false) {
                 throw new NetconfDocumentedException(id + ": " + msg + " failed: " + compositeNodeRpcResult.getErrors(), NetconfDocumentedException.ErrorType.application,
                         NetconfDocumentedException.ErrorTag.operation_failed, NetconfDocumentedException.ErrorSeverity.warning);
@@ -64,7 +68,7 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
         } catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new RuntimeException(e);
-        } catch (final ExecutionException | TimeoutException e) {
+        } catch (final ExecutionException e) {
             throw new NetconfDocumentedException(id + ": " + msg + " failed: " + e.getMessage(), e, NetconfDocumentedException.ErrorType.application,
                     NetconfDocumentedException.ErrorTag.operation_failed, NetconfDocumentedException.ErrorSeverity.warning);
         }
@@ -166,4 +170,17 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
     }
 
     protected abstract void editConfig(DataContainerChild<?, ?> editStructure, Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException;
+
+    protected ListenableFuture<DOMRpcResult> perfomRequestWithTimeout(String operation, ListenableFuture<DOMRpcResult> future) {
+        try {
+            future.get(defaultRequestTimeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("{}: {} failed with error", operation, id, e);
+            return Futures.immediateFailedCheckedFuture(new RuntimeException(id + ": " + operation + " failed"));
+        } catch (TimeoutException e) {
+            LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, defaultRequestTimeoutMillis, e);
+            return Futures.immediateFailedCheckedFuture(new SchemaSourceException(e.getMessage()));
+        }
+        return future;
+    }
 }
index e08731ed407525735e15c4ba424d85847433d49d..4200c6c137cfdad58441dedec690cf9487f4db81 100644 (file)
@@ -14,7 +14,11 @@ import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
@@ -40,9 +44,12 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
     private final RemoteDeviceId id;
     private final FutureCallback<DOMRpcResult> loggingCallback;
 
-    public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id) {
+    private final long requestTimeoutMillis;
+
+    public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id, final long requestTimeoutMillis) {
         this.netconfOps = netconfOps;
         this.id = id;
+        this.requestTimeoutMillis = requestTimeoutMillis;
 
         // Simple logging callback to log result of read operation
         loggingCallback = new FutureCallback<DOMRpcResult>() {
@@ -53,7 +60,6 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
                 } else {
                     LOG.warn("{}: Reading data unsuccessful: {}", id, result.getErrors());
                 }
-
             }
 
             @Override
@@ -77,6 +83,11 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
             }
         });
 
+
+        if(!readWithTimeout("readConfigurationData", configRunning)) {
+            return null;
+        }
+
         return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
     }
 
@@ -108,6 +119,10 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
             }
         });
 
+        if(!readWithTimeout("readOperationalData", configCandidate)) {
+            return null;
+        }
+
         return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
     }
 
@@ -146,4 +161,18 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
     public Object getIdentifier() {
         return this;
     }
+
+    private boolean readWithTimeout(String operation, ListenableFuture<DOMRpcResult> future) {
+        try {
+            future.get(requestTimeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("{}: {} failed with error", id, operation, e);
+            throw new RuntimeException(id + ": readOperationalData failed");
+        } catch (TimeoutException e) {
+            LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, requestTimeoutMillis, e);
+            future.cancel(true);
+            return false;
+        }
+        return true;
+    }
 }
index fbd99c5128bd00340f2d4ae368f1b7703eeb9fe8..403df18fbf6f8ecd6901ca48f47402620b3d6d10 100644 (file)
@@ -8,8 +8,10 @@
 
 package org.opendaylight.controller.sal.connect.netconf.sal.tx;
 
+
 import com.google.common.base.Function;
 import com.google.common.util.concurrent.ListenableFuture;
+
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
 import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
@@ -29,7 +31,7 @@ public class WriteCandidateRunningTx extends WriteCandidateTx {
 
     private static final Logger LOG  = LoggerFactory.getLogger(WriteCandidateRunningTx.class);
 
-    public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final boolean rollbackSupport, long requestTimeoutMillis) {
+    public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final boolean rollbackSupport, final long requestTimeoutMillis) {
         super(id, netOps, rollbackSupport, requestTimeoutMillis);
     }
 
@@ -46,11 +48,13 @@ public class WriteCandidateRunningTx extends WriteCandidateTx {
     }
 
     private void lockRunning() {
+        final String operation = "Lock Running";
         try {
-            invokeBlocking("Lock running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+            invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
                 @Override
                 public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-                    return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
+                    return perfomRequestWithTimeout(operation, input.lockRunning(new NetconfRpcFutureCallback(operation, id)));
+
                 }
             });
         } catch (final NetconfDocumentedException e) {
index 0f2b00280a162b0cddf0435b04a7df4d9ec566ac..4008b4215a4907f8044229582f46604d8a19e904 100644 (file)
@@ -14,6 +14,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
@@ -93,11 +94,12 @@ public class WriteCandidateTx extends AbstractWriteTx {
     }
 
     private void lock() throws NetconfDocumentedException {
+        final String operation = "Lock candidate";
         try {
-            invokeBlocking("Lock candidate", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+            invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
                 @Override
                 public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-                    return input.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id));
+                    return perfomRequestWithTimeout(operation, input.lockCandidate(new NetconfRpcFutureCallback(operation, id)));
                 }
             });
         } catch (final NetconfDocumentedException e) {
@@ -183,14 +185,16 @@ public class WriteCandidateTx extends AbstractWriteTx {
 
     @Override
     protected void editConfig(final DataContainerChild<?, ?> editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
-        invokeBlocking("Edit candidate", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+        final String operation = "Edit candidate";
+        invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
             @Override
             public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-                        return defaultOperation.isPresent()
-                                ? input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, defaultOperation.get(),
+
+                        return perfomRequestWithTimeout(operation, defaultOperation.isPresent()
+                                ? input.editConfigCandidate(new NetconfRpcFutureCallback(operation, id), editStructure, defaultOperation.get(),
                                 rollbackSupport)
-                                : input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure,
-                                rollbackSupport);
+                                : input.editConfigCandidate(new NetconfRpcFutureCallback(operation, id), editStructure,
+                                rollbackSupport));
             }
         });
     }
index ffc2b0c65c48e5f05f78404b70d1c16e78f0dddd..7930ed757fb9670995d62662e6bb8703d0ef174d 100644 (file)
@@ -13,6 +13,7 @@ import com.google.common.base.Optional;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
@@ -59,11 +60,12 @@ public class WriteRunningTx extends AbstractWriteTx {
     }
 
     private void lock() {
+        final String operation = "Lock running";
         try {
-            invokeBlocking("Lock running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+            invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
                 @Override
                 public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-                    return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
+                    return perfomRequestWithTimeout(operation, input.lockRunning(new NetconfRpcFutureCallback(operation, id)));
                 }
             });
         } catch (final NetconfDocumentedException e) {
@@ -94,14 +96,14 @@ public class WriteRunningTx extends AbstractWriteTx {
 
     @Override
     public synchronized CheckedFuture<Void, TransactionCommitFailedException> submit() {
-        final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
+        final ListenableFuture<Void> commitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
             @Override
             public Void apply(final RpcResult<TransactionStatus> input) {
                 return null;
             }
         });
 
-        return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
+        return Futures.makeChecked(commitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
             @Override
             public TransactionCommitFailedException apply(final Exception input) {
                 return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
@@ -117,24 +119,26 @@ public class WriteRunningTx extends AbstractWriteTx {
 
     @Override
     protected void editConfig(final DataContainerChild<?, ?> editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
-        invokeBlocking("Edit running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+        final String operation = "Edit running";
+        invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
             @Override
             public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-                        return defaultOperation.isPresent()
-                                ? input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, defaultOperation.get(),
+                        return perfomRequestWithTimeout(operation, defaultOperation.isPresent()
+                                ? input.editConfigRunning(new NetconfRpcFutureCallback(operation, id), editStructure, defaultOperation.get(),
                                 rollbackSupport)
-                                : input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure,
-                                rollbackSupport);
+                                : input.editConfigRunning(new NetconfRpcFutureCallback(operation, id), editStructure,
+                                rollbackSupport));
             }
         });
     }
 
     private void unlock() {
+        final String operation = "Unlocking running";
         try {
-            invokeBlocking("Unlocking running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+            invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
                 @Override
                 public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-                    return input.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
+                    return perfomRequestWithTimeout(operation, input.unlockRunning(new NetconfRpcFutureCallback(operation, id)));
                 }
             });
         } catch (final NetconfDocumentedException e) {
index e8587d609d74d6d50db7e0c0eb5b4c2bf5c8d1f5..2a5a0998f180a15ed01cf79e1d3c8c612f07a15c 100644 (file)
@@ -13,15 +13,27 @@ import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
 import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
 import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
 import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
@@ -31,7 +43,12 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
 
+@PrepareForTest({NetconfBaseOps.class})
+@RunWith(PowerMockRunner.class)
 public class ReadOnlyTxTest {
 
     private static final YangInstanceIdentifier path = YangInstanceIdentifier.create();
@@ -53,11 +70,33 @@ public class ReadOnlyTxTest {
     public void testRead() throws Exception {
         final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc, mock(SchemaContext.class));
 
-        final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)));
+        final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)), 60000L);
 
         readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create());
         verify(rpc).invokeRpc(Mockito.eq(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME)), any(NormalizedNode.class));
         readOnlyTx.read(LogicalDatastoreType.OPERATIONAL, path);
         verify(rpc).invokeRpc(Mockito.eq(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.NETCONF_GET_QNAME)), any(NormalizedNode.class));
     }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testReadTimeout() throws Exception {
+        final ListenableFuture<DOMRpcResult> future = mock(ListenableFuture.class);
+
+        Mockito.when(future.get(Mockito.anyLong(), any(TimeUnit.class))).then(new Answer<DOMRpcResult>() {
+            @Override
+            public DOMRpcResult answer(InvocationOnMock invocation)
+                    throws Throwable {
+                throw new TimeoutException("Processing Timeout");
+            }
+        });
+
+        final NetconfBaseOps netconfOps = PowerMockito.mock(NetconfBaseOps.class);
+        Mockito.when(netconfOps.getConfigRunning(any(FutureCallback.class), any(Optional.class))).thenReturn(future);
+
+
+        final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)), 100L);
+        Assert.assertNull("Read operation didn't correctly timeout", readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create()));
+        readOnlyTx.close();
+    }
 }
\ No newline at end of file