Add exists method on DOMStoreReadTransaction and DOMDataReadTransaction
authorMoiz Raja <moraja@cisco.com>
Sun, 10 Aug 2014 23:18:33 +0000 (16:18 -0700)
committerMoiz Raja <moraja@cisco.com>
Tue, 12 Aug 2014 17:18:29 +0000 (10:18 -0700)
Introducing this API does not impact clients as it is mostly internal.

One reason for adding this API is because of ensureParentsByMerge used in a couple of places
which keeps checking if a parent node exists by reading a node to figure out if it exists. This is
fine for the InMemoryDataStore but it can be terrible in a distributed data store where the shard
which contains the data is remote. All sorts of overhead is associated with a remote read including
serialization which can actually be pretty expensive.

Change-Id: Ib5be5f6dc60be683d7a04c81dad08c56cd5681f4
Signed-off-by: Moiz Raja <moraja@cisco.com>
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractReadWriteTransaction.java
opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/ForwardedBackwardsCompatibleDataBrokerTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/test/DataBrokerTestCustomizer.java
opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataReadTransaction.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMForwardedReadOnlyTransaction.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMForwardedReadWriteTransaction.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/BackwardsCompatibleMountPoint.java
opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/sal/core/spi/data/DOMStoreReadTransaction.java
opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadTransaction.java
opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedReadWriteTransaction.java
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDataStoreTest.java

