Delay snapshot backed transaction ready error to 3PC canCommit.
Change-Id: Ie4004db194483dfa1528258b464df8a2f8fa573e
JIRA: CONTROLLER-1812
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
private final DOMStoreTransactionChainImpl txChain;
ChainedTransactionCommitImpl(final InMemoryDOMDataStore store,
- final SnapshotBackedWriteTransaction<String> transaction,final DataTreeModification modification,
- final DOMStoreTransactionChainImpl txChain) {
- super(store, transaction, modification);
+ final SnapshotBackedWriteTransaction<String> transaction,
+ final DataTreeModification modification,
+ final DOMStoreTransactionChainImpl txChain,
+ final Exception operationError) {
+ super(store, transaction, modification, operationError);
this.txChain = Preconditions.checkNotNull(txChain);
}
@Override
protected DOMStoreThreePhaseCommitCohort createCohort(final SnapshotBackedWriteTransaction<String> tx,
- final DataTreeModification modification) {
- return new ChainedTransactionCommitImpl(store, tx, modification, this);
+ final DataTreeModification modification,
+ final Exception operationError) {
+ return new ChainedTransactionCommitImpl(store, tx, modification, this, operationError);
}
@Override
@Override
protected DOMStoreThreePhaseCommitCohort transactionReady(final SnapshotBackedWriteTransaction<String> tx,
- final DataTreeModification modification) {
+ final DataTreeModification modification,
+ final Exception readyError) {
LOG.debug("Tx: {} is submitted. Modifications: {}", tx.getIdentifier(), modification);
- return new InMemoryDOMStoreThreePhaseCommitCohort(this, tx, modification);
+ return new InMemoryDOMStoreThreePhaseCommitCohort(this, tx, modification, readyError);
}
String nextIdentifier() {
private final DataTreeModification modification;
private final InMemoryDOMDataStore store;
private DataTreeCandidate candidate;
+ private final Exception operationError;
InMemoryDOMStoreThreePhaseCommitCohort(final InMemoryDOMDataStore store,
- final SnapshotBackedWriteTransaction<String> writeTransaction, final DataTreeModification modification) {
+ final SnapshotBackedWriteTransaction<String> writeTransaction,
+ final DataTreeModification modification,
+ final Exception operationError) {
this.transaction = Preconditions.checkNotNull(writeTransaction);
this.modification = Preconditions.checkNotNull(modification);
this.store = Preconditions.checkNotNull(store);
+ this.operationError = operationError;
}
private static void warnDebugContext(final AbstractDOMStoreTransaction<?> transaction) {
@SuppressWarnings("checkstyle:IllegalCatch")
@Override
public final ListenableFuture<Boolean> canCommit() {
+ if (operationError != null) {
+ return Futures.immediateFailedFuture(operationError);
+ }
+
try {
store.validate(modification);
LOG.debug("Store Transaction: {} can be committed", getTransaction().getIdentifier());
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
writeTx.ready();
}
+ @Test
+ public void testReadyWithMissingMandatoryData() throws InterruptedException {
+ DOMStoreWriteTransaction writeTx = domStore.newWriteOnlyTransaction();
+ NormalizedNode<?, ?> testNode = ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(new NodeIdentifier(TestModel.MANDATORY_DATA_TEST_QNAME))
+ .addChild(ImmutableNodes.leafNode(TestModel.OPTIONAL_QNAME, "data"))
+ .build();
+ writeTx.write(TestModel.MANDATORY_DATA_TEST_PATH, testNode);
+ DOMStoreThreePhaseCommitCohort ready = writeTx.ready();
+ try {
+ ready.canCommit().get();
+ Assert.fail("Expected exception on canCommit");
+ } catch (ExecutionException e) {
+ // nop
+ }
+ }
+
@Test
public void testTransactionAbort() throws InterruptedException, ExecutionException {
public static final YangInstanceIdentifier OUTER_LIST_PATH =
YangInstanceIdentifier.builder(TEST_PATH).node(OUTER_LIST_QNAME).build();
+ public static final QName MANDATORY_DATA_TEST_QNAME =
+ QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test",
+ "2014-03-13",
+ "mandatory-data-test");
+ public static final QName OPTIONAL_QNAME = QName.create(MANDATORY_DATA_TEST_QNAME, "optional-data");
+ public static final QName MANDATORY_QNAME = QName.create(MANDATORY_DATA_TEST_QNAME, "mandatory-data");
+ public static final YangInstanceIdentifier MANDATORY_DATA_TEST_PATH =
+ YangInstanceIdentifier.of(MANDATORY_DATA_TEST_QNAME);
+
private TestModel() {
throw new UnsupportedOperationException();
}
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
import java.lang.reflect.Field;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedTransactions;
public class InMemoryDOMStoreThreePhaseCommitCohortTest {
- private static InMemoryDOMStoreThreePhaseCommitCohort inMemoryDOMStoreThreePhaseCommitCohort = null;
- private static final InMemoryDOMDataStore IN_MEMORY_DOM_DATA_STORE = mock(InMemoryDOMDataStore.class);
- private static final DataTreeCandidate DATA_TREE_CANDIDATE = mock(DataTreeCandidate.class);
+ private static InMemoryDOMStoreThreePhaseCommitCohort inMemoryDOMStoreThreePhaseCommitCohort = null;
+
+ @Mock
+ private static InMemoryDOMDataStore IN_MEMORY_DOM_DATA_STORE;
+
+ @Mock
+ private static DataTreeCandidate DATA_TREE_CANDIDATE;
+
+ @Mock
+ private static TransactionReadyPrototype<String> TRANSACTION_READY_PROTOTYPE;
+
+ @Mock
+ private static DataTreeSnapshot DATA_TREE_SNAPSHOT;
+
+ @Mock
+ private static DataTreeModification DATA_TREE_MODIFICATION;
@Before
public void setUp() throws Exception {
- reset(IN_MEMORY_DOM_DATA_STORE);
- DataTreeSnapshot dataTreeSnapshot = mock(DataTreeSnapshot.class);
- TransactionReadyPrototype<String> transactionReadyPrototype = mock(TransactionReadyPrototype.class);
- DataTreeModification dataTreeModification = mock(DataTreeModification.class);
- doReturn(dataTreeModification).when(dataTreeSnapshot).newModification();
- doReturn("testModification").when(dataTreeModification).toString();
-
+ initMocks(this);
+ doReturn(DATA_TREE_MODIFICATION).when(DATA_TREE_SNAPSHOT).newModification();
+ doReturn("testModification").when(DATA_TREE_MODIFICATION).toString();
inMemoryDOMStoreThreePhaseCommitCohort =
new InMemoryDOMStoreThreePhaseCommitCohort(IN_MEMORY_DOM_DATA_STORE,
- SnapshotBackedTransactions
- .newWriteTransaction("test", false, dataTreeSnapshot, transactionReadyPrototype),
- dataTreeModification);
+ SnapshotBackedTransactions.newWriteTransaction(
+ "test", false, DATA_TREE_SNAPSHOT, TRANSACTION_READY_PROTOTYPE),
+ DATA_TREE_MODIFICATION,
+ null);
}
@Test
verify(IN_MEMORY_DOM_DATA_STORE).validate(any());
}
+ @Test
+ public void canCommitWithOperationError() throws Exception {
+ RuntimeException operationError = new RuntimeException();
+ inMemoryDOMStoreThreePhaseCommitCohort =
+ new InMemoryDOMStoreThreePhaseCommitCohort(IN_MEMORY_DOM_DATA_STORE,
+ SnapshotBackedTransactions.newWriteTransaction(
+ "test", false, DATA_TREE_SNAPSHOT, TRANSACTION_READY_PROTOTYPE),
+ DATA_TREE_MODIFICATION,
+ operationError);
+ doNothing().when(IN_MEMORY_DOM_DATA_STORE).validate(any());
+ try {
+ inMemoryDOMStoreThreePhaseCommitCohort.canCommit().get();
+ fail("Expected exception");
+ } catch (ExecutionException e) {
+ assertTrue(e.getCause() == operationError);
+ }
+ }
+
@SuppressWarnings({ "checkstyle:IllegalThrows", "checkstyle:avoidHidingCauseException" })
@Test(expected = OptimisticLockFailedException.class)
public void canCommitTestWithOptimisticLockFailedException() throws Throwable {
}
}
}
+
+ container mandatory-data-test {
+ presence "needs to be present when empty";
+
+ leaf optional-data {
+ type string;
+ }
+ leaf mandatory-data {
+ type string;
+ mandatory true;
+ }
+ }
}
\ No newline at end of file
@Override
protected final DOMStoreThreePhaseCommitCohort transactionReady(
- final SnapshotBackedWriteTransaction<T> tx, final DataTreeModification tree) {
+ final SnapshotBackedWriteTransaction<T> tx,
+ final DataTreeModification tree,
+ final Exception readyError) {
final State localState = state;
if (localState instanceof Allocated) {
LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
}
- return createCohort(tx, tree);
+ return createCohort(tx, tree, readyError);
}
@Override
* Create a cohort for driving the transaction through the commit process.
* @param transaction Transaction handle
* @param modification {@link DataTreeModification} which needs to be applied to the backend
+ * @param operationError Any previous error that could be reported through three phase commit
* @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
*/
- protected abstract DOMStoreThreePhaseCommitCohort createCohort(
- SnapshotBackedWriteTransaction<T> transaction, DataTreeModification modification);
+ protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
+ DataTreeModification modification,
+ Exception operationError);
}
import com.google.common.base.Throwables;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import javax.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
"Transaction %s is no longer open. No further modifications allowed.", getIdentifier());
}
+ @SuppressWarnings("checkstyle:IllegalCatch")
@Override
public DOMStoreThreePhaseCommitCohort ready() {
@SuppressWarnings("unchecked")
final DataTreeModification tree = mutableTree;
TREE_UPDATER.lazySet(this, null);
- tree.ready();
- return wasReady.transactionReady(this, tree);
+ try {
+ tree.ready();
+ return wasReady.transactionReady(this, tree, null);
+ } catch (RuntimeException e) {
+ LOG.debug("Store transaction: {}: unexpected failure when readying", getIdentifier(), e);
+ return wasReady.transactionReady(this, tree, e);
+ }
}
@Override
* Transaction on which ready was invoked.
* @param tree
* Modified data tree which has been constructed.
+ * @param readyError
+ * Any error that has already happened when readying.
* @return DOMStoreThreePhaseCommitCohort associated with transaction
*/
- protected abstract DOMStoreThreePhaseCommitCohort transactionReady(
- SnapshotBackedWriteTransaction<T> tx, DataTreeModification tree);
+ protected abstract DOMStoreThreePhaseCommitCohort transactionReady(SnapshotBackedWriteTransaction<T> tx,
+ DataTreeModification tree,
+ @Nullable Exception readyError);
}
}
this.newWriteOnlyTransaction().close();
this.newReadWriteTransaction().close();
- this.transactionReady(snapshotBackedWriteTransaction, dataTreeModification);
+ this.transactionReady(snapshotBackedWriteTransaction, dataTreeModification, null);
this.transactionAborted(snapshotBackedWriteTransaction);
@Override
protected DOMStoreThreePhaseCommitCohort createCohort(final SnapshotBackedWriteTransaction<Object> transaction,
- final DataTreeModification modification) {
+ final DataTreeModification modification,
+ final Exception operationError) {
return domStoreThreePhaseCommitCohort;
}
}
\ No newline at end of file
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
doNothing().when(TRANSACTION_READY_PROTOTYPE).transactionAborted(any());
doReturn("testDataTreeModification").when(DATA_TREE_MODIFICATION).toString();
doReturn("testNormalizedNode").when(NORMALIZED_NODE).toString();
- doReturn(DOM_STORE_THREE_PHASE_COMMIT_COHORT).when(TRANSACTION_READY_PROTOTYPE).transactionReady(any(),any());
+ doReturn(DOM_STORE_THREE_PHASE_COMMIT_COHORT)
+ .when(TRANSACTION_READY_PROTOTYPE)
+ .transactionReady(any(),any(), any());
doReturn(NORMALIZED_NODE_OPTIONAL).when(DATA_TREE_MODIFICATION).readNode(YangInstanceIdentifier.EMPTY);
snapshotBackedWriteTransaction = new SnapshotBackedWriteTransaction<>(new Object(), false, DATA_TREE_SNAPSHOT,
TRANSACTION_READY_PROTOTYPE);
SnapshotBackedWriteTransaction<Object> tx = new SnapshotBackedWriteTransaction<>(new Object(), false,
DATA_TREE_SNAPSHOT, TRANSACTION_READY_PROTOTYPE);
Assert.assertNotNull(tx.ready());
- verify(TRANSACTION_READY_PROTOTYPE).transactionReady(any(), any());
+ verify(TRANSACTION_READY_PROTOTYPE).transactionReady(any(), any(), eq(null));
tx.close();
}
+ @Test
+ public void readyWithException() {
+ Exception thrown = new RuntimeException();
+ doThrow(thrown).when(DATA_TREE_MODIFICATION).ready();
+ Assert.assertNotNull(snapshotBackedWriteTransaction.ready());
+ verify(TRANSACTION_READY_PROTOTYPE).transactionReady(any(), any(), same(thrown));
+ }
+
@Test(expected = IllegalArgumentException.class)
public void writeWithException() throws Exception {
doThrow(TestException.class).when(DATA_TREE_MODIFICATION).write(any(), any());