From 1ed67da6416e25cfd5a8270b4875cac3a3764634 Mon Sep 17 00:00:00 2001 From: Tomas Cere Date: Thu, 2 Jun 2016 17:19:49 +0200 Subject: [PATCH] BUG 6057: Rewrite ShardedDOMProducer to use new cursor api Change ShardedDOMProducer, ShardedDOMDataTreeWriteTransaction to use cursor api's. Make the user facing cursor implementation enforce claimed subtrees. Add binding adapters for the DOM cursor api's. Change-Id: I21fa6533b8ce7da0e5cc864569f6f143a273ae86 Signed-off-by: Tomas Cere --- binding/mdsal-binding-api/pom.xml | 4 + .../api/CursorAwareWriteTransaction.java | 67 +++++ .../mdsal/binding/api/DataTreeCursor.java | 82 ++++++ .../binding/api/DataTreeCursorProvider.java | 31 +++ .../mdsal/binding/api/DataTreeProducer.java | 4 +- .../binding/api/DataTreeWriteCursor.java | 50 ++++ ...DOMCursorAwareWriteTransactionAdapter.java | 52 ++++ .../BindingDOMDataTreeProducerAdapter.java | 8 +- .../BindingDOMDataTreeWriteCursorAdapter.java | 103 +++++++ .../api/DOMDataTreeCommitCohortRegistry.java | 1 + .../DOMDataTreeCursorAwareTransaction.java | 70 +++++ .../mdsal/dom/api/DOMDataTreeProducer.java | 4 +- .../AbstractDOMDataTreeServiceTestSuite.java | 11 +- .../mdsal/dom/broker/ShardedDOMDataTree.java | 8 +- .../broker/ShardedDOMDataTreeProducer.java | 118 +++----- .../ShardedDOMDataTreeWriteTransaction.java | 263 ++++++++++++++++++ .../ShardedDOMDataWriteTransaction.java | 117 -------- .../ShardedDOMDataWriteTransactionTest.java | 192 +++++++++---- ...rdedDOMDataTreeProducerMultiShardTest.java | 3 +- ...dedDOMDataTreeProducerSingleShardTest.java | 34 ++- .../broker/test/ShardedDOMDataTreeTest.java | 230 +++++++++++++++ .../AbstractDOMShardTreeChangePublisher.java | 11 +- .../DOMDataTreeShardWriteTransaction.java | 6 +- .../inmemory/InMemoryDOMDataTreeShard.java | 6 +- ...MemoryDOMDataTreeShardChangePublisher.java | 6 +- ...emoryDOMDataTreeShardWriteTransaction.java | 39 ++- 26 files changed, 1213 insertions(+), 307 deletions(-) create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java create mode 100644 binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java create mode 100644 dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java create mode 100644 dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java delete mode 100644 dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java create mode 100644 dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java diff --git a/binding/mdsal-binding-api/pom.xml b/binding/mdsal-binding-api/pom.xml index 4b0c902eec..c33c4a5ad1 100644 --- a/binding/mdsal-binding-api/pom.xml +++ b/binding/mdsal-binding-api/pom.xml @@ -35,6 +35,10 @@ org.opendaylight.yangtools yang-common + + org.opendaylight.yangtools + yang-data-api + org.osgi org.osgi.core diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java new file mode 100644 index 0000000000..839c19cd95 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.api; + +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Write transaction that provides cursor's with write access to the data tree. + */ +public interface CursorAwareWriteTransaction extends DataTreeCursorProvider { + + /** + * Create a {@link DataTreeWriteCursor} anchored at the specified path. + * There can only be one cursor open at a time. + * + * @param path Path at which the cursor is to be anchored + * @return write cursor at the desired location. + * @throws IllegalStateException when there's an open cursor, or this transaction is closed already. + */ + @Nullable + @Override + DataTreeWriteCursor createCursor(@Nonnull DataTreeIdentifier path); + + /** + * Cancels the transaction. + * + * Transactions can only be cancelled if it was not yet submited. + * + * Invoking cancel() on failed or already canceled will have no effect, and transaction is + * considered cancelled. + * + * Invoking cancel() on finished transaction (future returned by {@link #submit()} already + * successfully completed) will always fail (return false). + * + * @return false if the task could not be cancelled, typically because it has already + * completed normally; true otherwise + * + */ + boolean cancel(); + + /** + * Submits this transaction to be asynchronously applied to update the logical data tree. The + * returned CheckedFuture conveys the result of applying the data changes. + *

+ * Note: It is strongly recommended to process the CheckedFuture result in an + * asynchronous manner rather than using the blocking get() method. + * + * This call logically seals the transaction, which prevents the client from further changing + * data tree using this transaction's cursor. Any subsequent calls to + * createCursorCursor(DOMDataTreeIdentifier + * or any of the cursor's methods will fail with {@link IllegalStateException}. + * + * The transaction is marked as submitted and enqueued into the shard back-end for + * processing. + */ + CheckedFuture submit(); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java new file mode 100644 index 0000000000..84895acba5 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.api; + +import com.google.common.annotations.Beta; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.NotThreadSafe; +import org.opendaylight.yangtools.yang.binding.DataContainer; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; + + +/** + * A cursor holding a logical position within a conceptual data tree. It allows operations relative + * to that position, as well as moving the position up or down the tree. + */ +@Beta +@NotThreadSafe +public interface DataTreeCursor extends AutoCloseable { + /** + * Move the cursor to the specified child of the current position. + * + * @param child Child identifier + * @throws IllegalArgumentException when specified identifier does not identify a valid child, + * or if that child is not an instance of {@link DataContainer}. + */ + void enter(@Nonnull PathArgument child); + + /** + * Move the cursor to the specified child of the current position. This is the equivalent of + * multiple invocations of {@link #enter(PathArgument)}, except the operation is performed all + * at once. + * + * @param path Nested child identifier + * @throws IllegalArgumentException when specified path does not identify a valid child, or if + * that child is not an instance of {@link DataContainer}. + */ + void enter(@Nonnull PathArgument... path); + + /** + * Move the cursor to the specified child of the current position. This is equivalent to + * {@link #enter(PathArgument...)}, except it takes an {@link Iterable} argument. + * + * @param path Nested child identifier + * @throws IllegalArgumentException when specified path does not identify a valid child, or if + * that child is not an instance of {@link DataContainer}. + */ + void enter(@Nonnull Iterable path); + + /** + * Move the cursor up to the parent of current position. This is equivalent of invoking + * exit(1). + * + * @throws IllegalStateException when exiting would violate containment, typically by attempting + * to exit more levels than previously entered. + */ + void exit(); + + /** + * Move the cursor up by specified amounts of steps from the current position. This is + * equivalent of invoking {@link #exit()} multiple times, except the operation is performed + * atomically. + * + * @param depth number of steps to exit + * @throws IllegalArgumentException when depth is negative. + * @throws IllegalStateException when exiting would violate containment, typically by attempting + * to exit more levels than previously entered. + */ + void exit(int depth); + + /** + * Close this cursor. Attempting any further operations on the cursor will lead to undefined + * behavior. + */ + @Override + void close(); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java new file mode 100644 index 0000000000..003afcf578 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.api; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Provides access to {#link DataTreeCursor}'s anchored at the specified path. + */ +public interface DataTreeCursorProvider { + + /** + * Create a new {@link DataTreeCursor} at specified path. May fail if specified path + * does not exist. + * + * @param path Path at which the cursor is to be anchored + * @return A new cursor, or null if the path does not exist. + * @throws IllegalStateException if there is another cursor currently open, or the transaction + * is already closed (closed or submitted). + */ + @Nullable + DataTreeCursor createCursor(@Nonnull DataTreeIdentifier path); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeProducer.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeProducer.java index 07e2559269..0520b40e21 100644 --- a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeProducer.java +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeProducer.java @@ -48,7 +48,7 @@ public interface DataTreeProducer extends DataTreeProducerFactory, AutoCloseable * is processed separately from any preceding transactions. Non-barrier transactions may * be merged and processed in a batch, such that any observers see the modifications * contained in them as if the modifications were made in a single transaction. - * @return A new {@link WriteTransaction} + * @return A new {@link CursorAwareWriteTransaction} * @throws IllegalStateException if a previous transaction was not closed. * @throws IllegalThreadStateException if the calling thread context does not match the * lifecycle rules enforced by the producer state (e.g. bound or unbound). This @@ -56,7 +56,7 @@ public interface DataTreeProducer extends DataTreeProducerFactory, AutoCloseable * correct operation. */ @Nonnull - WriteTransaction createTransaction(boolean isolated); + CursorAwareWriteTransaction createTransaction(boolean isolated); /** * {@inheritDoc} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java new file mode 100644 index 0000000000..653c6a4e73 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.api; + +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.tree.BackendFailedException; + +/** + * {@inheritDoc} + * + * In addition this cursor also provides write operations(delete, merge, write). + */ +public interface DataTreeWriteCursor extends DataTreeCursor { + + /** + * Delete the specified child. + * + * @param child Child identifier + * @throws BackendFailedException when implementation-specific errors occurs while servicing the + * request. + */ + void delete(PathArgument child); + + /** + * Merge the specified data with the currently-present data at specified path. + * + * @param child Child identifier + * @param data Data to be merged + * @throws BackendFailedException when implementation-specific errors occurs while servicing the + * request. + */ + void merge(PathArgument child, T data); + + /** + * Replace the data at specified path with supplied data. + * + * @param child Child identifier + * @param data New node data + * @throws BackendFailedException when implementation-specific errors occurs while servicing the + * request. + */ + void write(PathArgument child, T data); +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java new file mode 100644 index 0000000000..b01dd9946e --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.dom.adapter; + +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.binding.api.CursorAwareWriteTransaction; +import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; +import org.opendaylight.mdsal.binding.api.DataTreeWriteCursor; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +public class BindingDOMCursorAwareWriteTransactionAdapter implements CursorAwareWriteTransaction { + + private T delegate; + private BindingToNormalizedNodeCodec codec; + + public BindingDOMCursorAwareWriteTransactionAdapter(final T delegate, final BindingToNormalizedNodeCodec codec) { + + this.delegate = delegate; + this.codec = codec; + } + + @Nullable + @Override + public DataTreeWriteCursor createCursor(final DataTreeIdentifier path) { + final YangInstanceIdentifier yPath = codec.toNormalized(path.getRootIdentifier()); + final DOMDataTreeWriteCursor cursor = delegate.createCursor(new DOMDataTreeIdentifier(path.getDatastoreType(), yPath)); + return new BindingDOMDataTreeWriteCursorAdapter<>(path, cursor, codec); + } + + @Override + public boolean cancel() { + return delegate.cancel(); + } + + @Override + public CheckedFuture submit() { + return delegate.submit(); + } + +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeProducerAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeProducerAdapter.java index 6cf6d02a5c..dc74a0121b 100644 --- a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeProducerAdapter.java +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeProducerAdapter.java @@ -9,10 +9,12 @@ package org.opendaylight.mdsal.binding.dom.adapter; import com.google.common.base.Preconditions; import java.util.Collection; +import org.opendaylight.mdsal.binding.api.CursorAwareWriteTransaction; import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; import org.opendaylight.mdsal.binding.api.DataTreeProducer; import org.opendaylight.mdsal.binding.api.DataTreeProducerException; import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException; @@ -32,9 +34,9 @@ class BindingDOMDataTreeProducerAdapter implements DataTreeProducer { } @Override - public WriteTransaction createTransaction(final boolean isolated) { - final DOMDataTreeWriteTransaction domTx = delegate.createTransaction(isolated); - return new BindingDOMWriteTransactionAdapter(domTx, codec); + public CursorAwareWriteTransaction createTransaction(final boolean isolated) { + final DOMDataTreeCursorAwareTransaction domTx = delegate.createTransaction(isolated); + return new BindingDOMCursorAwareWriteTransactionAdapter<>(domTx, codec); } static DataTreeProducer create(final DOMDataTreeProducer domProducer, diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java new file mode 100644 index 0000000000..448c0bb53f --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.dom.adapter; + +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map.Entry; +import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; +import org.opendaylight.mdsal.binding.api.DataTreeWriteCursor; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +public class BindingDOMDataTreeWriteCursorAdapter implements DataTreeWriteCursor { + + private T delegate; + private BindingToNormalizedNodeCodec codec; + private final Deque stack = new ArrayDeque<>(); + + public BindingDOMDataTreeWriteCursorAdapter(final DataTreeIdentifier path, final T delegate, final BindingToNormalizedNodeCodec codec) { + + this.delegate = delegate; + this.codec = codec; + path.getRootIdentifier().getPathArguments().forEach(stack::push); + } + + private YangInstanceIdentifier.PathArgument convertToNormalized(final PathArgument child) { + stack.push(child); + final InstanceIdentifier iid = InstanceIdentifier.create(stack); + final YangInstanceIdentifier ret = codec.toNormalized(iid); + stack.pop(); + return ret.getLastPathArgument(); + } + + private Entry> convertToNormalized(final PathArgument child, final T data) { + stack.push(child); + final InstanceIdentifier iid = InstanceIdentifier.create(stack); + final Entry> entry = codec.toNormalizedNode(new SimpleEntry<>(iid, data)); + stack.pop(); + return entry; + } + + @Override + public void delete(final PathArgument child) { + delegate.delete(convertToNormalized(child)); + } + + @Override + public void merge(final PathArgument child, final T data) { + final Entry> entry = convertToNormalized(child, data); + delegate.merge(entry.getKey().getLastPathArgument(), entry.getValue()); + } + + @Override + public void write(PathArgument child, T data) { + final Entry> entry = convertToNormalized(child, data); + delegate.write(entry.getKey().getLastPathArgument(), entry.getValue()); + } + + @Override + public void enter(final PathArgument child) { + stack.push(child); + } + + @Override + public void enter(final PathArgument... path) { + for (final PathArgument pathArgument : path) { + enter(pathArgument); + } + } + + @Override + public void enter(final Iterable path) { + path.forEach(this::enter); + } + + @Override + public void exit() { + stack.pop(); + } + + @Override + public void exit(int depth) { + for (int i = 0; i < depth; i++) { + exit(); + } + } + + @Override + public void close() { + delegate.close(); + } +} diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCommitCohortRegistry.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCommitCohortRegistry.java index 075dd9d04d..63cba172b7 100644 --- a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCommitCohortRegistry.java +++ b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCommitCohortRegistry.java @@ -25,6 +25,7 @@ public interface DOMDataTreeCommitCohortRegistry extends DOMDataBrokerExtension * * @param path Subtree path on which commit cohort operates. * @param cohort Commit cohort + * @param Cohort subclass * @return Registaration object for DOM Data Three Commit cohort. */ DOMDataTreeCommitCohortRegistration registerCommitCohort( diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java new file mode 100644 index 0000000000..b4abfb2001 --- /dev/null +++ b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 Cisco Systems, 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.dom.api; + +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.yangtools.concepts.Identifiable; + +/** + * Write transaction that provides cursor's with write access to the data tree. + */ +public interface DOMDataTreeCursorAwareTransaction extends DOMDataTreeCursorProvider, Identifiable { + + /** + * Create a {@link DOMDataTreeWriteCursor} anchored at the specified path. + * There can only be one cursor open at a time. + * + * @param path Path at which the cursor is to be anchored + * @return write cursor at the desired location. + * @throws IllegalStateException when there's an open cursor, or this transaction is closed already. + */ + @Nullable + @Override + DOMDataTreeWriteCursor createCursor(@Nonnull DOMDataTreeIdentifier path); + + /** + * Cancels the transaction. + * + * Transactions can only be cancelled if it was not yet submited. + * + * Invoking cancel() on failed or already canceled will have no effect, and transaction is + * considered cancelled. + * + * Invoking cancel() on finished transaction (future returned by {@link #submit()} already + * successfully completed) will always fail (return false). + * + * @return false if the task could not be cancelled, typically because it has already + * completed normally; true otherwise + * + */ + boolean cancel(); + + /** + * Submits this transaction to be asynchronously applied to update the logical data tree. The + * returned CheckedFuture conveys the result of applying the data changes. + *

+ * Note: It is strongly recommended to process the CheckedFuture result in an + * asynchronous manner rather than using the blocking get() method. + * + * This call logically seals the transaction, which prevents the client from further changing + * data tree using this transaction's cursor. Any subsequent calls to + * createCursorCursor(DOMDataTreeIdentifier + * or any of the cursor's methods will fail with {@link IllegalStateException}. + * + * The transaction is marked as submitted and enqueued into the shard back-end for + * processing. + * + * @return Checked future informing of success/failure + */ + CheckedFuture submit(); + +} diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeProducer.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeProducer.java index eb11d510cf..7ff7d96487 100644 --- a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeProducer.java +++ b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeProducer.java @@ -47,14 +47,14 @@ public interface DOMDataTreeProducer extends DOMDataTreeProducerFactory, AutoClo * is processed separately from any preceding transactions. Non-barrier transactions may * be merged and processed in a batch, such that any observers see the modifications * contained in them as if the modifications were made in a single transaction. - * @return A new {@link DOMDataTreeWriteTransaction} + * @return A new {@link DOMDataTreeCursorAwareTransaction} * @throws IllegalStateException if a previous transaction was not closed. * @throws IllegalThreadStateException if the calling thread context does not match the * lifecycle rules enforced by the producer state (e.g. bound or unbound). This * exception is thrown on a best effort basis and programs should not rely on it for * correct operation. */ - @Nonnull DOMDataTreeWriteTransaction createTransaction(boolean isolated); + @Nonnull DOMDataTreeCursorAwareTransaction createTransaction(boolean isolated); /** * {@inheritDoc} diff --git a/dom/mdsal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java b/dom/mdsal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java index c0448a12e8..979cc533b0 100644 --- a/dom/mdsal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java +++ b/dom/mdsal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java @@ -12,10 +12,13 @@ import static org.junit.Assert.assertNotNull; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursor; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException; import org.opendaylight.mdsal.dom.api.DOMDataTreeService; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import com.google.common.util.concurrent.CheckedFuture; import java.net.URI; @@ -55,16 +58,20 @@ public abstract class AbstractDOMDataTreeServiceTestSuite { * @throws DOMDataTreeProducerException * @throws TransactionCommitFailedException */ + @Test public final void testBasicProducer() throws DOMDataTreeProducerException, TransactionCommitFailedException { // Create a producer. It is an AutoCloseable resource, hence the try-with pattern try (final DOMDataTreeProducer prod = service().createProducer(Collections.singleton(UNORDERED_CONTAINER_TREE))) { assertNotNull(prod); - final DOMDataTreeWriteTransaction tx = prod.createTransaction(true); + final DOMDataTreeCursorAwareTransaction tx = prod.createTransaction(true); assertNotNull(tx); - tx.put(LogicalDatastoreType.OPERATIONAL, UNORDERED_CONTAINER_IID, ImmutableContainerNodeBuilder.create().build()); + final DOMDataTreeWriteCursor cursor = tx.createCursor(new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, UNORDERED_CONTAINER_IID)); + assertNotNull(cursor); + cursor.write(UNORDERED_CONTAINER_IID.getLastPathArgument(), ImmutableContainerNodeBuilder.create().build()); + cursor.close(); final CheckedFuture f = tx.submit(); assertNotNull(f); diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java index 5a585dfe2a..764d7eb8ef 100644 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java @@ -129,12 +129,12 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty"); final Map shardMap = new HashMap<>(); - for (final DOMDataTreeIdentifier s : subtrees) { + for (final DOMDataTreeIdentifier subtree : subtrees) { // Attempting to create a disconnected producer -- all subtrees have to be unclaimed - final DOMDataTreeProducer producer = findProducer(s); - Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", s, producer); + final DOMDataTreeProducer producer = findProducer(subtree); + Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", subtree, producer); - shardMap.put(s, shards.lookup(s).getValue().getInstance()); + shardMap.put(subtree, shards.lookup(subtree).getValue().getInstance()); } return createProducer(shardMap); diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java index 83a073d701..f16f0ad1f3 100644 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java @@ -8,112 +8,91 @@ package org.opendaylight.mdsal.dom.broker; import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableBiMap.Builder; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; -import java.util.Queue; import java.util.Set; import javax.annotation.concurrent.GuardedBy; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException; import org.opendaylight.mdsal.dom.api.DOMDataTreeShard; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; -import org.opendaylight.mdsal.dom.spi.store.DOMStore; -import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionChain; -import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; +import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class); - private final BiMap shardToChain; private final Map idToShard; private final ShardedDOMDataTree dataTree; + private final BiMap shardToProducer; + private final BiMap idToProducer; + @GuardedBy("this") - private Map children = Collections.emptyMap(); + private DOMDataTreeCursorAwareTransaction openTx; @GuardedBy("this") - private DOMDataTreeWriteTransaction openTx; + private Map children = Collections.emptyMap(); @GuardedBy("this") private boolean closed; @GuardedBy("this") private ShardedDOMDataTreeListenerContext attachedListener; - ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree, final Map shardMap, final Set shards) { + ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree, + final Map shardMap, + final Multimap shardToId) { this.dataTree = Preconditions.checkNotNull(dataTree); - // Create shard -> chain map - final Builder cb = ImmutableBiMap.builder(); - final Queue es = new LinkedList<>(); - - for (final DOMDataTreeShard s : shards) { - if (s instanceof DOMStore) { - try { - final DOMStoreTransactionChain c = ((DOMStore)s).createTransactionChain(); - LOG.trace("Using DOMStore chain {} to access shard {}", c, s); - cb.put(s, c); - } catch (final Exception e) { - LOG.error("Failed to instantiate chain for shard {}", s, e); - es.add(e); + final Builder builder = ImmutableBiMap.builder(); + final Builder idToProducerBuilder = ImmutableBiMap.builder(); + for (final Entry> entry : shardToId.asMap().entrySet()) { + if (entry.getKey() instanceof WriteableDOMDataTreeShard) { + //create a single producer for all prefixes in a single shard + final DOMDataTreeShardProducer producer = ((WriteableDOMDataTreeShard) entry.getKey()).createProducer(entry.getValue()); + builder.put(entry.getKey(), producer); + + // id mapped to producers + for (final DOMDataTreeIdentifier id : entry.getValue()) { + idToProducerBuilder.put(id, producer); } } else { - LOG.error("Unhandled shard instance type {}", s.getClass()); + LOG.error("Unable to create a producer for shard that's not a WriteableDOMDataTreeShard"); } } - this.shardToChain = cb.build(); - - // An error was encountered, close chains and report the error - if (shardToChain.size() != shards.size()) { - for (final DOMStoreTransactionChain c : shardToChain.values()) { - try { - c.close(); - } catch (final Exception e) { - LOG.warn("Exception raised while closing chain {}", c, e); - } - } - - final IllegalStateException e = new IllegalStateException("Failed to completely allocate contexts", es.poll()); - while (!es.isEmpty()) { - e.addSuppressed(es.poll()); - } + this.shardToProducer = builder.build(); + this.idToProducer = idToProducerBuilder.build(); + idToShard = ImmutableMap.copyOf(shardMap); + } - throw e; + static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree, final Map shardMap) { + final Multimap shardToIdetifiers = ArrayListMultimap.create(); + // map which identifier belongs to which shard + for (final Entry entry : shardMap.entrySet()) { + shardToIdetifiers.put(entry.getValue(), entry.getKey()); } - idToShard = ImmutableMap.copyOf(shardMap); + return new ShardedDOMDataTreeProducer(dataTree, shardMap, shardToIdetifiers); } @Override - public synchronized DOMDataTreeWriteTransaction createTransaction(final boolean isolated) { + public synchronized DOMDataTreeCursorAwareTransaction createTransaction(final boolean isolated) { Preconditions.checkState(!closed, "Producer is already closed"); Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx); - // Allocate backing transactions - final Map shardToTx = new HashMap<>(); - for (final Entry e : shardToChain.entrySet()) { - shardToTx.put(e.getKey(), e.getValue().newWriteOnlyTransaction()); - } + this.openTx = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, children); - // Create the ID->transaction map - final ImmutableMap.Builder b = ImmutableMap.builder(); - for (final Entry e : idToShard.entrySet()) { - b.put(e.getKey(), shardToTx.get(e.getValue())); - } - - final ShardedDOMDataWriteTransaction ret = new ShardedDOMDataWriteTransaction(this, b.build()); - openTx = ret; - return ret; + return openTx; } @GuardedBy("this") @@ -169,7 +148,7 @@ final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { return ret; } - boolean isDelegatedToChild(DOMDataTreeIdentifier path) { + boolean isDelegatedToChild(final DOMDataTreeIdentifier path) { for (final DOMDataTreeIdentifier c : children.keySet()) { if (c.contains(path)) { return true; @@ -191,24 +170,11 @@ final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { } } - static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree, final Map shardMap) { - /* - * FIXME: we do not allow multiple multiple shards in a producer because we do not implement the - * synchronization primitives yet - */ - final Set shards = ImmutableSet.copyOf(shardMap.values()); - if (shards.size() > 1) { - throw new UnsupportedOperationException("Cross-shard producers are not supported yet"); - } - - return new ShardedDOMDataTreeProducer(dataTree, shardMap, shards); - } - Set getSubtrees() { return idToShard.keySet(); } - synchronized void cancelTransaction(final ShardedDOMDataWriteTransaction transaction) { + synchronized void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) { if (!openTx.equals(transaction)) { LOG.warn("Transaction {} is not open in producer {}", transaction, this); return; @@ -218,12 +184,12 @@ final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { openTx = null; } - synchronized void transactionSubmitted(ShardedDOMDataWriteTransaction transaction) { + synchronized void transactionSubmitted(final ShardedDOMDataTreeWriteTransaction transaction) { Preconditions.checkState(openTx.equals(transaction)); openTx = null; } - synchronized void boundToListener(ShardedDOMDataTreeListenerContext listener) { + synchronized void boundToListener(final ShardedDOMDataTreeListenerContext listener) { // FIXME: Add option to dettach Preconditions.checkState(this.attachedListener == null, "Producer %s is already attached to other listener.", diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java new file mode 100644 index 0000000000..d6f44bab17 --- /dev/null +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2015 Cisco Systems, 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.dom.broker; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.NotThreadSafe; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +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; + +@NotThreadSafe +final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAwareTransaction { + private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeWriteTransaction.class); + private static final AtomicLong COUNTER = new AtomicLong(); + private final Map idToTransaction; + private final ShardedDOMDataTreeProducer producer; + private final String identifier; + private final Set childBoundaries = new HashSet<>(); + @GuardedBy("this") + private boolean closed = false; + + @GuardedBy("this") + private DOMDataTreeWriteCursor openCursor; + + ShardedDOMDataTreeWriteTransaction(final ShardedDOMDataTreeProducer producer, + final Map idToProducer, + final Map childProducers) { + this.producer = Preconditions.checkNotNull(producer); + idToTransaction = new HashMap<>(); + Preconditions.checkNotNull(idToProducer).forEach((id, prod) -> idToTransaction.put(id, prod.createTransaction())); + this.identifier = "SHARDED-DOM-" + COUNTER.getAndIncrement(); + childProducers.forEach((id, prod) -> childBoundaries.add(id.getRootIdentifier())); + } + + // FIXME: use atomic operations + @GuardedBy("this") + private DOMDataTreeShardWriteTransaction lookup(final DOMDataTreeIdentifier prefix) { + for (final Entry e : idToTransaction.entrySet()) { + if (e.getKey().contains(prefix)) { + Preconditions.checkArgument(!producer.isDelegatedToChild(prefix), + "Path %s is delegated to child producer.", + prefix); + return e.getValue(); + } + } + throw new IllegalArgumentException(String.format("Path %s is not accessible from transaction %s", prefix, this)); + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public synchronized boolean cancel() { + if (closed) { + return false; + } + + LOG.debug("Cancelling transaction {}", identifier); + if (openCursor != null) { + openCursor.close(); + } + for (final DOMDataTreeShardWriteTransaction tx : ImmutableSet.copyOf(idToTransaction.values())) { + tx.close(); + } + + closed = true; + producer.cancelTransaction(this); + return true; + } + + @Override + public synchronized DOMDataTreeWriteCursor createCursor(final DOMDataTreeIdentifier prefix) { + Preconditions.checkState(!closed, "Transaction is closed already"); + Preconditions.checkState(openCursor == null, "There is still a cursor open"); + final DOMDataTreeShardWriteTransaction lookup = lookup(prefix); + openCursor = new DelegatingCursor(lookup.createCursor(prefix), prefix); + return openCursor; + } + + @Override + public synchronized CheckedFuture submit() { + Preconditions.checkState(!closed, "Transaction %s is already closed", identifier); + Preconditions.checkState(openCursor == null, "Cannot submit transaction while there is a cursor open"); + + final Set txns = ImmutableSet.copyOf(idToTransaction.values()); + for (final DOMDataTreeShardWriteTransaction tx : txns) { + tx.ready(); + } + producer.transactionSubmitted(this); + try { + return Futures.immediateCheckedFuture(new SubmitCoordinationTask(identifier, txns).call()); + } catch (final TransactionCommitFailedException e) { + return Futures.immediateFailedCheckedFuture(e); + } + } + + synchronized void cursorClosed() { + openCursor = null; + } + + private class DelegatingCursor implements DOMDataTreeWriteCursor { + + private final DOMDataTreeWriteCursor delegate; + private final Deque path = new LinkedList<>(); + + public DelegatingCursor(final DOMDataTreeWriteCursor delegate, final DOMDataTreeIdentifier rootPosition) { + this.delegate = delegate; + path.addAll(rootPosition.getRootIdentifier().getPathArguments()); + } + + @Override + public void enter(@Nonnull final PathArgument child) { + checkAvailable(child); + path.push(child); + delegate.enter(child); + } + + @Override + public void enter(@Nonnull final PathArgument... path) { + for (final PathArgument pathArgument : path) { + enter(pathArgument); + } + } + + @Override + public void enter(@Nonnull final Iterable path) { + for (final PathArgument pathArgument : path) { + enter(pathArgument); + } + } + + @Override + public void exit() { + path.pop(); + delegate.exit(); + } + + @Override + public void exit(final int depth) { + for (int i = 0; i < depth; i++) { + path.pop(); + } + delegate.exit(depth); + } + + @Override + public void close() { + delegate.close(); + cursorClosed(); + } + + @Override + public void delete(final PathArgument child) { + checkAvailable(child); + delegate.delete(child); + } + + @Override + public void merge(final PathArgument child, final NormalizedNode data) { + checkAvailable(child); + delegate.merge(child, data); + } + + @Override + public void write(final PathArgument child, final NormalizedNode data) { + checkAvailable(child); + delegate.write(child, data); + } + + void checkAvailable(final PathArgument child) { + path.add(child); + final YangInstanceIdentifier yid = YangInstanceIdentifier.create(path); + childBoundaries.forEach(id -> { + if (id.contains(yid)) { + path.removeLast(); + throw new IllegalArgumentException("Path {" + yid + "} is not available to this cursor since it's already claimed by a child producer"); + } + }); + path.removeLast(); + } + } + + private static class SubmitCoordinationTask implements Callable { + + private static final Logger LOG = LoggerFactory.getLogger(SubmitCoordinationTask.class); + + private final String identifier; + private final Collection transactions; + + SubmitCoordinationTask(final String identifier, + final Collection transactions) { + this.identifier = identifier; + this.transactions = transactions; + } + + @Override + public Void call() throws TransactionCommitFailedException { + + try { + LOG.debug("Producer {}, submit started", identifier); + submitBlocking(); + + return null; + } catch (final TransactionCommitFailedException e) { + LOG.warn("Failure while submitting transaction for producer {}", identifier, e); + //FIXME abort here + throw e; + } + } + + void submitBlocking() throws TransactionCommitFailedException { + for (final ListenableFuture commit : submitAll()) { + try { + commit.get(); + } catch (InterruptedException | ExecutionException e) { + throw new TransactionCommitFailedException("Submit failed", e); + } + } + } + + private ListenableFuture[] submitAll() { + final ListenableFuture[] ops = new ListenableFuture[transactions.size()]; + int i = 0; + for (final DOMDataTreeShardWriteTransaction tx : transactions) { + ops[i++] = tx.submit(); + } + return ops; + } + } +} diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java deleted file mode 100644 index 5a0833654d..0000000000 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, 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.dom.broker; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.Futures; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.concurrent.GuardedBy; -import javax.annotation.concurrent.NotThreadSafe; -import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; -import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; -import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort; -import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@NotThreadSafe -final class ShardedDOMDataWriteTransaction implements DOMDataTreeWriteTransaction { - private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataWriteTransaction.class); - private static final AtomicLong COUNTER = new AtomicLong(); - private final Map idToTransaction; - private final ShardedDOMDataTreeProducer producer; - private final String identifier; - @GuardedBy("this") - private boolean closed = false; - - ShardedDOMDataWriteTransaction(final ShardedDOMDataTreeProducer producer, final Map idToTransaction) { - this.producer = Preconditions.checkNotNull(producer); - this.idToTransaction = Preconditions.checkNotNull(idToTransaction); - this.identifier = "SHARDED-DOM-" + COUNTER.getAndIncrement(); - } - - // FIXME: use atomic operations - @GuardedBy("this") - private DOMStoreWriteTransaction lookup(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - final DOMDataTreeIdentifier id = new DOMDataTreeIdentifier(store, path); - - for (final Entry e : idToTransaction.entrySet()) { - if (e.getKey().contains(id)) { - Preconditions.checkArgument(!producer.isDelegatedToChild(id), - "Path %s is delegated to child producer.", - id); - return e.getValue(); - } - } - throw new IllegalArgumentException(String.format("Path %s is not acessible from transaction %s", id, this)); - } - - @Override - public String getIdentifier() { - return identifier; - } - - @Override - public synchronized boolean cancel() { - if (closed) { - return false; - } - - LOG.debug("Cancelling transaction {}", identifier); - for (final DOMStoreWriteTransaction tx : ImmutableSet.copyOf(idToTransaction.values())) { - tx.close(); - } - - closed = true; - producer.cancelTransaction(this); - return true; - } - - @Override - public synchronized CheckedFuture submit() { - Preconditions.checkState(!closed, "Transaction %s is already closed", identifier); - - final Set txns = ImmutableSet.copyOf(idToTransaction.values()); - final List cohorts = new ArrayList<>(txns.size()); - for (final DOMStoreWriteTransaction tx : txns) { - cohorts.add(tx.ready()); - } - producer.transactionSubmitted(this); - try { - return Futures.immediateCheckedFuture(new CommitCoordinationTask(this, cohorts, null).call()); - } catch (final TransactionCommitFailedException e) { - return Futures.immediateFailedCheckedFuture(e); - } - } - - @Override - public synchronized void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - lookup(store, path).delete(path); - } - - @Override - public synchronized void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - lookup(store, path).write(path, data); - } - - @Override - public synchronized void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - lookup(store, path).merge(path, data); - } -} diff --git a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransactionTest.java b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransactionTest.java index 380633e247..3cf4bfd61d 100644 --- a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransactionTest.java +++ b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransactionTest.java @@ -9,109 +9,201 @@ package org.opendaylight.mdsal.dom.broker; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - -import java.util.ArrayList; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.MockitoAnnotations.initMocks; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; import org.junit.Test; +import org.mockito.Mock; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; -import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort; -import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction; +import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; public class ShardedDOMDataWriteTransactionTest { private static final Map>> TEST_MAP = new HashMap<>(); + private static final DOMDataTreeIdentifier ROOT_ID = + new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.EMPTY); + + @Mock + private WriteableDOMDataTreeShard rootShard; + + @Mock + private DOMDataTreeShardProducer mockedProducer; + @Test public void basicTests() throws Exception { + + initMocks(this); + + doReturn(new TestDOMShardWriteTransaction()).when(mockedProducer).createTransaction(); + doReturn(mockedProducer).when(rootShard).createProducer(any(Collection.class)); + final ShardedDOMDataTree shardedDOMDataTree = new ShardedDOMDataTree(); - final ShardedDOMDataTreeProducer shardedDOMDataTreeProducer = - new ShardedDOMDataTreeProducer(shardedDOMDataTree, new HashMap<>(), new HashSet<>()); - final TestDOMStoreWriteTransaction testDOMStoreWriteTransaction = - new TestDOMStoreWriteTransaction(); - final Map idToTransaction = - new HashMap<>(); + shardedDOMDataTree.registerDataTreeShard(ROOT_ID, rootShard); final YangInstanceIdentifier yangInstanceIdentifier = YangInstanceIdentifier.of(QName.create("test")); - idToTransaction.put(new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier), - testDOMStoreWriteTransaction); - final ShardedDOMDataWriteTransaction shardedDOMDataWriteTransaction = - new ShardedDOMDataWriteTransaction(shardedDOMDataTreeProducer, idToTransaction); - final ShardedDOMDataWriteTransaction otherShardedDOMDataWriteTransaction = - new ShardedDOMDataWriteTransaction(shardedDOMDataTreeProducer, idToTransaction); + final DOMDataTreeProducer producer = shardedDOMDataTree.createProducer(Collections.singletonList(ROOT_ID)); + final DOMDataTreeCursorAwareTransaction transaction = producer.createTransaction(false); + final DOMDataTreeWriteCursor cursor = transaction.createCursor(ROOT_ID); + assertNotNull(cursor); assertFalse(TEST_MAP.containsKey(yangInstanceIdentifier)); - shardedDOMDataWriteTransaction.put( - LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier, TestUtils.TEST_CONTAINER); + cursor.write(yangInstanceIdentifier.getLastPathArgument(), TestUtils.TEST_CONTAINER); assertTrue(TEST_MAP.containsKey(yangInstanceIdentifier)); - shardedDOMDataWriteTransaction.delete( - LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier); + + cursor.delete(yangInstanceIdentifier.getLastPathArgument()); assertFalse(TEST_MAP.containsKey(yangInstanceIdentifier)); - shardedDOMDataWriteTransaction.merge( - LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier, TestUtils.TEST_CONTAINER); + + cursor.merge(yangInstanceIdentifier.getLastPathArgument(), TestUtils.TEST_CONTAINER); assertTrue(TEST_MAP.get(yangInstanceIdentifier).contains(TestUtils.TEST_CONTAINER)); try { - shardedDOMDataWriteTransaction.put( - LogicalDatastoreType.CONFIGURATION, yangInstanceIdentifier, TestUtils.TEST_CONTAINER); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains(LogicalDatastoreType.CONFIGURATION.toString())); + producer.createTransaction(false); + fail("Should have failed, there's still a tx open"); + } catch (final IllegalStateException e) { + assertTrue(e.getMessage().contains("open")); + } + + cursor.close(); + try { + transaction.createCursor(new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.EMPTY)); + fail("Should have failed, config ds not available to this tx"); + } catch (final IllegalArgumentException e) { + assertTrue(e.getMessage().contains("not accessible")); } - shardedDOMDataTreeProducer.createTransaction(true); - assertTrue(shardedDOMDataWriteTransaction.cancel()); - assertFalse(shardedDOMDataWriteTransaction.cancel()); + assertTrue(transaction.cancel()); + assertFalse(transaction.cancel()); - assertTrue(shardedDOMDataWriteTransaction.getIdentifier().matches("^SHARDED-DOM-\\d$")); - assertNotEquals(shardedDOMDataWriteTransaction.getIdentifier(), - otherShardedDOMDataWriteTransaction.getIdentifier()); + final DOMDataTreeCursorAwareTransaction newTx = producer.createTransaction(false); + assertTrue("Transaction identifier incorrect " + transaction.getIdentifier(), ((String) transaction.getIdentifier()).contains("SHARDED-DOM-")); + assertNotEquals(transaction.getIdentifier(), + newTx.getIdentifier()); } - private final class TestDOMStoreWriteTransaction implements DOMStoreWriteTransaction { + private final class TestDOMShardWriteTransaction implements DOMDataTreeShardWriteTransaction { + + @Nonnull + @Override + public DOMDataTreeWriteCursor createCursor(@Nonnull final DOMDataTreeIdentifier prefix) { + return new TestCursor(); + } @Override - public void write(YangInstanceIdentifier path, NormalizedNode data) { - List> dataList = TEST_MAP.get(path); - if(dataList == null) dataList = new ArrayList<>(); - dataList.add(data); - TEST_MAP.put(path, dataList); + public void ready() { + } @Override - public void merge(YangInstanceIdentifier path, NormalizedNode data) { - List> dataList = TEST_MAP.get(path); - if(dataList == null) dataList = new ArrayList<>(); - dataList.add(data); - TEST_MAP.put(path, dataList); + public void close() { + } @Override - public void delete(YangInstanceIdentifier path) { - TEST_MAP.remove(path); + public ListenableFuture submit() { + return null; } @Override - public DOMStoreThreePhaseCommitCohort ready() { + public ListenableFuture validate() { return null; } @Override - public Object getIdentifier() { + public ListenableFuture prepare() { return null; } + @Override + public ListenableFuture commit() { + return null; + } + } + + private final class TestCursor implements DOMDataTreeWriteCursor { + + private final Deque stack = new ArrayDeque<>(); + + @Override + public void delete(final PathArgument child) { + final ArrayDeque newPath = new ArrayDeque<>(stack); + newPath.push(child); + TEST_MAP.remove(YangInstanceIdentifier.create(newPath)); + } + + @Override + public void merge(final PathArgument child, final NormalizedNode data) { + final ArrayDeque newPath = new ArrayDeque<>(stack); + newPath.push(child); + final List> dataList = TEST_MAP.get(YangInstanceIdentifier.create(newPath)); + if (dataList != null) { + dataList.add(data); + } else { + TEST_MAP.put(YangInstanceIdentifier.create(newPath), Lists.newArrayList(data)); + } + } + + @Override + public void write(final PathArgument child, final NormalizedNode data) { + final ArrayDeque newPath = new ArrayDeque<>(stack); + newPath.push(child); + TEST_MAP.put(YangInstanceIdentifier.create(newPath), Lists.newArrayList(data)); + } + + @Override + public void enter(@Nonnull final PathArgument child) { + stack.push(child); + } + + @Override + public void enter(@Nonnull final PathArgument... path) { + for (final PathArgument pathArgument : path) { + stack.push(pathArgument); + } + } + + @Override + public void enter(@Nonnull final Iterable path) { + path.forEach(stack::push); + } + + @Override + public void exit() { + stack.pop(); + } + + @Override + public void exit(final int depth) { + stack.pop(); + } + @Override public void close() { - // NOOP + } } } \ No newline at end of file diff --git a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerMultiShardTest.java b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerMultiShardTest.java index 42ad3d3c09..24a79b84ae 100644 --- a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerMultiShardTest.java +++ b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerMultiShardTest.java @@ -2,7 +2,6 @@ package org.opendaylight.mdsal.dom.broker.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyCollection; import static org.mockito.Matchers.anyMap; @@ -62,7 +61,7 @@ public class ShardedDOMDataTreeProducerMultiShardTest { static { try { schemaContext = TestModel.createTestContext(); - } catch (ReactorException e) { + } catch (final ReactorException e) { LOG.error("Unable to create schema context for TestModel", e); } } diff --git a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerSingleShardTest.java b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerSingleShardTest.java index b940565013..7a9ebdd766 100644 --- a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerSingleShardTest.java +++ b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerSingleShardTest.java @@ -8,9 +8,11 @@ package org.opendaylight.mdsal.dom.broker.test; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import com.google.common.util.concurrent.Futures; import java.util.Collection; import java.util.Collections; import org.junit.Before; @@ -18,22 +20,22 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException; import org.opendaylight.mdsal.dom.api.DOMDataTreeService; -import org.opendaylight.mdsal.dom.api.DOMDataTreeShard; import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingConflictException; -import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.broker.ShardedDOMDataTree; import org.opendaylight.mdsal.dom.broker.test.util.TestModel; -import org.opendaylight.mdsal.dom.spi.store.DOMStore; import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionChain; import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction; +import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; public class ShardedDOMDataTreeProducerSingleShardTest { @@ -46,7 +48,7 @@ public class ShardedDOMDataTreeProducerSingleShardTest { private static final Collection SUBTREES_ROOT = Collections.singleton(ROOT_ID); private static final Collection SUBTREES_TEST = Collections.singleton(TEST_ID); - interface MockTestShard extends DOMDataTreeShard, DOMStore { + interface MockTestShard extends WriteableDOMDataTreeShard { } @@ -57,6 +59,12 @@ public class ShardedDOMDataTreeProducerSingleShardTest { @Mock(name = "storeWriteTx") private DOMStoreWriteTransaction writeTxMock; + @Mock + private DOMDataTreeShardProducer producerMock; + + @Mock + private DOMDataTreeShardWriteTransaction shardTxMock; + @Mock(name = "storeTxChain") private DOMStoreTransactionChain txChainMock; @@ -72,9 +80,10 @@ public class ShardedDOMDataTreeProducerSingleShardTest { shardReg = impl.registerDataTreeShard(ROOT_ID, rootShard); doReturn("rootShard").when(rootShard).toString(); - doReturn(txChainMock).when(rootShard).createTransactionChain(); - doReturn(writeTxMock).when(txChainMock).newWriteOnlyTransaction(); - doReturn(TestCommitCohort.ALLWAYS_SUCCESS).when(writeTxMock).ready(); + doReturn(producerMock).when(rootShard).createProducer(any(Collection.class)); + doReturn(shardTxMock).when(producerMock).createTransaction(); + doNothing().when(shardTxMock).ready(); + doReturn(Futures.immediateFuture(null)).when(shardTxMock).submit(); producer = treeService.createProducer(SUBTREES_ROOT); } @@ -92,7 +101,7 @@ public class ShardedDOMDataTreeProducerSingleShardTest { @Test public void closeWithTxSubmitted() throws DOMDataTreeProducerException { - DOMDataTreeWriteTransaction tx = producer.createTransaction(false); + DOMDataTreeCursorAwareTransaction tx = producer.createTransaction(false); tx.submit(); producer.close(); } @@ -121,9 +130,8 @@ public class ShardedDOMDataTreeProducerSingleShardTest { public void writeChildProducerDataToParentTx() { DOMDataTreeProducer childProducer = producer.createProducer(SUBTREES_TEST); assertNotNull(childProducer); - DOMDataTreeWriteTransaction parentTx = producer.createTransaction(true); - parentTx.put(TEST_ID.getDatastoreType(), TEST_ID.getRootIdentifier(), - ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + DOMDataTreeCursorAwareTransaction parentTx = producer.createTransaction(true); + parentTx.createCursor(TEST_ID); } @Test diff --git a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java new file mode 100644 index 0000000000..9b828c23fc --- /dev/null +++ b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java @@ -0,0 +1,230 @@ +package org.opendaylight.mdsal.dom.broker.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyCollection; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMDataTreeListener; +import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; +import org.opendaylight.mdsal.dom.broker.ShardedDOMDataTree; +import org.opendaylight.mdsal.dom.broker.test.util.TestModel; +import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataTreeShard; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ShardedDOMDataTreeTest { + + private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducerMultiShardTest.class); + + private static SchemaContext schemaContext = null; + + static { + try { + schemaContext = TestModel.createTestContext(); + } catch (final ReactorException e) { + LOG.error("Unable to create schema context for TestModel", e); + } + } + + private static final DOMDataTreeIdentifier ROOT_ID = + new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.EMPTY); + private static final DOMDataTreeIdentifier TEST_ID = + new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TestModel.TEST_PATH); + + private static final DOMDataTreeIdentifier INNER_CONTAINER_ID = + new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TestModel.INNER_CONTAINER_PATH); + + private InMemoryDOMDataTreeShard rootShard; + + private ShardedDOMDataTree dataTreeService; + private ListenerRegistration rootShardReg; + + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Captor + private ArgumentCaptor> captorForChanges; + @Captor + private ArgumentCaptor>> captorForSubtrees; + + private final ContainerNode crossShardContainer = createCrossShardContainer(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + rootShard = InMemoryDOMDataTreeShard.create(ROOT_ID, executor, 5000); + rootShard.onGlobalContextUpdated(schemaContext); + + final ShardedDOMDataTree dataTree = new ShardedDOMDataTree(); + rootShardReg = dataTree.registerDataTreeShard(ROOT_ID, rootShard); + + dataTreeService = dataTree; + } + + @Test(expected = IllegalArgumentException.class) + public void testProducerPathContention() throws Exception { + final DOMDataTreeProducer p1 = dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); + final DOMDataTreeProducer p2 = dataTreeService.createProducer(Collections.singletonList(TEST_ID)); + } + + @Test + public void testSingleShardWrite() throws Exception { + final DOMDataTreeListener mockedDataTreeListener = Mockito.mock(DOMDataTreeListener.class); + doNothing().when(mockedDataTreeListener).onDataTreeChanged(anyCollection(), anyMap()); + + dataTreeService.registerListener(mockedDataTreeListener, Collections.singletonList(INNER_CONTAINER_ID), true, Collections.emptyList()); + + final DOMDataTreeProducer producer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); + DOMDataTreeCursorAwareTransaction tx = producer.createTransaction(false); + DOMDataTreeWriteCursor cursor = tx.createCursor(ROOT_ID); + assertNotNull(cursor); + + cursor.write(TEST_ID.getRootIdentifier().getLastPathArgument(), crossShardContainer); + + try { + tx.submit().checkedGet(); + fail("There's still an open cursor"); + } catch (final IllegalStateException e) { + assertTrue(e.getMessage().contains("cursor open")); + } + + cursor.close(); + tx.submit().checkedGet(); + + tx = producer.createTransaction(false); + cursor = tx.createCursor(TEST_ID); + assertNotNull(cursor); + + cursor.delete(TestModel.INNER_CONTAINER_PATH.getLastPathArgument()); + cursor.close(); + tx.submit().checkedGet(); + + verify(mockedDataTreeListener, timeout(1000).times(2)).onDataTreeChanged(captorForChanges.capture(), captorForSubtrees.capture()); + final List> capturedValue = captorForChanges.getAllValues(); + assertTrue(capturedValue.size() == 2); + + final ContainerNode capturedChange = (ContainerNode) capturedValue.get(0).iterator().next().getRootNode().getDataAfter().get(); + final ContainerNode innerContainerVerify = (ContainerNode) crossShardContainer.getChild(TestModel.INNER_CONTAINER_PATH.getLastPathArgument()).get(); + assertEquals(innerContainerVerify, capturedChange); + + verifyNoMoreInteractions(mockedDataTreeListener); + } + + @Test + public void testMultipleProducerCursorCreation() throws Exception { + + final DOMDataTreeProducer rootProducer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); + DOMDataTreeCursorAwareTransaction rootTx = rootProducer.createTransaction(false); + //check if we can create cursor where the new producer will be + DOMDataTreeWriteCursor rootTxCursor = rootTx.createCursor(INNER_CONTAINER_ID); + assertNotNull(rootTxCursor); + rootTxCursor.close(); + + try { + rootProducer.createProducer(Collections.singletonList(INNER_CONTAINER_ID)); + fail("Should've failed there is still a tx open"); + } catch (final IllegalStateException e) { + assertTrue(e.getMessage().contains("open")); + } + + assertTrue(rootTx.cancel()); + + final DOMDataTreeProducer innerContainerProducer = rootProducer.createProducer(Collections.singletonList(INNER_CONTAINER_ID)); + + rootTx = rootProducer.createTransaction(false); + try { + rootTx.createCursor(INNER_CONTAINER_ID); + fail("Subtree should not be available to this producer"); + } catch (final IllegalArgumentException e) { + assertTrue(e.getMessage().contains("delegated to child producer")); + } + + rootTxCursor = rootTx.createCursor(TEST_ID); + assertNotNull(rootTxCursor); + try { + rootTxCursor.enter(INNER_CONTAINER_ID.getRootIdentifier().getLastPathArgument()); + fail("Cursor should not have this subtree available"); + } catch (final IllegalArgumentException e) { + assertTrue(e.getMessage().contains("not available to this cursor")); + } + + try { + rootTxCursor.write(TestModel.INNER_CONTAINER_PATH.getLastPathArgument(), + ImmutableContainerNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_CONTAINER)) + .build()); + fail("Cursor should not have this subtree available"); + } catch (final IllegalArgumentException e) { + assertTrue(e.getMessage().contains("not available to this cursor")); + } + + final DOMDataTreeCursorAwareTransaction innerShardTx = innerContainerProducer.createTransaction(false); + final DOMDataTreeWriteCursor innerShardCursor = innerShardTx.createCursor(INNER_CONTAINER_ID); + assertNotNull(innerShardCursor); + } + + private ContainerNode createCrossShardContainer() { + final LeafNode shardedValue1 = + ImmutableLeafNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(TestModel.SHARDED_VALUE_1)).withValue("sharded value 1").build(); + final LeafNode shardedValue2 = + ImmutableLeafNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(TestModel.SHARDED_VALUE_2)).withValue("sharded value 2").build(); + + + final ContainerNode lowerShardContainer = ImmutableContainerNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(TestModel.ANOTHER_SHARD_CONTAINER)) + .withChild(ImmutableLeafNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(TestModel.ANOTHER_SHARD_VALUE)) + .withValue("testing-value") + .build()) + .build(); + + final ContainerNode containerNode = + ImmutableContainerNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_CONTAINER)) + .withChild(shardedValue1) + .withChild(shardedValue2) + .withChild(lowerShardContainer) + .build(); + + final ContainerNode testContainer = ImmutableContainerNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) + .withChild(containerNode) + .build(); + + return testContainer; + } +} diff --git a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/AbstractDOMShardTreeChangePublisher.java b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/AbstractDOMShardTreeChangePublisher.java index fc4ad229d9..086689f2c1 100644 --- a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/AbstractDOMShardTreeChangePublisher.java +++ b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/AbstractDOMShardTreeChangePublisher.java @@ -126,7 +126,6 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener { - // TODO should we synchronize the access to the dataTree snapshots? private final DataTree dataTree; private final YangInstanceIdentifier listenerPath; private final DOMDataTreeChangeListener delegate; @@ -145,14 +144,14 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC @Override public void onDataTreeChanged(@Nonnull final Collection changes) { LOG.debug("Received data changed {}", changes.iterator().next()); - final DataTreeCandidate newCandidate = applyChanges(changes); - delegate.onDataTreeChanged(Collections.singleton(newCandidate)); + delegate.onDataTreeChanged(changes); } void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection changes) { - onDataTreeChanged(changes.stream() + final List newCandidates = changes.stream() .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode())) - .collect(Collectors.toList())); + .collect(Collectors.toList()); + delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates))); } void addSubshard(final ChildShardContext context) { @@ -167,7 +166,7 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC } void close() { - for (ListenerRegistration registration : registrations.values()) { + for (final ListenerRegistration registration : registrations.values()) { registration.close(); } registrations.clear(); diff --git a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/DOMDataTreeShardWriteTransaction.java b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/DOMDataTreeShardWriteTransaction.java index b457ddb491..59fa4b0966 100644 --- a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/DOMDataTreeShardWriteTransaction.java +++ b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/DOMDataTreeShardWriteTransaction.java @@ -11,12 +11,12 @@ package org.opendaylight.mdsal.dom.store.inmemory; import com.google.common.annotations.Beta; import com.google.common.util.concurrent.ListenableFuture; import javax.annotation.Nonnull; +import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorProvider; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; -// FIXME: this should be moved to a separate package @Beta -public interface DOMDataTreeShardWriteTransaction { +public interface DOMDataTreeShardWriteTransaction extends DOMDataTreeCursorProvider { /** * Create a new write cursor. Any previous cursors have to be {@link DOMDataTreeWriteCursor#close()}d. * @@ -25,6 +25,7 @@ public interface DOMDataTreeShardWriteTransaction { * @throws IllegalStateException if a previous cursor has not been closed. * @throws NullPointerException if prefix is null. */ + @Override @Nonnull DOMDataTreeWriteCursor createCursor(@Nonnull DOMDataTreeIdentifier prefix); /** @@ -43,7 +44,6 @@ public interface DOMDataTreeShardWriteTransaction { ListenableFuture submit(); - //FIXME: remove these from the public api? ListenableFuture validate(); ListenableFuture prepare(); diff --git a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShard.java b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShard.java index 4a48a6471a..051d658a12 100644 --- a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShard.java +++ b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShard.java @@ -39,7 +39,6 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha private static final class SubshardProducerSpecification { private final Collection prefixes = new ArrayList<>(1); private final ChildShardContext shard; - SubshardProducerSpecification(final ChildShardContext subshard) { this.shard = Preconditions.checkNotNull(subshard); } @@ -60,9 +59,11 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha private final DOMDataTreePrefixTable childShardsTable = DOMDataTreePrefixTable.create(); private final Map childShards = new HashMap<>(); + private final DOMDataTreeIdentifier prefix; private final DataTree dataTree; + private SchemaContext schemaContext; private InMemoryDOMDataTreeShardChangePublisher shardChangePublisher; private InMemoryDOMDataTreeShard(final DOMDataTreeIdentifier prefix, final ExecutorService dataTreeChangeExecutor, @@ -83,6 +84,7 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha @Override public void onGlobalContextUpdated(final SchemaContext context) { dataTree.setSchemaContext(context); + schemaContext = context; } @Override @@ -108,7 +110,7 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha @Nonnull @Override - public ListenerRegistration registerTreeChangeListener(@Nonnull YangInstanceIdentifier treeId, @Nonnull L listener) { + public ListenerRegistration registerTreeChangeListener(@Nonnull final YangInstanceIdentifier treeId, @Nonnull final L listener) { return shardChangePublisher.registerTreeChangeListener(treeId, listener); } diff --git a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShardChangePublisher.java b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShardChangePublisher.java index 90c05f649c..8c6b8be450 100644 --- a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShardChangePublisher.java +++ b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShardChangePublisher.java @@ -50,10 +50,12 @@ final class InMemoryDOMDataTreeShardChangePublisher extends AbstractDOMShardTree } @Override - protected void notifyListeners(@Nonnull Collection> registrations, @Nonnull YangInstanceIdentifier path, @Nonnull DataTreeCandidateNode node) { + protected void notifyListeners(@Nonnull final Collection> registrations, + @Nonnull final YangInstanceIdentifier path, + @Nonnull final DataTreeCandidateNode node) { final DataTreeCandidate candidate = DataTreeCandidates.newDataTreeCandidate(path, node); - for (AbstractDOMDataTreeChangeListenerRegistration reg : registrations) { + for (final AbstractDOMDataTreeChangeListenerRegistration reg : registrations) { LOG.debug("Enqueueing candidate {} to registration {}", candidate, registrations); notificationManager.submitNotification(reg, candidate); } diff --git a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransaction.java b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransaction.java index 95b28bdeb6..1aaa459b23 100644 --- a/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransaction.java +++ b/dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransaction.java @@ -17,7 +17,6 @@ import com.google.common.util.concurrent.MoreExecutors; import java.util.ArrayList; import java.util.Iterator; import java.util.Map.Entry; -import java.util.concurrent.Callable; import java.util.concurrent.Executors; import org.opendaylight.mdsal.common.api.ReadFailedException; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; @@ -63,9 +62,9 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT void apply(final DOMDataTreeWriteCursor cursor, final YangInstanceIdentifier path, final NormalizedNode data) { int enterCount = 0; - Iterator it = path.getPathArguments().iterator(); + final Iterator it = path.getPathArguments().iterator(); while (it.hasNext()) { - PathArgument currentArg = it.next(); + final PathArgument currentArg = it.next(); if (it.hasNext()) { // We need to enter one level deeper, we are not at leaf (modified) node cursor.enter(currentArg); @@ -80,15 +79,15 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT private final ShardDataModification modification; private DOMDataTreeWriteCursor cursor; - private DataTree rootShardDataTree; + private final DataTree rootShardDataTree; private DataTreeModification rootModification = null; - private ArrayList cohorts = new ArrayList<>(); - private InMemoryDOMDataTreeShardChangePublisher changePublisher; + private final ArrayList cohorts = new ArrayList<>(); + private final InMemoryDOMDataTreeShardChangePublisher changePublisher; private boolean finished = false; // FIXME inject into shard? - private ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); InmemoryDOMDataTreeShardWriteTransaction(final ShardDataModification root, final DataTree rootShardDataTree, @@ -106,7 +105,7 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT } void delete(final YangInstanceIdentifier path) { - YangInstanceIdentifier relativePath = toRelative(path); + final YangInstanceIdentifier relativePath = toRelative(path); Preconditions.checkArgument(!YangInstanceIdentifier.EMPTY.equals(relativePath), "Deletion of shard root is not allowed"); SimpleCursorOperation.DELETE.apply(getCursor(), relativePath , null); @@ -121,33 +120,27 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT } private YangInstanceIdentifier toRelative(final YangInstanceIdentifier path) { - Optional relative = + final Optional relative = path.relativeTo(modification.getPrefix().getRootIdentifier()); Preconditions.checkArgument(relative.isPresent()); return relative.get(); } public CheckedFuture>, ReadFailedException> read(final YangInstanceIdentifier path) { - // FIXME: Implement this - return null; + throw new UnsupportedOperationException("Not implemented yet"); } public CheckedFuture exists(final YangInstanceIdentifier path) { - // TODO Auto-generated method stub - return null; - } - - - public Object getIdentifier() { - // TODO Auto-generated method stub - return null; + throw new UnsupportedOperationException("Not implemented yet"); } @Override public void close() { Preconditions.checkState(!finished, "Attempting to close an already finished transaction."); modification.closeTransactions(); - cursor.close(); + if (cursor != null) { + cursor.close(); + } finished = true; } @@ -170,7 +163,7 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT rootModification = modification.seal(); cohorts.add(new InMemoryDOMDataTreeShardThreePhaseCommitCohort(rootShardDataTree, rootModification, changePublisher)); - for (Entry entry : modification.getChildShards().entrySet()) { + for (final Entry entry : modification.getChildShards().entrySet()) { cohorts.add(new ForeignShardThreePhaseCommitCohort(entry.getKey(), entry.getValue())); } finished = true; @@ -220,8 +213,8 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT public DOMDataTreeWriteCursor createCursor(final DOMDataTreeIdentifier prefix) { Preconditions.checkState(!finished, "Transaction is finished/closed already."); Preconditions.checkState(cursor == null, "Previous cursor wasn't closed"); - DOMDataTreeWriteCursor ret = getCursor(); - YangInstanceIdentifier relativePath = toRelative(prefix.getRootIdentifier()); + final DOMDataTreeWriteCursor ret = getCursor(); + final YangInstanceIdentifier relativePath = toRelative(prefix.getRootIdentifier()); ret.enter(relativePath.getPathArguments()); return ret; } -- 2.36.6