From db54f9d1bbb9ca06985d768f37b34d2ae992dfcb Mon Sep 17 00:00:00 2001 From: Stephen Kitt Date: Mon, 10 Sep 2018 18:31:07 +0200 Subject: [PATCH] Implement managed transactions MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Managed transactions ensure that transactions are always closed in the appropriate way: * read-only transactions are closed when they are no longer needed; * read-write and write-only transactions are submitted if the code manipulating them didn’t throw an exception, cancelled otherwise. Managed transactions are datastore-specific and enforce either configuration or operational reads and writes. Two providers are included: a basic transaction manager which provides transactions, ensures they are closed, without any extras, and a retrying transaction manager which will retry if a retriable error occurs (i.e. an OptimisticLockFailedException when writing, or a ReadFailedException when reading). This is copied from Genius where it’s used extensively; it’s also used in NetVirt. Change-Id: I391ca4ca50d1b1177d827e33c0d11e98378e1fb4 Signed-off-by: Stephen Kitt --- binding/mdsal-binding-util-tests/pom.xml | 60 ++++ .../ManagedNewTransactionRunnerImplTest.java | 278 ++++++++++++++++++ ...tryingManagedNewTransactionRunnerTest.java | 94 ++++++ .../binding/util/TransactionAdapterTest.java | 143 +++++++++ binding/mdsal-binding-util/pom.xml | 12 +- .../mdsal/binding/util/CheckedConsumer.java | 27 ++ .../mdsal/binding/util/CheckedFunction.java | 20 ++ .../mdsal/binding/util/Datastore.java | 65 ++++ .../util/InterruptibleCheckedConsumer.java | 25 ++ .../util/InterruptibleCheckedFunction.java | 20 ++ .../util/ManagedNewTransactionRunner.java | 46 +++ .../util/ManagedNewTransactionRunnerImpl.java | 101 +++++++ .../binding/util/ManagedTransactionChain.java | 15 + .../util/ManagedTransactionChainImpl.java | 19 ++ .../util/ManagedTransactionFactory.java | 197 +++++++++++++ .../util/ManagedTransactionFactoryImpl.java | 139 +++++++++ .../RetryingManagedNewTransactionRunner.java | 80 +++++ ...tryingManagedNewTransactionRunnerImpl.java | 220 ++++++++++++++ .../binding/util/TransactionAdapter.java | 159 ++++++++++ .../binding/util/TypedReadTransaction.java | 37 +++ .../util/TypedReadTransactionImpl.java | 39 +++ .../util/TypedReadWriteTransaction.java | 21 ++ .../util/TypedReadWriteTransactionImpl.java | 36 +++ .../mdsal/binding/util/TypedTransaction.java | 23 ++ .../binding/util/TypedWriteTransaction.java | 80 +++++ .../util/TypedWriteTransactionImpl.java | 60 ++++ .../WriteTrackingReadWriteTransaction.java | 64 ++++ .../util/WriteTrackingTransaction.java | 15 + ...TrackingTypedReadWriteTransactionImpl.java | 62 ++++ ...riteTrackingTypedWriteTransactionImpl.java | 62 ++++ .../util/WriteTrackingWriteTransaction.java | 63 ++++ .../mdsal/binding/util/package-info.java | 9 + .../mdsal/binding/util/DatastoreTest.java | 39 +++ binding/pom.xml | 1 + 34 files changed, 2330 insertions(+), 1 deletion(-) create mode 100644 binding/mdsal-binding-util-tests/pom.xml create mode 100644 binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java create mode 100644 binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java create mode 100644 binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java create mode 100644 binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java create mode 100644 binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java diff --git a/binding/mdsal-binding-util-tests/pom.xml b/binding/mdsal-binding-util-tests/pom.xml new file mode 100644 index 0000000000..bf694496c6 --- /dev/null +++ b/binding/mdsal-binding-util-tests/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + + org.opendaylight.odlparent + bundle-parent + 4.0.2 + + + org.opendaylight.mdsal + mdsal-binding-util-tests + 1.0.0-SNAPSHOT + + + + + org.opendaylight.mdsal + mdsal-artifacts + 3.0.0-SNAPSHOT + pom + import + + + + + + + com.google.truth + truth + test + + + com.google.truth.extensions + truth-java8-extension + + + org.opendaylight.mdsal + mdsal-binding-test-model + + + org.opendaylight.mdsal + mdsal-binding-test-utils + + + org.opendaylight.mdsal + mdsal-binding-dom-adapter + test-jar + + + \ No newline at end of file diff --git a/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java new file mode 100644 index 0000000000..d5bb9a2a4b --- /dev/null +++ b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java @@ -0,0 +1,278 @@ +/* + * 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.util; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.TOP_FOO_KEY; +import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.path; +import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.topLevelList; +import static org.opendaylight.mdsal.binding.util.Datastore.OPERATIONAL; + +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest; +import org.opendaylight.mdsal.binding.testutils.DataBrokerFailuresImpl; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.common.api.OptimisticLockFailedException; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Test for {@link ManagedNewTransactionRunnerImpl}. + * + * @author Michael Vorburger.ch + * @author Stephen Kitt + */ +public class ManagedNewTransactionRunnerImplTest extends AbstractConcurrentDataBrokerTest { + + static final InstanceIdentifier TEST_PATH = path(TOP_FOO_KEY); + + DataBrokerFailuresImpl testableDataBroker; + ManagedNewTransactionRunner managedNewTransactionRunner; + + public ManagedNewTransactionRunnerImplTest() { + super(true); + } + + protected ManagedNewTransactionRunner createManagedNewTransactionRunnerToTest(DataBroker dataBroker) { + return new ManagedNewTransactionRunnerImpl(dataBroker); + } + + @Before + public void beforeTest() throws Exception { + setup(); + testableDataBroker = new DataBrokerFailuresImpl(getDataBroker()); + managedNewTransactionRunner = createManagedNewTransactionRunnerToTest(testableDataBroker); + } + + @Test + public void testApplyWithNewReadTransactionAndCloseEmptySuccessfully() { + assertEquals(Long.valueOf(1), + managedNewTransactionRunner.applyWithNewReadOnlyTransactionAndClose(OPERATIONAL, tx -> 1L)); + } + + @Test + public void testCallWithNewReadTransactionAndCloseEmptySuccessfully() { + managedNewTransactionRunner.callWithNewReadOnlyTransactionAndClose(OPERATIONAL, tx -> { }); + } + + @Test + public void testCallWithNewTypedWriteOnlyTransactionAndSubmitEmptySuccessfully() throws Exception { + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeTx -> { }).get(); + } + + @Test + public void testCallWithNewTypedReadWriteTransactionAndSubmitEmptySuccessfully() throws Exception { + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, tx -> { }).get(); + } + + @Test + public void testApplyWithNewReadWriteTransactionAndSubmitEmptySuccessfully() throws Exception { + assertEquals(1, + (long) managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + tx -> 1).get()); + } + + @Test + public void testCallWithNewTypedWriteOnlyTransactionAndSubmitPutSuccessfully() throws Exception { + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, data)).get(); + assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testCallWithNewTypedReadWriteTransactionAndSubmitPutSuccessfully() throws Exception { + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + tx -> tx.put(TEST_PATH, data)).get(); + assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testApplyWithNewReadWriteTransactionAndSubmitPutSuccessfully() throws Exception { + TopLevelList data = newTestDataObject(); + assertEquals(1, (long) managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit( + OPERATIONAL, tx -> { + tx.put(TEST_PATH, data); + return 1; + }).get()); + assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testCallWithNewReadTransactionAndCloseReadSuccessfully() throws Exception { + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + tx -> tx.put(TEST_PATH, data)).get(); + assertEquals(data, managedNewTransactionRunner.applyWithNewReadOnlyTransactionAndClose(OPERATIONAL, + tx -> tx.read(TEST_PATH)).get().get()); + } + + TopLevelList newTestDataObject() { + TreeComplexUsesAugment fooAugment = new TreeComplexUsesAugmentBuilder() + .setContainerWithUses(new ContainerWithUsesBuilder().setLeafFromGrouping("foo").build()).build(); + return topLevelList(TOP_FOO_KEY, fooAugment); + } + + @Test + public void testCallWithNewTypedWriteOnlyTransactionAndSubmitPutButLaterException() throws Exception { + try { + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeTx -> { + writeTx.put(TEST_PATH, newTestDataObject()); + // We now throw an arbitrary kind of checked (not unchecked!) exception here + throw new IOException("something didn't quite go as expected..."); + }).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof IOException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testCallWithNewTypedReadWriteTransactionAndSubmitPutButLaterException() throws Exception { + try { + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, writeTx -> { + writeTx.put(TEST_PATH, newTestDataObject()); + // We now throw an arbitrary kind of checked (not unchecked!) exception here + throw new IOException("something didn't quite go as expected..."); + }).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof IOException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testApplyWithNewReadWriteTransactionAndSubmitPutButLaterException() throws Exception { + try { + managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> { + writeTx.put(TEST_PATH, newTestDataObject()); + // We now throw an arbitrary kind of checked (not unchecked!) exception here + throw new IOException("something didn't quite go as expected..."); + }).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof IOException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testCallWithNewTypedWriteOnlyTransactionCommitFailedException() throws Exception { + try { + testableDataBroker.failCommits(new TransactionCommitFailedException("bada boum bam!")); + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof TransactionCommitFailedException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testCallWithNewTypedReadWriteTransactionCommitFailedException() throws Exception { + try { + testableDataBroker.failCommits(new TransactionCommitFailedException("bada boum bam!")); + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof TransactionCommitFailedException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testApplyWithNewReadWriteTransactionCommitFailedException() throws Exception { + try { + testableDataBroker.failCommits(new TransactionCommitFailedException("bada boum bam!")); + managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> { + writeTx.put(TEST_PATH, newTestDataObject()); + return 1; + }).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof TransactionCommitFailedException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testCallWithNewTypedWriteOnlyTransactionOptimisticLockFailedException() throws Exception { + try { + testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!")); + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof OptimisticLockFailedException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testCallWithNewTypedReadWriteTransactionOptimisticLockFailedException() throws Exception { + try { + testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!")); + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof OptimisticLockFailedException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testApplyWithNewReadWriteTransactionOptimisticLockFailedException() throws Exception { + try { + testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!")); + managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> { + writeTx.put(TEST_PATH, newTestDataObject()); + return 1; + }).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertThat(e.getCause() instanceof OptimisticLockFailedException).isTrue(); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + private Optional syncReadOptional(LogicalDatastoreType datastoreType, + InstanceIdentifier path) throws ExecutionException, InterruptedException { + try (ReadTransaction tx = getDataBroker().newReadOnlyTransaction()) { + return tx.read(datastoreType, path).get(); + } + } + + T syncRead(LogicalDatastoreType datastoreType, InstanceIdentifier path) + throws ExecutionException, InterruptedException { + return syncReadOptional(datastoreType, path).get(); + } +} diff --git a/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java new file mode 100644 index 0000000000..97d1e6afe3 --- /dev/null +++ b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java @@ -0,0 +1,94 @@ +/* + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.opendaylight.mdsal.binding.util.Datastore.OPERATIONAL; + +import org.junit.Assert; +import org.junit.Test; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.common.api.OptimisticLockFailedException; +import org.opendaylight.mdsal.common.api.ReadFailedException; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList; + +/** + * Test for {@link RetryingManagedNewTransactionRunner}. + * Note that this test (intentionally) extends the {@link ManagedNewTransactionRunnerImplTest}. + * + * @author Michael Vorburger.ch + */ +public class RetryingManagedNewTransactionRunnerTest extends ManagedNewTransactionRunnerImplTest { + + @Override + protected ManagedNewTransactionRunner createManagedNewTransactionRunnerToTest(DataBroker dataBroker) { + return new RetryingManagedNewTransactionRunner(dataBroker); + } + + @Override + public void testCallWithNewTypedWriteOnlyTransactionOptimisticLockFailedException() throws Exception { + // contrary to the super() test implementation for (just) ManagedNewTransactionRunnerImpl, in the parent class + // here we expect the x2 OptimisticLockFailedException to be retried, and then eventually succeed: + testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!")); + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, data)).get(); + Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Override + public void testCallWithNewTypedReadWriteTransactionOptimisticLockFailedException() throws Exception { + // contrary to the super() test implementation for (just) ManagedNewTransactionRunnerImpl, in the parent class + // here we expect the x2 OptimisticLockFailedException to be retried, and then eventually succeed: + testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!")); + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> writeTx.put(TEST_PATH, data)).get(); + Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Override + public void testApplyWithNewReadWriteTransactionOptimisticLockFailedException() throws Exception { + // contrary to the super() test implementation for (just) ManagedNewTransactionRunnerImpl, in the parent class + // here we expect the x2 OptimisticLockFailedException to be retried, and then eventually succeed: + testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!")); + TopLevelList data = newTestDataObject(); + assertEquals(1, (long) managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit( + OPERATIONAL, writeTx -> { + writeTx.put(TEST_PATH, data); + return 1; + }).get()); + Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testCallWithNewTypedReadWriteTransactionReadFailedException() throws Exception { + testableDataBroker.failReads(2, new ReadFailedException("bada boum bam!")); + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + tx -> { + tx.put(TEST_PATH, data); + Assert.assertEquals(data, tx.read(TEST_PATH).get().get()); + }).get(); + Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testApplyWithNewReadWriteTransactionReadFailedException() throws Exception { + testableDataBroker.failReads(2, new ReadFailedException("bada boum bam!")); + TopLevelList data = newTestDataObject(); + Assert.assertEquals(data, managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit( + OPERATIONAL, + tx -> { + tx.put(TEST_PATH, data); + return tx.read(TEST_PATH).get().get(); + }).get()); + Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } +} diff --git a/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java new file mode 100644 index 0000000000..4f7987667f --- /dev/null +++ b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java @@ -0,0 +1,143 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.TOP_FOO_KEY; +import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.path; +import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.topLevelList; +import static org.opendaylight.mdsal.binding.util.Datastore.OPERATIONAL; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest; +import org.opendaylight.mdsal.binding.testutils.DataBrokerFailuresImpl; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Test for {@link TransactionAdapter}. + */ +// This is a test for a deprecated class +@SuppressWarnings("deprecation") +public class TransactionAdapterTest extends AbstractConcurrentDataBrokerTest { + + private static final InstanceIdentifier TEST_PATH = path(TOP_FOO_KEY); + + private ManagedNewTransactionRunner managedNewTransactionRunner; + private DataBrokerFailuresImpl testableDataBroker; + + private ManagedNewTransactionRunner createManagedNewTransactionRunnerToTest(DataBroker dataBroker) { + return new ManagedNewTransactionRunnerImpl(dataBroker); + } + + @Before + public void beforeTest() throws Exception { + setup(); + testableDataBroker = new DataBrokerFailuresImpl(getDataBroker()); + managedNewTransactionRunner = createManagedNewTransactionRunnerToTest(testableDataBroker); + } + + @Test + public void testAdaptedWriteTransactionPutsSuccessfully() throws Exception { + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + writeTx -> TransactionAdapter.toWriteTransaction(writeTx).put(LogicalDatastoreType.OPERATIONAL, + TEST_PATH, data)).get(); + assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testAdaptedReadWriteTransactionPutsSuccessfully() throws Exception { + TopLevelList data = newTestDataObject(); + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> TransactionAdapter.toReadWriteTransaction(writeTx).put(LogicalDatastoreType.OPERATIONAL, + TEST_PATH, data)).get(); + assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH)); + } + + @Test + public void testAdaptedWriteTransactionFailsOnInvalidDatastore() throws Exception { + try { + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + writeTx -> TransactionAdapter.toWriteTransaction(writeTx).put(LogicalDatastoreType.CONFIGURATION, TEST_PATH, + newTestDataObject())).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof IllegalArgumentException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test + public void testAdaptedReadWriteTransactionFailsOnInvalidDatastore() throws Exception { + try { + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + writeTx -> TransactionAdapter.toReadWriteTransaction(writeTx).put(LogicalDatastoreType.CONFIGURATION, + TEST_PATH, newTestDataObject())).get(); + fail("This should have led to an ExecutionException!"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof IllegalArgumentException); + } + assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty(); + } + + @Test(expected = ExecutionException.class) + public void testAdaptedWriteTransactionCannotCommit() throws Exception { + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + tx -> TransactionAdapter.toWriteTransaction(tx).commit()).get(); + } + + @Test(expected = ExecutionException.class) + public void testAdaptedReadWriteTransactionCannotCommit() throws Exception { + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + tx -> TransactionAdapter.toReadWriteTransaction(tx).commit()).get(); + } + + @Test(expected = ExecutionException.class) + public void testAdaptedWriteTransactionCannotCancel() throws Exception { + managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, + tx -> TransactionAdapter.toWriteTransaction(tx).cancel()).get(); + } + + @Test(expected = ExecutionException.class) + public void testAdaptedReadWriteTransactionCannotCancel() throws Exception { + managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, + tx -> TransactionAdapter.toReadWriteTransaction(tx).cancel()).get(); + } + + private TopLevelList newTestDataObject() { + TreeComplexUsesAugment fooAugment = new TreeComplexUsesAugmentBuilder() + .setContainerWithUses(new ContainerWithUsesBuilder().setLeafFromGrouping("foo").build()).build(); + return topLevelList(TOP_FOO_KEY, fooAugment); + } + + private Optional syncReadOptional(LogicalDatastoreType datastoreType, + InstanceIdentifier path) throws ExecutionException, InterruptedException { + try (ReadTransaction tx = getDataBroker().newReadOnlyTransaction()) { + return tx.read(datastoreType, path).get(); + } + } + + private T syncRead(LogicalDatastoreType datastoreType, InstanceIdentifier path) + throws ExecutionException, InterruptedException { + return syncReadOptional(datastoreType, path).get(); + } +} diff --git a/binding/mdsal-binding-util/pom.xml b/binding/mdsal-binding-util/pom.xml index 67f4727071..23c1baf7e8 100644 --- a/binding/mdsal-binding-util/pom.xml +++ b/binding/mdsal-binding-util/pom.xml @@ -40,7 +40,17 @@ org.opendaylight.mdsal - mdsal-binding-api + mdsal-binding-spi + + + javax.inject + javax.inject + true + + + com.google.truth + truth + test diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java new file mode 100644 index 0000000000..eb6603ab04 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java @@ -0,0 +1,27 @@ +/* + * 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.util; + +import java.util.function.Consumer; + +/** + * {@link Consumer} which can throw a checked exception. + * + * @param the type of the input to the operation + * @param the type of the Exception to the operation + * + * @see Consumer + * + * @author Michael Vorburger.ch + */ +@FunctionalInterface +public interface CheckedConsumer { + + void accept(T input) throws E; + +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java new file mode 100644 index 0000000000..81a74dcd8e --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java @@ -0,0 +1,20 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +/** + * {@link java.util.function.Function} which can throw a checked exception. + * + * @param The type of the input to be processed. + * @param The type of the result to be returned. + * @param The type of the exception which may be thrown. + */ +@FunctionalInterface +public interface CheckedFunction { + R apply(T input) throws E; +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java new file mode 100644 index 0000000000..e581f400fe --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java @@ -0,0 +1,65 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import com.google.common.annotations.Beta; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; + +/** + * Strongly-typed representation of a datastore (configuration or operational). + */ +@Beta +// FIXME Base this on ietf-datastores.yang (RFC 8342) +public abstract class Datastore { + + /** Class representing the configuration datastore. */ + public static final Class CONFIGURATION = Configuration.class; + + /** Class representing the operational datastore. */ + public static final Class OPERATIONAL = Operational.class; + + public static final class Configuration extends Datastore {} + + public static final class Operational extends Datastore {} + + /** + * Returns the logical datastore type corresponding to the given datastore class. + * + * @param datastoreClass The datastore class to convert. + * @return The corresponding logical datastore type. + * @throws IllegalArgumentException if the provided datastore class isn’t handled. + */ + public static LogicalDatastoreType toType(Class datastoreClass) { + if (datastoreClass.equals(Configuration.class)) { + return LogicalDatastoreType.CONFIGURATION; + } else if (Operational.class.equals(datastoreClass)) { + return LogicalDatastoreType.OPERATIONAL; + } else { + throw new IllegalArgumentException("Unknown datastore class " + datastoreClass); + } + } + + /** + * Returns the datastore class corresponding to the given logical datastore type. + * @param datastoreType The logical datastore type to convert. + * @return The corresponding datastore class. + * @throws IllegalArgumentException if the provided logical datastore type isn’t handled. + */ + public static Class toClass(LogicalDatastoreType datastoreType) { + switch (datastoreType) { + case CONFIGURATION: + return CONFIGURATION; + case OPERATIONAL: + return OPERATIONAL; + default: + throw new IllegalArgumentException("Unknown datastore type " + datastoreType); + } + } + + private Datastore() {} +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java new file mode 100644 index 0000000000..7f43a41f10 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java @@ -0,0 +1,25 @@ +/* + * 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.util; + +import java.util.function.Consumer; + +/** + * {@link Consumer} which can throw a checked exception and be interrupted. + * + * @param the type of the input to the operation + * @param the type of the Exception to the operation + * + * @see Consumer + */ +@FunctionalInterface +public interface InterruptibleCheckedConsumer { + + void accept(T input) throws E, InterruptedException; + +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java new file mode 100644 index 0000000000..acf480e00b --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java @@ -0,0 +1,20 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +/** + * {@link java.util.function.Function} which can throw a checked exception and be interrupted. + * + * @param The type of the input to be processed. + * @param The type of the result to be returned. + * @param The type of the exception which may be thrown. + */ +@FunctionalInterface +public interface InterruptibleCheckedFunction { + R apply(T input) throws E, InterruptedException; +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java new file mode 100644 index 0000000000..f5aeaef232 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java @@ -0,0 +1,46 @@ +/* + * 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.util; + +import com.google.common.annotations.Beta; +import java.util.function.Function; +import org.opendaylight.mdsal.binding.api.DataBroker; + +/** + * Managed transactions utility to simplify handling of new transactions and ensure they are always closed. + * Implementation in {@link ManagedNewTransactionRunnerImpl}, alternative implementation of this API with optional + * retries is {@link RetryingManagedNewTransactionRunner}. + * + *

This should typically be used (only) directly in code which really must be creating its own new transactions, + * such as RPC entry points, or background jobs. Other lower level code "behind" such entry points should + * just get handed over the transaction provided by this API. + */ +@Beta +public interface ManagedNewTransactionRunner extends ManagedTransactionFactory { + + /** + * Invokes a function with a new {@link ManagedTransactionChain}, which is a wrapper around standard transaction + * chains providing managed semantics. The transaction chain will be closed when the function returns. + * + *

This is an asynchronous API, like {@link DataBroker}'s own; when this method returns, the transactions in + * the chain may well still be ongoing in the background, or pending. It is up to the consumer and + * caller to agree on how failure will be handled; for example, the return type can include the futures + * corresponding to the transactions in the chain. The implementation uses a default transaction chain listener + * which logs an error if any of the transactions fail. + * + *

The MD-SAL transaction chain semantics are preserved: each transaction in the chain will see the results of + * the previous transactions in the chain, even if they haven't been fully committed yet; and any error will result + * in subsequent transactions in the chain not being submitted. + * + * @param chainConsumer The {@link Function} that will build transactions in the transaction chain. + * @param The type of result returned by the function. + * @return The result of the function call. + */ + R applyWithNewTransactionChainAndClose(Function chainConsumer); + +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java new file mode 100644 index 0000000000..955cf2a4a8 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java @@ -0,0 +1,101 @@ +/* + * 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.util; + +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.util.concurrent.FluentFuture; +import java.util.function.Function; +import javax.annotation.CheckReturnValue; +import javax.inject.Inject; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.Transaction; +import org.opendaylight.mdsal.binding.api.TransactionChain; +import org.opendaylight.mdsal.binding.api.TransactionChainListener; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link ManagedNewTransactionRunner}. This is based on {@link ManagedTransactionFactoryImpl} but + * re-implements operations based on read-write transactions to cancel transactions which don't end up making any + * changes to the datastore. + */ +@Beta +// Do *NOT* mark this as @Singleton, because users choose their implementation +public class ManagedNewTransactionRunnerImpl extends ManagedTransactionFactoryImpl + implements ManagedNewTransactionRunner { + + private static final Logger LOG = LoggerFactory.getLogger(ManagedNewTransactionRunnerImpl.class); + + @Inject + public ManagedNewTransactionRunnerImpl(DataBroker broker) { + // Early check to ensure the error message is understandable for the caller + super(requireNonNull(broker, "broker must not be null")); + } + + // This is overridden to use this class’s commit method + @Override + @CheckReturnValue + public FluentFuture applyWithNewReadWriteTransactionAndSubmit( + Class datastoreType, InterruptibleCheckedFunction, R, E> txFunction) { + return super.applyWithNewTransactionAndSubmit(datastoreType, getTransactionFactory()::newReadWriteTransaction, + WriteTrackingTypedReadWriteTransactionImpl::new, txFunction::apply, this::commit); + } + + @Override + public R applyWithNewTransactionChainAndClose(Function chainConsumer) { + try (TransactionChain realTxChain = getTransactionFactory().createTransactionChain( + new TransactionChainListener() { + @Override + public void onTransactionChainFailed(TransactionChain chain, Transaction transaction, Throwable cause) { + LOG.error("Error handling a transaction chain", cause); + } + + @Override + public void onTransactionChainSuccessful(TransactionChain chain) { + // Nothing to do + } + })) { + return chainConsumer.apply(new ManagedTransactionChainImpl(realTxChain)); + } + } + + // This is overridden to use this class’s commit method + @Override + @CheckReturnValue + public FluentFuture + callWithNewReadWriteTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txConsumer) { + return callWithNewTransactionAndSubmit(datastoreType, getTransactionFactory()::newReadWriteTransaction, + WriteTrackingTypedReadWriteTransactionImpl::new, txConsumer::accept, this::commit); + } + + // This is overridden to use this class’s commit method + @Override + @CheckReturnValue + public FluentFuture callWithNewWriteOnlyTransactionAndSubmit( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer) { + return super.callWithNewTransactionAndSubmit(datastoreType, getTransactionFactory()::newWriteOnlyTransaction, + WriteTrackingTypedWriteTransactionImpl::new, txConsumer::accept, this::commit); + } + + @CheckReturnValue + private FluentFuture commit(WriteTransaction realTx, WriteTrackingTransaction wrappedTx) { + if (wrappedTx.isWritten()) { + // The transaction contains changes, commit it + return realTx.commit(); + } else { + // The transaction only handled reads, cancel it + realTx.cancel(); + return CommitInfo.emptyFluentFuture(); + } + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java new file mode 100644 index 0000000000..d79ea89e4c --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java @@ -0,0 +1,15 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +/** + * Managed transaction chains provide managed semantics around transaction chains, i.e. chains which provide + * transactions which are automatically submitted or cancelled. + */ +public interface ManagedTransactionChain extends ManagedTransactionFactory { +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java new file mode 100644 index 0000000000..f3d976c5a6 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java @@ -0,0 +1,19 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.TransactionChain; + +/** + * Implementation of {@link ManagedTransactionChain}, based on {@link ManagedTransactionFactoryImpl}. + */ +class ManagedTransactionChainImpl extends ManagedTransactionFactoryImpl implements ManagedTransactionChain { + ManagedTransactionChainImpl(TransactionChain realTxChain) { + super(realTxChain); + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java new file mode 100644 index 0000000000..4a27344038 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java @@ -0,0 +1,197 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.Futures; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Future; +import javax.annotation.CheckReturnValue; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.mdsal.binding.api.ReadWriteTransaction; +import org.opendaylight.mdsal.binding.api.WriteTransaction; + +/** + * Managed transaction factories provide managed transactions, i.e. transactions which are automatically + * submitted or cancelled (write) or closed (read). + *

+ * This is a common interface for broker- and chain-based transaction managers, and should not be used directly. + */ +public interface ManagedTransactionFactory { + /** + * Invokes a function with a NEW {@link TypedReadTransaction}, and ensures that that transaction is closed. + * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and + * waste memory. + * + *

The function must not itself attempt to close the transaction. (It can't directly, since + * {@link TypedReadTransaction} doesn't expose a {@code close()} method.) + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txFunction the {@link InterruptibleCheckedFunction} that needs a new read transaction + * @return the result of the function. + * @throws E if an error occurs. + * @throws InterruptedException if the function is interrupted (this is passed through from the provided function). + */ + R applyInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedFunction, R, E> txFunction) + throws E, InterruptedException; + + /** + * Invokes a function with a NEW {@link TypedReadTransaction}, and ensures that that transaction is closed. + * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and + * waste memory. + * + *

The function must not itself attempt to close the transaction. (It can't directly, since + * {@link TypedReadTransaction} doesn't expose a {@code close()} method.) + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txFunction the {@link InterruptibleCheckedFunction} that needs a new read transaction + * @return the result of the function. + * @throws E if an error occurs. + */ + R applyWithNewReadOnlyTransactionAndClose(Class datastoreType, + CheckedFunction, R, E> txFunction) throws E; + + /** + * Invokes a function with a NEW {@link ReadWriteTransaction}, and then submits that transaction and + * returns the Future from that submission, or cancels it if an exception was thrown and returns a failed + * future with that exception. Thus when this method returns, that transaction is guaranteed to have + * been either submitted or cancelled, and will never "leak" and waste memory. + * + *

The function must not itself use + * {@link ReadWriteTransaction#cancel()}, or + * {@link ReadWriteTransaction#commit()} (it will throw an {@link UnsupportedOperationException}). + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + *

This is an asynchronous API, like {@link DataBroker}'s own; + * when returning from this method, the operation of the Transaction may well still be ongoing in the background, + * or pending; + * calling code therefore must handle the returned future, e.g. by passing it onwards (return), + * or by itself adding callback listeners to it using {@link Futures}' methods, or by transforming it into a + * {@link CompletionStage} + * (but better NOT by using the blocking {@link Future#get()} on it). + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txFunction the {@link InterruptibleCheckedFunction} that needs a new read-write transaction + * @return the {@link FluentFuture} returned by {@link ReadWriteTransaction#commit()}, or a failed future with an + * application specific exception (not from submit()) + */ + @CheckReturnValue + + FluentFuture applyWithNewReadWriteTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedFunction, R, E> txFunction); + + /** + * Invokes a function with a NEW {@link ReadTransaction}, and ensures that that transaction is closed. + * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and + * waste memory. + * + *

The function must not itself attempt to close the transaction. (It can't directly, since + * {@link ReadTransaction} doesn't expose a {@code close()} method.) + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txConsumer the {@link InterruptibleCheckedFunction} that needs a new read transaction + * @throws E if an error occurs. + * @throws InterruptedException if the function is interrupted (this is passed through from the provided function). + */ + void callInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer) + throws E, InterruptedException; + + /** + * Invokes a function with a NEW {@link ReadTransaction}, and ensures that that transaction is closed. + * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and + * waste memory. + * + *

The function must not itself attempt to close the transaction. (It can't directly, since + * {@link ReadTransaction} doesn't expose a {@code close()} method.) + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txConsumer the {@link InterruptibleCheckedFunction} that needs a new read transaction + * @throws E if an error occurs. + */ + void callWithNewReadOnlyTransactionAndClose(Class datastoreType, + CheckedConsumer, E> txConsumer) throws E; + + /** + * Invokes a consumer with a NEW {@link ReadWriteTransaction}, and then submits that transaction and + * returns the Future from that submission, or cancels it if an exception was thrown and returns a failed + * future with that exception. Thus when this method returns, that transaction is guaranteed to have + * been either submitted or cancelled, and will never "leak" and waste memory. + * + *

The consumer should not (cannot) itself use + * {@link ReadWriteTransaction#cancel()}, or + * {@link ReadWriteTransaction#commit()} (it will throw an {@link UnsupportedOperationException}). + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + *

This is an asynchronous API, like {@link DataBroker}'s own; + * when returning from this method, the operation of the Transaction may well still be ongoing in the background, + * or pending; + * calling code therefore must handle the returned future, e.g. by passing it onwards (return), + * or by itself adding callback listeners to it using {@link Futures}' methods, or by transforming it into a + * {@link CompletionStage} + * (but better NOT by using the blocking {@link Future#get()} on it). + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txConsumer the {@link InterruptibleCheckedConsumer} that needs a new read-write transaction + * @return the {@link FluentFuture} returned by {@link ReadWriteTransaction#commit()}, or a failed future with an + * application specific exception (not from submit()) + */ + @CheckReturnValue + + FluentFuture callWithNewReadWriteTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txConsumer); + + /** + * Invokes a consumer with a NEW {@link WriteTransaction}, and then submits that transaction and + * returns the Future from that submission, or cancels it if an exception was thrown and returns a failed + * future with that exception. Thus when this method returns, that transaction is guaranteed to have + * been either submitted or cancelled, and will never "leak" and waste memory. + * + *

The consumer should not (cannot) itself use + * {@link WriteTransaction#cancel()}, or + * {@link WriteTransaction#commit()} (it will throw an {@link UnsupportedOperationException}). + * + *

The provided transaction is specific to the given logical datastore type and cannot be used for any + * other. + * + *

This is an asynchronous API, like {@link DataBroker}'s own; + * when returning from this method, the operation of the Transaction may well still be ongoing in the background, + * or pending; + * calling code therefore must handle the returned future, e.g. by passing it onwards (return), + * or by itself adding callback listeners to it using {@link Futures}' methods, or by transforming it into a + * {@link CompletionStage} + * (but better NOT by using the blocking {@link Future#get()} on it). + * + * @param datastoreType the {@link Datastore} type that will be accessed + * @param txConsumer the {@link InterruptibleCheckedConsumer} that needs a new write only transaction + * @return the {@link FluentFuture} returned by {@link WriteTransaction#commit()}, or a failed future with an + * application specific exception (not from submit()) + */ + @CheckReturnValue + + FluentFuture callWithNewWriteOnlyTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txConsumer); +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java new file mode 100644 index 0000000000..299ef158a3 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java @@ -0,0 +1,139 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import static java.util.Objects.requireNonNull; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import javax.annotation.CheckReturnValue; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.mdsal.binding.api.TransactionFactory; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.yangtools.util.concurrent.FluentFutures; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Basic implementation of a {@link ManagedTransactionFactory}. + */ +class ManagedTransactionFactoryImpl implements ManagedTransactionFactory { + private static final Logger LOG = LoggerFactory.getLogger(ManagedTransactionFactoryImpl.class); + + private final T transactionFactory; + + ManagedTransactionFactoryImpl(T transactionFactory) { + this.transactionFactory = requireNonNull(transactionFactory, "transactionFactory must not be null"); + } + + @Override + public R applyInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedFunction, R, E> txFunction) + throws E, InterruptedException { + try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) { + TypedReadTransaction + wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx); + return txFunction.apply(wrappedTx); + } + } + + @Override + public R applyWithNewReadOnlyTransactionAndClose( + Class datastoreType, CheckedFunction, R, E> txFunction) throws E { + try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) { + TypedReadTransaction + wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx); + return txFunction.apply(wrappedTx); + } + } + + @Override + @CheckReturnValue + public + FluentFuture applyWithNewReadWriteTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedFunction, R, E> txFunction) { + return applyWithNewTransactionAndSubmit(datastoreType, transactionFactory::newReadWriteTransaction, + TypedReadWriteTransactionImpl::new, txFunction, (realTx, wrappedTx) -> realTx.commit()); + } + + @Override + public void callInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer) + throws E, InterruptedException { + try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) { + TypedReadTransaction wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx); + txConsumer.accept(wrappedTx); + } + } + + @Override + public void callWithNewReadOnlyTransactionAndClose( + Class datastoreType, CheckedConsumer, E> txConsumer) throws E { + try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) { + TypedReadTransaction wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx); + txConsumer.accept(wrappedTx); + } + } + + @Override + @CheckReturnValue + public + FluentFuture callWithNewReadWriteTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txConsumer) { + return callWithNewTransactionAndSubmit(datastoreType, transactionFactory::newReadWriteTransaction, + TypedReadWriteTransactionImpl::new, txConsumer, (realTx, wrappedTx) -> realTx.commit()); + } + + @Override + @CheckReturnValue + public FluentFuture callWithNewWriteOnlyTransactionAndSubmit( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer) { + return callWithNewTransactionAndSubmit(datastoreType, transactionFactory::newWriteOnlyTransaction, + TypedWriteTransactionImpl::new, txConsumer, (realTx, wrappedTx) -> realTx.commit()); + } + + @CheckReturnValue + protected FluentFuture + callWithNewTransactionAndSubmit( + Class datastoreType, Supplier txSupplier, BiFunction, T, W> txWrapper, + InterruptibleCheckedConsumer txConsumer, BiFunction> txSubmitter) { + return applyWithNewTransactionAndSubmit(datastoreType, txSupplier, txWrapper, tx -> { + txConsumer.accept(tx); + return null; + }, txSubmitter); + } + + @CheckReturnValue + @SuppressWarnings("checkstyle:IllegalCatch") + protected FluentFuture + applyWithNewTransactionAndSubmit( + Class datastoreType, Supplier txSupplier, BiFunction, T, W> txWrapper, + InterruptibleCheckedFunction txFunction, BiFunction> txSubmitter) { + T realTx = txSupplier.get(); + W wrappedTx = txWrapper.apply(datastoreType, realTx); + R result; + try { + // We must store the result before submitting the transaction; if we inline the next line in the + // transform lambda, that's not guaranteed + result = txFunction.apply(wrappedTx); + } catch (Exception e) { + // catch Exception for both the thrown by accept() as well as any RuntimeException + if (!realTx.cancel()) { + LOG.error("Transaction.cancel() returned false - this should never happen (here)"); + } + return FluentFutures.immediateFailedFluentFuture(e); + } + return txSubmitter.apply(realTx, wrappedTx).transform(v -> result, MoreExecutors.directExecutor()); + } + + protected T getTransactionFactory() { + return transactionFactory; + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java new file mode 100644 index 0000000000..30dc642fb7 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java @@ -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.util; + +import com.google.common.annotations.Beta; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.Executor; +import javax.inject.Inject; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.common.api.OptimisticLockFailedException; + +/** + * Implementation of {@link ManagedNewTransactionRunner} with automatic transparent retries. + * + *

Details about the threading model used by this class

+ * + *

This class runs the first attempt to call the delegated {@link ManagedNewTransactionRunner}, + * which typically is a {@link ManagedNewTransactionRunnerImpl} which safely invokes {@link WriteTransaction#commit()}, + * in the using application's thread (like a {@link MoreExecutors#directExecutor()} would, if this were an + * {@link Executor}, which it's not). + * + *

Any retry attempts required, if that submit() (eventually) fails with an + * {@link OptimisticLockFailedException}, are run in the calling thread of that eventual future completion by a + * {@link MoreExecutors#directExecutor()} implicit in the constructor which does not require you to specify an + * explicit Executor argument. Normally that will be an internal thread from the respective DataBroker implementation, + * not your application's thread anymore, because that meanwhile could well be off doing something else! Normally, + * that is not a problem, because retries "should" be relatively uncommon, and (re)issuing some DataBroker + * put() or delete() and re-submit() should be fast. + * + *

If this default is not suitable (e.g. for particularly slow try/retry code), then you can specify + * another {@link Executor} to be used for the retries by using the alternative constructor. + * + * @author Michael Vorburger.ch & Stephen Kitt + */ +@Beta +// Do *NOT* mark this as @Singleton, because users choose Impl; and as long as this in API, because of https://wiki.opendaylight.org/view/BestPractices/DI_Guidelines#Nota_Bene +public class RetryingManagedNewTransactionRunner extends RetryingManagedNewTransactionRunnerImpl { + + /** + * Constructor. + * Please see the class level documentation above for more details about the threading model used. + * This uses the default of 3 retries, which is typically suitable. + * + * @param dataBroker the {@link DataBroker} from which transactions are obtained + * @throws NullPointerException if {@code dataBroker} is {@code null}. + */ + @Inject + public RetryingManagedNewTransactionRunner(DataBroker dataBroker) { + super(new ManagedNewTransactionRunnerImpl(dataBroker)); + } + + /** + * Constructor. + * Please see the class level documentation above for more details about the threading model used. + * + * @param dataBroker the {@link DataBroker} from which transactions are obtained + * @param maxRetries the maximum number of retry attempts + */ + public RetryingManagedNewTransactionRunner(DataBroker dataBroker, int maxRetries) { + super(new ManagedNewTransactionRunnerImpl(dataBroker), maxRetries); + } + + /** + * Constructor. + * Please see the class level documentation above for more details about the threading model used. + * + * @param dataBroker the {@link DataBroker} from which transactions are obtained + * @param executor the {@link Executor} to asynchronously run any retry attempts in + * @param maxRetries the maximum number of retry attempts + */ + public RetryingManagedNewTransactionRunner(DataBroker dataBroker, Executor executor, int maxRetries) { + super(new ManagedNewTransactionRunnerImpl(dataBroker), executor, maxRetries); + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java new file mode 100644 index 0000000000..412bcbea68 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java @@ -0,0 +1,220 @@ +/* + * 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.util; + +import static java.util.Objects.requireNonNull; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.function.Function; +import org.opendaylight.mdsal.common.api.OptimisticLockFailedException; +import org.opendaylight.mdsal.common.api.ReadFailedException; + +/** + * Implementation of {@link ManagedNewTransactionRunner} with automatic transparent retries on transaction failure + * ({@link OptimisticLockFailedException} on write transactions and {@link ReadFailedException} on read transactions + * will cause the operation constructing the transaction to be re-run). + * This is a package local private internal class; end-users use the {@link RetryingManagedNewTransactionRunner}. + * @see RetryingManagedNewTransactionRunner + * + * @author Michael Vorburger.ch & Stephen Kitt, with input from Tom Pantelis re. catchingAsync & direct Executor + */ +// intentionally package local +class RetryingManagedNewTransactionRunnerImpl implements ManagedNewTransactionRunner { + + // NB: The RetryingManagedNewTransactionRunnerTest is in mdsalutil-testutils's src/test, not this project's + + private static final int DEFAULT_RETRIES = 3; // duplicated in SingleTransactionDataBroker + + private final int maxRetries; + + private final ManagedNewTransactionRunner delegate; + + private final Executor executor; + + RetryingManagedNewTransactionRunnerImpl(ManagedNewTransactionRunner delegate) { + this(delegate, MoreExecutors.directExecutor(), DEFAULT_RETRIES); + } + + RetryingManagedNewTransactionRunnerImpl(ManagedNewTransactionRunner delegate, int maxRetries) { + this(delegate, MoreExecutors.directExecutor(), maxRetries); + } + + RetryingManagedNewTransactionRunnerImpl(ManagedNewTransactionRunner delegate, Executor executor, int maxRetries) { + this.delegate = requireNonNull(delegate, "delegate must not be null"); + this.executor = requireNonNull(executor, "executor must not be null"); + this.maxRetries = maxRetries; + } + + @Override + public R applyInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedFunction, R, E> txFunction) + throws E, InterruptedException { + return applyInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, maxRetries); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + private R applyInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedFunction, R, E> txFunction, + int tries) throws E, InterruptedException { + try { + return delegate.applyInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction); + } catch (Exception e) { + if (isRetriableException(e) && tries - 1 > 0) { + return applyInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, tries - 1); + } else { + throw e; + } + } + } + + @Override + public R applyWithNewReadOnlyTransactionAndClose( + Class datastoreType, CheckedFunction, R, E> txFunction) throws E { + return applyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, maxRetries); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + private R applyWithNewReadOnlyTransactionAndClose( + Class datastoreType, CheckedFunction, R, E> txFunction, int tries) throws E { + try { + return delegate.applyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction); + } catch (Exception e) { + if (isRetriableException(e) && tries - 1 > 0) { + return applyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, tries - 1); + } else { + throw e; + } + } + } + + @Override + public FluentFuture applyWithNewReadWriteTransactionAndSubmit( + Class datastoreType, InterruptibleCheckedFunction, R, E> txFunction) { + return applyWithNewReadWriteTransactionAndSubmit(datastoreType, txFunction, maxRetries); + } + + private FluentFuture applyWithNewReadWriteTransactionAndSubmit( + Class datastoreType, InterruptibleCheckedFunction, R, E> txRunner, + int tries) { + FluentFuture future = requireNonNull( + delegate.applyWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner), + "delegate.callWithNewReadWriteTransactionAndSubmit() == null"); + return future.catchingAsync(Exception.class, exception -> { + if (isRetriableException(exception) && tries - 1 > 0) { + return applyWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner, tries - 1); + } else { + throw exception; + } + }, executor); + } + + @Override + public R applyWithNewTransactionChainAndClose(Function chainConsumer) { + throw new UnsupportedOperationException("The retrying transaction manager doesn't support transaction chains"); + } + + @Override + public void callInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer) + throws E, InterruptedException { + callInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, maxRetries); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + private void callInterruptiblyWithNewReadOnlyTransactionAndClose( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer, int tries) + throws E, InterruptedException { + try { + delegate.callInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer); + } catch (Exception e) { + if (isRetriableException(e) && tries - 1 > 0) { + callInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, tries - 1); + } else { + throw e; + } + } + } + + @Override + public void callWithNewReadOnlyTransactionAndClose( + Class datastoreType, CheckedConsumer, E> txConsumer) throws E { + callWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, maxRetries); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + private void callWithNewReadOnlyTransactionAndClose( + Class datastoreType, CheckedConsumer, E> txConsumer, int tries) throws E { + try { + delegate.callWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer); + } catch (Exception e) { + if (isRetriableException(e) && tries - 1 > 0) { + callWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, tries - 1); + } else { + throw e; + } + } + } + + @Override + public FluentFuture + callWithNewReadWriteTransactionAndSubmit( + Class datastoreType, InterruptibleCheckedConsumer, E> txConsumer) { + return callWithNewReadWriteTransactionAndSubmit(datastoreType, txConsumer, maxRetries); + } + + private FluentFuture + callWithNewReadWriteTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txRunner, int tries) { + + return (FluentFuture) requireNonNull( + delegate.callWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner), + "delegate.callWithNewWriteOnlyTransactionAndSubmit() == null") + .catchingAsync(Exception.class, exception -> { + // as per AsyncWriteTransaction.submit()'s JavaDoc re. retries + if (isRetriableException(exception) && tries - 1 > 0) { + return callWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner, tries - 1); + } else { + // out of retries, so propagate the exception + throw exception; + } + }, executor); + } + + @Override + public FluentFuture + callWithNewWriteOnlyTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txConsumer) { + return callWithNewWriteOnlyTransactionAndSubmit(datastoreType, txConsumer, maxRetries); + } + + private FluentFuture + callWithNewWriteOnlyTransactionAndSubmit(Class datastoreType, + InterruptibleCheckedConsumer, E> txRunner, int tries) { + + return (FluentFuture) requireNonNull( + delegate.callWithNewWriteOnlyTransactionAndSubmit(datastoreType, txRunner), + "delegate.callWithNewWriteOnlyTransactionAndSubmit() == null") + .catchingAsync(OptimisticLockFailedException.class, optimisticLockFailedException -> { + // as per AsyncWriteTransaction.submit()'s JavaDoc re. retries + if (tries - 1 > 0) { + return callWithNewWriteOnlyTransactionAndSubmit(datastoreType, txRunner, tries - 1); + } else { + // out of retries, so propagate the OptimisticLockFailedException + throw optimisticLockFailedException; + } + }, executor); + } + + private boolean isRetriableException(Throwable throwable) { + return throwable instanceof OptimisticLockFailedException || throwable instanceof ReadFailedException || ( + throwable instanceof ExecutionException && isRetriableException(throwable.getCause())); + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java new file mode 100644 index 0000000000..35110027b5 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java @@ -0,0 +1,159 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ForwardingObject; +import com.google.common.util.concurrent.FluentFuture; +import java.util.Optional; +import org.eclipse.jdt.annotation.NonNull; +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.LogicalDatastoreType; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Adapter allowing managed, datastore-constrained transactions to be used with methods expecting + * generic {@link DataBroker} transactions. + * + *

The adapted transactions maintain the following constraints: they cannot be cancelled or + * submitted (only the transaction manager can do this), and they cannot access a logical datastore + * other than the one they were created for. + * + * @deprecated This is only intended for temporary use during complex migrations to managed transactions. + */ +@Deprecated +public final class TransactionAdapter { + private TransactionAdapter() { + } + + /** + * Adapts the given datastore-constrained read-write transaction to a generic read-write transaction. + * + * @param datastoreTx The transaction to adapt. + * @return The adapted transaction. + * @throws NullPointerException if the provided transaction is {@code null}. + */ + public static ReadWriteTransaction toReadWriteTransaction( + TypedReadWriteTransaction datastoreTx) { + if (datastoreTx instanceof TypedReadWriteTransactionImpl) { + TypedReadWriteTransactionImpl nonSubmitCancelableDatastoreReadWriteTransaction = + (TypedReadWriteTransactionImpl) datastoreTx; + return new ReadWriteTransactionAdapter(nonSubmitCancelableDatastoreReadWriteTransaction.datastoreType, + nonSubmitCancelableDatastoreReadWriteTransaction); + } + throw new IllegalArgumentException( + "Unsupported TypedWriteTransaction implementation " + datastoreTx.getClass()); + } + + /** + * Adapts the given datastore-constrained write transaction to a generic write transaction. Note that this + * can be used to adapt a read-write transaction to a write transaction. + * + * @param datastoreTx The transaction to adapt. + * @return The adapted transaction. + */ + public static WriteTransaction toWriteTransaction(TypedWriteTransaction datastoreTx) { + if (datastoreTx instanceof TypedWriteTransactionImpl) { + TypedWriteTransactionImpl nonSubmitCancelableDatastoreWriteTransaction = + (TypedWriteTransactionImpl) datastoreTx; + return new WriteTransactionAdapter(nonSubmitCancelableDatastoreWriteTransaction.datastoreType, + nonSubmitCancelableDatastoreWriteTransaction); + } + throw new IllegalArgumentException( + "Unsupported TypedWriteTransaction implementation " + datastoreTx.getClass()); + } + + // We want to subclass this class, even though it has a private constructor + @SuppressWarnings("FinalClass") + private static class WriteTransactionAdapter> + extends ForwardingObject implements WriteTransaction { + private final LogicalDatastoreType datastoreType; + private final T delegate; + + private WriteTransactionAdapter(LogicalDatastoreType datastoreType, T delegate) { + this.datastoreType = datastoreType; + this.delegate = delegate; + } + + @Override + public void put(LogicalDatastoreType store, InstanceIdentifier path, T data) { + checkStore(store); + delegate.put(path, data); + } + + @Override + public void put(LogicalDatastoreType store, InstanceIdentifier path, T data, + boolean createMissingParents) { + checkStore(store); + delegate.put(path, data, createMissingParents); + } + + @Override + public void merge(LogicalDatastoreType store, InstanceIdentifier path, T data) { + checkStore(store); + delegate.merge(path, data); + } + + @Override + public void merge(LogicalDatastoreType store, InstanceIdentifier path, T data, + boolean createMissingParents) { + checkStore(store); + delegate.merge(path, data, createMissingParents); + } + + @Override + public boolean cancel() { + throw new UnsupportedOperationException("Managed transactions must not be cancelled"); + } + + @Override + public void delete(LogicalDatastoreType store, InstanceIdentifier path) { + checkStore(store); + delegate.delete(path); + } + + @Override + public @NonNull FluentFuture commit() { + throw new UnsupportedOperationException("Managed transactions must not be committed"); + } + + void checkStore(LogicalDatastoreType store) { + checkArgument(datastoreType.equals(store), "Invalid datastore %s used instead of %s", store, datastoreType); + } + + @Override + public Object getIdentifier() { + return delegate.getIdentifier(); + } + + @Override + protected T delegate() { + return delegate; + } + } + + private static final class ReadWriteTransactionAdapter + extends WriteTransactionAdapter> implements ReadWriteTransaction { + private ReadWriteTransactionAdapter(LogicalDatastoreType datastoreType, TypedReadWriteTransaction delegate) { + super(datastoreType, delegate); + } + + @Override + public FluentFuture> read(LogicalDatastoreType store, + InstanceIdentifier path) { + checkStore(store); + return delegate().read(path); + } + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java new file mode 100644 index 0000000000..f42e69203d --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import com.google.common.util.concurrent.FluentFuture; +import java.util.Optional; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.mdsal.binding.api.Transaction; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Read transaction which is specific to a single logical datastore (configuration or operational). Designed for use + * with {@link ManagedNewTransactionRunner} (it doesn’t support explicit cancel or commit operations). + * + * @see ReadTransaction + * + * @param The logical datastore handled by the transaction. + */ +public interface TypedReadTransaction extends Transaction { + /** + * Reads an object from the given path. + * + * @see ReadTransaction#read(LogicalDatastoreType, InstanceIdentifier) + * + * @param path The path to read from. + * @param The type of the expected object. + * @return A future providing access to the result of the read, when it’s available, or any error encountered. + */ + FluentFuture> read(InstanceIdentifier path); +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java new file mode 100644 index 0000000000..c06e240815 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import com.google.common.util.concurrent.FluentFuture; +import java.util.Optional; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Implementation of {@link TypedReadTransaction}. + * + * @param The datastore which the transaction targets. + */ +class TypedReadTransactionImpl extends TypedTransaction + implements TypedReadTransaction { + private final ReadTransaction delegate; + + TypedReadTransactionImpl(Class datastoreType, ReadTransaction realTx) { + super(datastoreType); + this.delegate = realTx; + } + + @Override + public FluentFuture> read(InstanceIdentifier path) { + return FluentFuture.from(delegate.read(getDatastoreType(), path)); + } + + @Override + public Object getIdentifier() { + return delegate.getIdentifier(); + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java new file mode 100644 index 0000000000..1085e7a07f --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java @@ -0,0 +1,21 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.ReadWriteTransaction; + +/** + * Read-write transaction which is specific to a single logical datastore (configuration or operational). Designed + * for use with {@link ManagedNewTransactionRunner} (it doesn’t support explicit cancel or commit operations). + * + * @param The logical datastore handled by the transaction. + * @see ReadWriteTransaction + */ +public interface TypedReadWriteTransaction + extends TypedReadTransaction, TypedWriteTransaction { +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java new file mode 100644 index 0000000000..f78b4cd4fe --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java @@ -0,0 +1,36 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import com.google.common.util.concurrent.FluentFuture; +import java.util.Optional; +import org.opendaylight.mdsal.binding.api.ReadWriteTransaction; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Implementation of {@link TypedReadWriteTransaction}. + * + * @param The datastore which the transaction targets. + */ +class TypedReadWriteTransactionImpl + extends TypedWriteTransactionImpl + implements TypedReadWriteTransaction { + // Temporarily package protected for TransactionAdapter + final ReadWriteTransaction delegate; + + TypedReadWriteTransactionImpl(Class datastoreType, ReadWriteTransaction realTx) { + super(datastoreType, realTx); + this.delegate = realTx; + } + + @Override + public FluentFuture> read(InstanceIdentifier path) { + return FluentFuture.from(delegate.read(getDatastoreType(), path)); + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java new file mode 100644 index 0000000000..1e9c5088c4 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; + +abstract class TypedTransaction { + // Temporarily package protected for TransactionAdapter + final LogicalDatastoreType datastoreType; + + TypedTransaction(Class datastoreType) { + this.datastoreType = Datastore.toType(datastoreType); + } + + LogicalDatastoreType getDatastoreType() { + return this.datastoreType; + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java new file mode 100644 index 0000000000..f8be2a4ed3 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java @@ -0,0 +1,80 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.Transaction; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Write transaction which is specific to a single logical datastore (configuration or operational). Designed for use + * with {@link ManagedNewTransactionRunner} (it doesn’t support explicit cancel or commit operations). + * + * @param The logical datastore handled by the transaction. + * @see WriteTransaction + */ +public interface TypedWriteTransaction extends Transaction { + /** + * Writes an object to the given path. + * + * @see WriteTransaction#put(LogicalDatastoreType, InstanceIdentifier, DataObject) + * + * @param path The path to write to. + * @param data The object to write. + * @param The type of the provided object. + */ + void put(InstanceIdentifier path, T data); + + /** + * Writes an object to the given path, creating missing parents if requested. + * + * @see WriteTransaction#put(LogicalDatastoreType, InstanceIdentifier, DataObject, boolean) + * + * @param path The path to write to. + * @param data The object to write. + * @param createMissingParents {@link WriteTransaction#CREATE_MISSING_PARENTS} to create missing parents, + * {@link WriteTransaction#FAIL_ON_MISSING_PARENTS} to fail if parents are missing. + * @param The type of the provided object. + */ + void put(InstanceIdentifier path, T data, boolean createMissingParents); + + /** + * Merges an object with the data already present at the given path. + * + * @see WriteTransaction#merge(LogicalDatastoreType, InstanceIdentifier, DataObject) + * + * @param path The path to write to. + * @param data The object to merge. + * @param The type of the provided object. + */ + void merge(InstanceIdentifier path, T data); + + /** + * Merges an object with the data already present at the given path, creating missing parents if requested. + * + * @see WriteTransaction#merge(LogicalDatastoreType, InstanceIdentifier, DataObject, boolean) + * + * @param path The path to write to. + * @param data The object to merge. + * @param createMissingParents {@link WriteTransaction#CREATE_MISSING_PARENTS} to create missing parents, + * {@link WriteTransaction#FAIL_ON_MISSING_PARENTS} to fail if parents are missing. + * @param The type of the provided object. + */ + void merge(InstanceIdentifier path, T data, boolean createMissingParents); + + /** + * Deletes the object present at the given path. + * + * @see WriteTransaction#delete(LogicalDatastoreType, InstanceIdentifier) + * + * @param path The path to delete. + */ + void delete(InstanceIdentifier path); +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java new file mode 100644 index 0000000000..85746cceaf --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java @@ -0,0 +1,60 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import javax.annotation.Nonnull; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Implementation of {@link TypedWriteTransaction}. + * + * @param The datastore which the transaction targets. + */ +class TypedWriteTransactionImpl extends TypedTransaction + implements TypedWriteTransaction { + // Temporarily package protected for TransactionAdapter + final WriteTransaction delegate; + + TypedWriteTransactionImpl(Class datastoreType, WriteTransaction realTx) { + super(datastoreType); + this.delegate = realTx; + } + + @Override + public void put(InstanceIdentifier path, T data) { + delegate.put(getDatastoreType(), path, data); + } + + @Override + public void put(InstanceIdentifier path, T data, boolean createMissingParents) { + delegate.put(getDatastoreType(), path, data, createMissingParents); + } + + @Override + public void merge(InstanceIdentifier path, T data) { + delegate.merge(getDatastoreType(), path, data); + } + + @Override + public void merge(InstanceIdentifier path, T data, boolean createMissingParents) { + delegate.merge(getDatastoreType(), path, data, createMissingParents); + } + + @Override + public void delete(InstanceIdentifier path) { + delegate.delete(getDatastoreType(), path); + } + + @Override + @Nonnull + public Object getIdentifier() { + return delegate.getIdentifier(); + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java new file mode 100644 index 0000000000..9df66175d1 --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java @@ -0,0 +1,64 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.ReadWriteTransaction; +import org.opendaylight.mdsal.binding.spi.ForwardingReadWriteTransaction; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Read-write transaction which keeps track of writes. + */ +class WriteTrackingReadWriteTransaction extends ForwardingReadWriteTransaction implements WriteTrackingTransaction { + // This is volatile to ensure we get the latest value; transactions aren't supposed to be used in multiple threads, + // but the cost here is tiny (one read penalty at the end of a transaction) so we play it safe + private volatile boolean written; + + WriteTrackingReadWriteTransaction(ReadWriteTransaction delegate) { + super(delegate); + } + + @Override + public void put(LogicalDatastoreType store, InstanceIdentifier path, T data) { + super.put(store, path, data); + written = true; + } + + @Override + public void put(LogicalDatastoreType store, InstanceIdentifier path, T data, + boolean createMissingParents) { + super.put(store, path, data, createMissingParents); + written = true; + } + + @Override + public void merge(LogicalDatastoreType store, InstanceIdentifier path, T data) { + super.merge(store, path, data); + written = true; + } + + @Override + public void merge(LogicalDatastoreType store, InstanceIdentifier path, T data, + boolean createMissingParents) { + super.merge(store, path, data, createMissingParents); + written = true; + } + + @Override + public void delete(LogicalDatastoreType store, InstanceIdentifier path) { + super.delete(store, path); + written = true; + } + + @Override + public boolean isWritten() { + return written; + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java new file mode 100644 index 0000000000..19f652715f --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2018 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.util; + +// intentionally package local, not public +interface WriteTrackingTransaction { + + boolean isWritten(); + +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java new file mode 100644 index 0000000000..f882aabafb --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.ReadWriteTransaction; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Read-write typed transaction which keeps track of writes. + */ +class WriteTrackingTypedReadWriteTransactionImpl extends TypedReadWriteTransactionImpl + implements WriteTrackingTransaction { + + // This is volatile to ensure we get the latest value; transactions aren't supposed to be used in multiple threads, + // but the cost here is tiny (one read penalty at the end of a transaction) so we play it safe + private volatile boolean written; + + WriteTrackingTypedReadWriteTransactionImpl(Class datastoreType, ReadWriteTransaction realTx) { + super(datastoreType, realTx); + } + + @Override + public void put(InstanceIdentifier path, T data) { + super.put(path, data); + written = true; + } + + @Override + public void put(InstanceIdentifier path, T data, boolean createMissingParents) { + super.put(path, data, createMissingParents); + written = true; + } + + @Override + public void merge(InstanceIdentifier path, T data) { + super.merge(path, data); + written = true; + } + + @Override + public void merge(InstanceIdentifier path, T data, boolean createMissingParents) { + super.merge(path, data, createMissingParents); + written = true; + } + + @Override + public void delete(InstanceIdentifier path) { + super.delete(path); + written = true; + } + + @Override + public boolean isWritten() { + return written; + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java new file mode 100644 index 0000000000..b6c322340d --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Write typed transaction which keeps track of writes. + */ +class WriteTrackingTypedWriteTransactionImpl extends TypedWriteTransactionImpl + implements WriteTrackingTransaction { + + // This is volatile to ensure we get the latest value; transactions aren't supposed to be used in multiple threads, + // but the cost here is tiny (one read penalty at the end of a transaction) so we play it safe + private volatile boolean written; + + WriteTrackingTypedWriteTransactionImpl(Class datastoreType, WriteTransaction realTx) { + super(datastoreType, realTx); + } + + @Override + public void put(InstanceIdentifier path, T data) { + super.put(path, data); + written = true; + } + + @Override + public void put(InstanceIdentifier path, T data, boolean createMissingParents) { + super.put(path, data, createMissingParents); + written = true; + } + + @Override + public void merge(InstanceIdentifier path, T data) { + super.merge(path, data); + written = true; + } + + @Override + public void merge(InstanceIdentifier path, T data, boolean createMissingParents) { + super.merge(path, data, createMissingParents); + written = true; + } + + @Override + public void delete(InstanceIdentifier path) { + super.delete(path); + written = true; + } + + @Override + public boolean isWritten() { + return written; + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java new file mode 100644 index 0000000000..9d21fdb2bd --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2018 Red Hat, Inc. and others. + * + * 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.util; + +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.binding.spi.ForwardingWriteTransaction; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Write transaction which keeps track of writes. + */ +class WriteTrackingWriteTransaction extends ForwardingWriteTransaction implements WriteTrackingTransaction { + // This is only ever read *after* changes to the transaction are complete + private boolean written; + + WriteTrackingWriteTransaction(WriteTransaction delegate) { + super(delegate); + } + + @Override + public void put(LogicalDatastoreType store, InstanceIdentifier path, T data) { + super.put(store, path, data); + written = true; + } + + @Override + public void put(LogicalDatastoreType store, InstanceIdentifier path, T data, + boolean createMissingParents) { + super.put(store, path, data, createMissingParents); + written = true; + } + + @Override + public void merge(LogicalDatastoreType store, InstanceIdentifier path, T data) { + super.merge(store, path, data); + written = true; + } + + @Override + public void merge(LogicalDatastoreType store, InstanceIdentifier path, T data, + boolean createMissingParents) { + super.merge(store, path, data, createMissingParents); + written = true; + } + + @Override + public void delete(LogicalDatastoreType store, InstanceIdentifier path) { + super.delete(store, path); + written = true; + } + + @Override + public boolean isWritten() { + return written; + } +} diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java new file mode 100644 index 0000000000..f10ce228cd --- /dev/null +++ b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright © 2017, 2018 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 + */ +@org.eclipse.jdt.annotation.NonNullByDefault +package org.opendaylight.mdsal.binding.util; diff --git a/binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java b/binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java new file mode 100644 index 0000000000..8e1976e2dc --- /dev/null +++ b/binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 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.util; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Assert; +import org.junit.Test; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; + +public class DatastoreTest { + + @Test + public void testDatastore() { + assertThat(Datastore.toType(Datastore.CONFIGURATION)).isEqualTo(LogicalDatastoreType.CONFIGURATION); + assertThat(Datastore.toType(Datastore.OPERATIONAL)).isEqualTo(LogicalDatastoreType.OPERATIONAL); + try { + Datastore.toType(null); + Assert.fail("Expected Datastore.toType(null) to throw NullPointerException"); + } catch (NullPointerException e) { + // OK, this is what we're expecting + } + + assertThat(Datastore.toClass(LogicalDatastoreType.CONFIGURATION)).isEqualTo(Datastore.CONFIGURATION); + assertThat(Datastore.toClass(LogicalDatastoreType.OPERATIONAL)).isEqualTo(Datastore.OPERATIONAL); + try { + Datastore.toClass(null); + Assert.fail("Expected Datastore.toClass(null) to throw NullPointerException"); + } catch (NullPointerException e) { + // OK, this is what we're expecting + } + } + +} diff --git a/binding/pom.xml b/binding/pom.xml index 691fca4a49..123aeb21a4 100644 --- a/binding/pom.xml +++ b/binding/pom.xml @@ -45,6 +45,7 @@ mdsal-binding-util mdsal-binding-test-utils mdsal-binding-dom-adapter + mdsal-binding-util-tests -- 2.36.6