index 5ced7bae9fd3459b494a50013e297aacd8b3425f..8fbc118a16efcc59b74b4016743a8ff87ee24f7a 100644 (file)
@@ -7,22 +7,19 @@
  */
 package org.opendaylight.controller.md.sal.binding.impl;
 
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Optional;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 
 public class AbstractReadWriteTransaction extends AbstractWriteTransaction<DOMDataReadWriteTransaction> {
 
@@ -50,15 +47,15 @@ public class AbstractReadWriteTransaction extends AbstractWriteTransaction<DOMDa
             org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier currentPath = org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.create(
                     currentArguments);
 
-            final Optional<NormalizedNode<?, ?>> d;
+            final Boolean exists;
             try {
-                d = getDelegate().read(store, currentPath).get();
-            } catch (InterruptedException | ExecutionException e) {
+                exists = getDelegate().exists(store, currentPath).checkedGet();
+            } catch (ReadFailedException e) {
                 LOG.error("Failed to read pre-existing data from store {} path {}", store, currentPath, e);
                 throw new IllegalStateException("Failed to read pre-existing data", e);
             }
 
-            if (!d.isPresent() && iterator.hasNext()) {
+            if (!exists && iterator.hasNext()) {
                 getDelegate().merge(store, currentPath, currentOp.createDefault(currentArg));
             }
         }
diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/ForwardedBackwardsCompatibleDataBrokerTest.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/ForwardedBackwardsCompatibleDataBrokerTest.java
new file mode 100644 (file)
index 0000000..f91e356
--- /dev/null
@@ -0,0 +1,72 @@
+package org.opendaylight.controller.md.sal.binding.impl.test;
+
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.binding.impl.ForwardedBackwardsCompatibleDataBroker;
+import org.opendaylight.controller.md.sal.binding.test.AbstractSchemaAwareTest;
+import org.opendaylight.controller.md.sal.binding.test.DataBrokerTestCustomizer;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+import java.util.concurrent.ExecutionException;
+
+import static junit.framework.TestCase.assertNotNull;
+
+public class ForwardedBackwardsCompatibleDataBrokerTest extends
+    AbstractSchemaAwareTest {
+
+    private DataBrokerTestCustomizer testCustomizer;
+    private ForwardedBackwardsCompatibleDataBroker dataBroker;
+    private DOMDataBroker domBroker;
+
+    private static final InstanceIdentifier<Top> TOP_PATH = InstanceIdentifier.create(Top.class);
+    private static final TopLevelListKey TOP_LIST_KEY = new TopLevelListKey("foo");
+    private static final InstanceIdentifier<TopLevelList> NODE_PATH = TOP_PATH.child(TopLevelList.class, TOP_LIST_KEY);
+    private static final TopLevelList NODE = new TopLevelListBuilder().setKey(TOP_LIST_KEY).build();
+
+    protected DataBrokerTestCustomizer createDataBrokerTestCustomizer() {
+        return new DataBrokerTestCustomizer();
+    }
+
+    @Override
+    protected void setupWithSchema(final SchemaContext context) {
+        testCustomizer = createDataBrokerTestCustomizer();
+
+        domBroker = testCustomizer.createDOMDataBroker();
+        dataBroker = testCustomizer.createBackwardsCompatibleDataBroker();
+        testCustomizer.updateSchema(context);
+    }
+
+
+    /**
+     * The purpose of this test is to exercise the backwards compatible broker
+     * <p>
+     * This test tries to execute the code which ensures that the parents
+     * for a given node get automatically created.
+     *
+     * @see org.opendaylight.controller.md.sal.binding.impl.AbstractReadWriteTransaction#ensureParentsByMerge(org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType, org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier, org.opendaylight.yangtools.yang.binding.InstanceIdentifier)
+     */
+    @Test
+    public void test() throws InterruptedException, ExecutionException {
+        DataModificationTransaction writeTx =
+            dataBroker.beginTransaction();
+
+        writeTx.putOperationalData(NODE_PATH, NODE);
+
+        writeTx.commit();
+
+        // TOP_PATH should exist as it is the parent of NODE_PATH
+        DataObject object = dataBroker.readOperationalData(TOP_PATH);
+
+        assertNotNull(object);
+
+    }
+
+
+}
index 666c819c82ce45ad55e8bf4f3c83c6ad7da349cd..60eec55ca55df3df580318e5608ea08f1a319801 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.controller.md.sal.binding.test;
 import javassist.ClassPool;
 
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.impl.ForwardedBackwardsCompatibleDataBroker;
 import org.opendaylight.controller.md.sal.binding.impl.ForwardedBindingDataBroker;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
@@ -71,6 +72,11 @@ public class DataBrokerTestCustomizer {
         return new ForwardedBindingDataBroker(getDOMDataBroker(), getMappingService(), getSchemaService());
     }
 
+    public ForwardedBackwardsCompatibleDataBroker createBackwardsCompatibleDataBroker() {
+        return new ForwardedBackwardsCompatibleDataBroker(getDOMDataBroker(), getMappingService(), getSchemaService(), MoreExecutors.sameThreadExecutor());
+    }
+
+
     private SchemaService getSchemaService() {
         return schemaService;
     }
index fc251c8445a77d1485a2dffc4880afa9b3907f95..9b70f0c4d708d63b1544d05dd1948df35e209912 100644 (file)
@@ -47,4 +47,22 @@ public interface DOMDataReadTransaction extends AsyncReadTransaction<YangInstanc
      */
     CheckedFuture<Optional<NormalizedNode<?,?>>, ReadFailedException> read(
             LogicalDatastoreType store, YangInstanceIdentifier path);
+
+    /**
+     * Checks if data is available in the logical data store located at provided path
+     *
+     * @param path
+     *            Path which uniquely identifies subtree which client want to
+     *            check existence of
+     * @return a CheckFuture containing the result of the check.
+     *         <ul>
+     *         <li>If the data at the supplied path exists, the Future returns a Boolean
+     *         whose value is true, false otherwise</li>
+     *         <li>If checking for the data fails, the Future will fail with a
+     *         {@link ReadFailedException} or an exception derived from ReadFailedException.</li>
+     *         </ul>
+     */
+    CheckedFuture<Boolean, ReadFailedException> exists(
+        LogicalDatastoreType store, YangInstanceIdentifier path);
+
 }
index b4562cf2eccd2c8b6560552c510fe1bfeb8d9989..5e2a417d28ce22acc6a19a1f556f7ea2c95a4382 100644 (file)
@@ -40,6 +40,12 @@ class DOMForwardedReadOnlyTransaction extends
         return getSubtransaction(store).read(path);
     }
 
+    @Override public CheckedFuture<Boolean, ReadFailedException> exists(
+        LogicalDatastoreType store,
+        YangInstanceIdentifier path) {
+        return getSubtransaction(store).exists(path);
+    }
+
     @Override
     public void close() {
         closeSubtransactions();
index 74a4c52e36ff33883754b0ef5543706054134967..67351ec94583cda374f5b0948eafd1089fd98510 100644 (file)
@@ -50,4 +50,10 @@ class DOMForwardedReadWriteTransaction extends DOMForwardedWriteTransaction<DOMS
             final LogicalDatastoreType store, final YangInstanceIdentifier path) {
         return getSubtransaction(store).read(path);
     }
