Add a failing DataBroker 63/76563/3
authorStephen Kitt <skitt@redhat.com>
Tue, 2 Oct 2018 14:19:25 +0000 (16:19 +0200)
committerRobert Varga <nite@hq.sk>
Tue, 2 Oct 2018 19:56:53 +0000 (19:56 +0000)
This imports DataBrokerFailures from Genius; this is a test class
which allows faults to be injected into a data broker.

Change-Id: I89ac278a9291245ca2a1cdc42e14ced4656695b4
Signed-off-by: Stephen Kitt <skitt@redhat.com>
binding/mdsal-binding-test-utils/pom.xml
binding/mdsal-binding-test-utils/src/main/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailures.java [new file with mode: 0644]
binding/mdsal-binding-test-utils/src/main/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailuresImpl.java [new file with mode: 0644]
binding/mdsal-binding-test-utils/src/test/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailuresTest.java [new file with mode: 0644]

index 3f504359c52f3d31d90b0256e25ea34d894f81f2..a164be328baac28cd23f37090198d567af687b68 100644 (file)
             <artifactId>diffutils</artifactId>
             <version>1.3.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-spi</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.mdsal</groupId>
             <artifactId>mdsal-binding-dom-adapter</artifactId>
diff --git a/binding/mdsal-binding-test-utils/src/main/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailures.java b/binding/mdsal-binding-test-utils/src/main/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailures.java
new file mode 100644 (file)
index 0000000..a33386a
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.testutils;
+
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Configures a DataBroker to simulate failures, useful for tests.
+ *
+ * @author Michael Vorburger.ch
+ */
+public interface DataBrokerFailures {
+
+    /**
+     * Fails all future reads.
+     *
+     * @param exception a {@link ReadFailedException} to throw from a
+     * {@link ReadTransaction#read(LogicalDatastoreType, InstanceIdentifier)} call.
+     */
+    void failReads(ReadFailedException exception);
+
+    /**
+     * Fails N future reads.
+     *
+     * @param howManyTimes how many times to throw the passed exception, until it resets.
+     *
+     * @param exception a {@link ReadFailedException} to throw from a
+     * {@link ReadTransaction#read(LogicalDatastoreType, InstanceIdentifier)} call.
+     */
+    void failReads(int howManyTimes, ReadFailedException exception);
+
+    /**
+     * Fails all future Transaction commits.
+     *
+     * @param exception an Exception to throw from a {@link WriteTransaction#commit()} method.
+     */
+    void failCommits(TransactionCommitFailedException exception);
+
+    /**
+     * Fails N future Transaction commits.
+     *
+     * @param howManyTimes
+     *               how many times to throw the passed exception, until it resets
+     *
+     * @param exception an Exception to throw from a {@link WriteTransaction#commit()} method.
+     */
+    void failCommits(int howManyTimes, TransactionCommitFailedException exception);
+
+    /**
+     * To simulate scenarios where even though the transaction throws a
+     * TransactionCommitFailedException (caused by
+     * akka.pattern.AskTimeoutException) it eventually succeeds. These timeouts
+     * are typically seen in scaled cluster environments under load. The new
+     * tell-based protocol, which will soon be enabled by default (c/61002),
+     * adds internal retries for transactions, making the application not to
+     * handle such scenarios.
+     */
+    void failButCommitAnyway();
+
+    /**
+     * Resets any earlier {@link #failReads(ReadFailedException)} or {@link #failReads(int, ReadFailedException)}.
+     */
+    void unfailReads();
+
+    /**
+     * Resets any earlier {@link #failCommits(TransactionCommitFailedException)} or
+     * {@link #failCommits(int, TransactionCommitFailedException)}.
+     */
+    void unfailCommits();
+}
diff --git a/binding/mdsal-binding-test-utils/src/main/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailuresImpl.java b/binding/mdsal-binding-test-utils/src/main/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailuresImpl.java
new file mode 100644 (file)
index 0000000..8b1f5db
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.testutils;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.binding.spi.ForwardingDataBroker;
+import org.opendaylight.mdsal.binding.spi.ForwardingReadWriteTransaction;
+import org.opendaylight.mdsal.binding.spi.ForwardingWriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataBroker with methods to simulate failures, useful for tests.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class DataBrokerFailuresImpl extends ForwardingDataBroker implements DataBrokerFailures {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DataBrokerFailuresImpl.class);
+
+    private final DataBroker delegate;
+    private volatile @Nullable ReadFailedException readException;
+    private volatile @Nullable TransactionCommitFailedException commitException;
+    private final AtomicInteger howManyFailingReads = new AtomicInteger();
+    private final AtomicInteger howManyFailingCommits = new AtomicInteger();
+    private boolean commitAndThrowException = false;
+
+    public DataBrokerFailuresImpl(DataBroker delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    protected DataBroker delegate() {
+        return delegate;
+    }
+
+    @Override
+    public void failReads(ReadFailedException exception) {
+        unfailReads();
+        readException = Objects.requireNonNull(exception, "exception == null");
+    }
+
+    @Override
+    public void failReads(int howManyTimes, ReadFailedException exception) {
+        unfailReads();
+        howManyFailingReads.set(howManyTimes);
+        readException = Objects.requireNonNull(exception, "exception == null");
+    }
+
+    @Override
+    public void failCommits(TransactionCommitFailedException exception) {
+        unfailCommits();
+        this.commitException = Objects.requireNonNull(exception, "exception == null");
+    }
+
+    @Override
+    public void failCommits(int howManyTimes, TransactionCommitFailedException exception) {
+        howManyFailingCommits.set(howManyTimes);
+        this.commitException = Objects.requireNonNull(exception, "exception == null");
+    }
+
+    @Override
+    public void unfailReads() {
+        readException = null;
+        howManyFailingReads.set(-1);
+    }
+
+    @Override
+    public void unfailCommits() {
+        this.commitException = null;
+        howManyFailingCommits.set(-1);
+        this.commitAndThrowException = false;
+    }
+
+    @Override
+    public void failButCommitAnyway() {
+        unfailCommits();
+        this.commitException = new TransactionCommitFailedException("caused by simulated AskTimeoutException");
+        this.commitAndThrowException = true;
+    }
+
+    private FluentFuture<? extends CommitInfo> handleCommit(Supplier<FluentFuture<? extends CommitInfo>> commitMethod) {
+        if (howManyFailingCommits.decrementAndGet() == -1) {
+            commitException = null;
+        }
+        if (commitException == null) {
+            return commitMethod.get();
+        } else {
+            if (commitAndThrowException) {
+                try {
+                    commitMethod.get().get();
+                } catch (InterruptedException | ExecutionException e) {
+                    LOG.warn("Exception while waiting for committed transaction", e);
+                }
+            }
+            return FluentFuture.from(Futures.immediateFailedFuture(commitException));
+        }
+    }
+
+    public <T extends DataObject> FluentFuture<Optional<T>> handleRead(
+            BiFunction<LogicalDatastoreType, InstanceIdentifier<T>, FluentFuture<Optional<T>>> readMethod,
+            LogicalDatastoreType store, InstanceIdentifier<T> path) {
+        if (howManyFailingReads.decrementAndGet() == -1) {
+            readException = null;
+        }
+        if (readException == null) {
+            return readMethod.apply(store, path);
+        } else {
+            return FluentFuture.from(Futures.immediateFailedFuture(readException));
+        }
+    }
+
+    @Override
+    public ReadWriteTransaction newReadWriteTransaction() {
+        return new ForwardingReadWriteTransaction(delegate.newReadWriteTransaction()) {
+            @Override
+            public <T extends DataObject> FluentFuture<Optional<T>> read(LogicalDatastoreType store,
+                    InstanceIdentifier<T> path) {
+                return handleRead(super::read, store, path);
+            }
+
+            @Override
+            public FluentFuture<? extends CommitInfo> commit() {
+                return handleCommit(super::commit);
+            }
+        };
+    }
+
+    @Override
+    public WriteTransaction newWriteOnlyTransaction() {
+        return new ForwardingWriteTransaction(delegate.newWriteOnlyTransaction()) {
+            @Override
+            public FluentFuture<? extends CommitInfo> commit() {
+                return handleCommit(super::commit);
+            }
+        };
+    }
+
+}
diff --git a/binding/mdsal-binding-test-utils/src/test/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailuresTest.java b/binding/mdsal-binding-test-utils/src/test/java/org/opendaylight/mdsal/binding/testutils/DataBrokerFailuresTest.java
new file mode 100644 (file)
index 0000000..fa9106a
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.testutils;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.runners.MethodSorters.NAME_ASCENDING;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+
+/**
+ * Unit test for DataBrokerFailuresImpl.
+ *
+ * @author Michael Vorburger.ch
+ */
+@FixMethodOrder(NAME_ASCENDING)
+public class DataBrokerFailuresTest {
+
+    private final DataBrokerFailures dbFailures;
+    private final DataBroker dataBroker;
+
+    public DataBrokerFailuresTest() {
+        DataBroker mockDataBroker = mock(DataBroker.class);
+        ReadWriteTransaction readWriteTransaction = mock(ReadWriteTransaction.class);
+        doReturn(CommitInfo.emptyFluentFuture()).when(readWriteTransaction).commit();
+        doReturn(readWriteTransaction).when(mockDataBroker).newReadWriteTransaction();
+        WriteTransaction writeTransaction = mock(WriteTransaction.class);
+        doReturn(CommitInfo.emptyFluentFuture()).when(writeTransaction).commit();
+        doReturn(writeTransaction).when(mockDataBroker).newWriteOnlyTransaction();
+        dbFailures = new DataBrokerFailuresImpl(mockDataBroker);
+        dataBroker = (DataBroker) dbFailures;
+    }
+
+    @Before
+    public void setup() {
+
+    }
+
+    @Test
+    public void testFailReadWriteTransactionCommit() throws TimeoutException, InterruptedException {
+        dbFailures.failCommits(new OptimisticLockFailedException("bada boum bam!"));
+        checkCommitFails();
+        // Now make sure that it still fails, and not just once:
+        checkCommitFails();
+        // and still:
+        checkCommitFails();
+    }
+
+    private void checkCommitFails() throws TimeoutException, InterruptedException {
+        try {
+            dataBroker.newReadWriteTransaction().commit().get(5, TimeUnit.SECONDS);
+            fail("This should have led to a TransactionCommitFailedException!");
+        } catch (ExecutionException e) {
+            assertTrue("Expected TransactionCommitFailedException",
+                    e.getCause() instanceof TransactionCommitFailedException);
+        }
+    }
+
+    @Test
+    public void testFailReadWriteTransactionCommitNext()
+            throws TimeoutException, InterruptedException, ExecutionException {
+        // This must pass (the failCommits from previous test cannot affect this)
+        // (It's a completely new instance of DataBroker & DataBrokerFailures anyways, but just to be to sure.)
+        dataBroker.newReadWriteTransaction().commit().get(5, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testFailTwoReadWriteTransactionCommit()
+            throws TimeoutException, InterruptedException, ExecutionException {
+        dbFailures.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+        checkCommitFails();
+        // Now make sure that it still fails again a 2nd time, and not just once:
+        checkCommitFails();
+        // But now it should pass.. because we specified howManyTimes = 2 above
+        dataBroker.newReadWriteTransaction().commit().get(5, TimeUnit.SECONDS);
+        dataBroker.newWriteOnlyTransaction().commit().get(5, TimeUnit.SECONDS);
+        dataBroker.newReadWriteTransaction().commit().get(5, TimeUnit.SECONDS);
+    }
+
+    @Test(expected = OptimisticLockFailedException.class)
+    @SuppressWarnings("checkstyle:AvoidHidingCauseException")
+    public void testFailWriteTransactionCommit()
+            throws TimeoutException, InterruptedException, TransactionCommitFailedException {
+        dbFailures.failCommits(new OptimisticLockFailedException("bada boum bam!"));
+        try {
+            dataBroker.newWriteOnlyTransaction().commit().get(5, TimeUnit.SECONDS);
+        } catch (ExecutionException e) {
+            assertTrue("Expected TransactionCommitFailedException",
+                    e.getCause() instanceof TransactionCommitFailedException);
+            throw (TransactionCommitFailedException)e.getCause();
+        }
+    }
+
+    @Test
+    public void testUnfailCommits() throws TimeoutException, InterruptedException, ExecutionException {
+        dbFailures.failCommits(new OptimisticLockFailedException("bada boum bam!"));
+        checkCommitFails();
+        dbFailures.unfailCommits();
+        dataBroker.newReadWriteTransaction().commit().get(5, TimeUnit.SECONDS);
+        dataBroker.newWriteOnlyTransaction().commit().get(5, TimeUnit.SECONDS);
+        dataBroker.newReadWriteTransaction().commit().get(5, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testFailButCommitAnywayReadWriteTransaction() throws TimeoutException, InterruptedException {
+        dbFailures.failButCommitAnyway();
+        checkCommitFails();
+    }
+
+    // TODO make this work for TransactionChain as well ...
+
+}