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>
*/
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> {
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));
}
}
--- /dev/null
+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);
+
+ }
+
+
+}
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;
return new ForwardedBindingDataBroker(getDOMDataBroker(), getMappingService(), getSchemaService());
}
+ public ForwardedBackwardsCompatibleDataBroker createBackwardsCompatibleDataBroker() {
+ return new ForwardedBackwardsCompatibleDataBroker(getDOMDataBroker(), getMappingService(), getSchemaService(), MoreExecutors.sameThreadExecutor());
+ }
+
+
private SchemaService getSchemaService() {
return schemaService;
}
*/
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);
+
}
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();
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);
+ }
+}
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;
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;
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
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();
* </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);
}
*/
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;
/**
*
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);
+ }
+ }
+}
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);
+ }
+ }
+}
*/
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;
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 {
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 {