-}
\ No newline at end of file
+
+    @Override public CheckedFuture<Boolean, ReadFailedException> exists(
+        LogicalDatastoreType store,
+        YangInstanceIdentifier path) {
+        return getSubtransaction(store).exists(path);
+    }
+}
index 5bd8a7bc021f935fd20251892101f861e2c2db39..fb72b5a99a130efbcfcd0ea04056ef61978f2e06 100644 (file)
@@ -15,13 +15,6 @@ import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.JdkFutureAdapters;
 import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
 import org.opendaylight.controller.md.sal.common.api.RegistrationListener;
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler;
@@ -78,6 +71,12 @@ import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
 
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
 public class BackwardsCompatibleMountPoint implements MountProvisionInstance, SchemaContextProvider, SchemaService {
 
     private final DataProviderService dataReader;
@@ -405,6 +404,16 @@ public class BackwardsCompatibleMountPoint implements MountProvisionInstance, Sc
                 final Optional<NormalizedNode<?, ?>> normalizedNodeOptional = Optional.<NormalizedNode<?, ?>>fromNullable(normalized.getValue());
                 return Futures.immediateCheckedFuture(normalizedNodeOptional);
             }
+
+            @Override public CheckedFuture<Boolean, ReadFailedException> exists(LogicalDatastoreType store,
+                YangInstanceIdentifier path) {
+
+                try {
+                    return Futures.immediateCheckedFuture(read(store, path).get().isPresent());
+                } catch (InterruptedException | ExecutionException e) {
+                    return Futures.immediateFailedCheckedFuture(new ReadFailedException("Exists failed",e));
+                }
+            }
         }
 
         @VisibleForTesting
@@ -518,6 +527,16 @@ public class BackwardsCompatibleMountPoint implements MountProvisionInstance, Sc
                 return new BackwardsCompatibleReadTransaction(dataReader, dataNormalizer).read(store, path);
             }
 
+            @Override public CheckedFuture<Boolean, ReadFailedException> exists(LogicalDatastoreType store,
+                YangInstanceIdentifier path) {
+
+                try {
+                    return Futures.immediateCheckedFuture(read(store, path).get().isPresent());
+                } catch (InterruptedException | ExecutionException e) {
+                    return Futures.immediateFailedCheckedFuture(new ReadFailedException("Exists failed",e));
+                }
+            }
+
             @Override
             public boolean cancel() {
                 return delegateWriteTx.cancel();
index 84d09c7cb024a275ebfdfa6edd8a75af00abc3af..719a6f0499d4423040e5547b82a3bc957c30abbe 100644 (file)
@@ -34,4 +34,20 @@ public interface DOMStoreReadTransaction extends DOMStoreTransaction {
      *         </ul>
      */
     CheckedFuture<Optional<NormalizedNode<?,?>>, ReadFailedException> read(YangInstanceIdentifier path);
+
+    /**
+     * Checks if data is available in the logical data store located at provided path
+     *
+     * @param path
+     *            Path which uniquely identifies subtree which client want to
+     *            check existence of
+     * @return a CheckFuture containing the result of the check.
+     *         <ul>
+     *         <li>If the data at the supplied path exists, the Future returns a Boolean
+     *         whose value is true, false otherwise</li>
+     *         <li>If checking for the data fails, the Future will fail with a
+     *         {@link ReadFailedException} or an exception derived from ReadFailedException.</li>
+     *         </ul>
+     */
+    CheckedFuture<Boolean, ReadFailedException> exists(YangInstanceIdentifier path);
 }
index 2a9840634381efea5c76cfd220530a11a83d80f0..44ee61c116a9a4c5f8f336890827fcfceea68075 100644 (file)
@@ -7,19 +7,19 @@
  */
 package org.opendaylight.controller.md.sal.dom.store.impl;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.Futures;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  *
@@ -63,4 +63,16 @@ final class SnapshotBackedReadTransaction extends AbstractDOMStoreTransaction
             return Futures.immediateFailedCheckedFuture(new ReadFailedException("Read failed",e));
         }
     }
