<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>
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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);
+ }
+ };
+ }
+
+}
--- /dev/null
+/*
+ * 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 ...
+
+}