-}
\ No newline at end of file
+
+    @Override public CheckedFuture<Boolean, ReadFailedException> exists(YangInstanceIdentifier path) {
+        LOG.debug("Tx: {} Exists: {}", getIdentifier(), path);
+        checkNotNull(path, "Path must not be null.");
+
+        try {
+            return Futures.immediateCheckedFuture(
+                read(path).checkedGet().isPresent());
+        } catch (ReadFailedException e) {
+            return Futures.immediateFailedCheckedFuture(e);
+        }
+    }
+}
index 5c5e9c6b6d82f4263959c6f8e137cc0bf1af25ad..ce7043fd4747542a98802a893d0d49c3c12b3de5 100644 (file)
@@ -61,4 +61,14 @@ class SnapshotBackedReadWriteTransaction extends SnapshotBackedWriteTransaction
             return Futures.immediateFailedCheckedFuture(new ReadFailedException("Read failed",e));
         }
     }
-}
\ No newline at end of file
+
+    @Override public CheckedFuture<Boolean, ReadFailedException> exists(
+        YangInstanceIdentifier path) {
+        try {
+            return Futures.immediateCheckedFuture(
+                read(path).checkedGet().isPresent());
+        } catch (ReadFailedException e) {
+            return Futures.immediateFailedCheckedFuture(e);
+        }
+    }
+}
index 4d38858667173df23ed112fa5e13c139914bdb73..c609e13e791c375979ad99bce62ed27df9d321f8 100644 (file)
@@ -7,13 +7,10 @@
  */
 package org.opendaylight.controller.md.sal.dom.store.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.concurrent.ExecutionException;
-
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -35,9 +32,12 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.ExecutionException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 
 public class InMemoryDataStoreTest {
@@ -185,6 +185,74 @@ public class InMemoryDataStoreTest {
         assertEquals( "After commit read: data", containerNode, afterCommitRead.get() );
     }
 
+
+    @Test
+    public void testExistsForExistingData() throws Exception {
+
+        DOMStoreReadWriteTransaction writeTx = domStore.newReadWriteTransaction();
+        assertNotNull( writeTx );
+
+        ContainerNode containerNode = ImmutableContainerNodeBuilder.create()
+            .withNodeIdentifier( new NodeIdentifier( TestModel.TEST_QNAME ) )
+            .addChild( ImmutableNodes.mapNodeBuilder( TestModel.OUTER_LIST_QNAME )
+                .addChild( ImmutableNodes.mapEntry( TestModel.OUTER_LIST_QNAME,
+                    TestModel.ID_QNAME, 1 ) ).build() ).build();
+
+        writeTx.merge( TestModel.TEST_PATH, containerNode );
+
+        CheckedFuture<Boolean, ReadFailedException> exists =
+            writeTx.exists(TestModel.TEST_PATH);
+
+        assertEquals(true, exists.checkedGet());
+
+        DOMStoreThreePhaseCommitCohort ready = writeTx.ready();
+
+        ready.preCommit().get();
+
+        ready.commit().get();
+
+        DOMStoreReadTransaction readTx = domStore.newReadOnlyTransaction();
+        assertNotNull( readTx );
+
+        exists =
+            readTx.exists(TestModel.TEST_PATH);
+
+        assertEquals(true, exists.checkedGet());
+    }
+
+    @Test
+    public void testExistsForNonExistingData() throws Exception {
+
+        DOMStoreReadWriteTransaction writeTx = domStore.newReadWriteTransaction();
+        assertNotNull( writeTx );
+
+        CheckedFuture<Boolean, ReadFailedException> exists =
+            writeTx.exists(TestModel.TEST_PATH);
+
+        assertEquals(false, exists.checkedGet());
+
+        DOMStoreReadTransaction readTx = domStore.newReadOnlyTransaction();
+        assertNotNull( readTx );
+
+        exists =
+            readTx.exists(TestModel.TEST_PATH);
+
+        assertEquals(false, exists.checkedGet());
+    }
+
+    @Test(expected=ReadFailedException.class)
+    public void testExistsThrowsReadFailedException() throws Exception {
+
+        DOMStoreReadTransaction readTx = domStore.newReadOnlyTransaction();
+        assertNotNull( readTx );
+
+        readTx.close();
+
+        readTx.exists(TestModel.TEST_PATH).checkedGet();
+    }
+
+
+
     @Test(expected=ReadFailedException.class)
     public void testReadWithReadOnlyTransactionClosed() throws Throwable {