<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-common</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
--- /dev/null
+/*
+ * 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
+ <T extends DataObject> DataTreeWriteCursor createCursor(@Nonnull DataTreeIdentifier<T> 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 <tt>false</tt> if the task could not be cancelled, typically because it has already
+ * completed normally; <tt>true</tt> 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.
+ * <p>
+ * <b>Note:</b> 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
+ * <code>createCursorCursor(DOMDataTreeIdentifier</code>
+ * 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<Void,TransactionCommitFailedException> submit();
+}
--- /dev/null
+/*
+ * 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<PathArgument> path);
+
+ /**
+ * Move the cursor up to the parent of current position. This is equivalent of invoking
+ * <code>exit(1)</code>.
+ *
+ * @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();
+}
--- /dev/null
+/*
+ * 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
+ <T extends DataObject> DataTreeCursor createCursor(@Nonnull DataTreeIdentifier<T> path);
+}
* 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
* correct operation.
*/
@Nonnull
- WriteTransaction createTransaction(boolean isolated);
+ CursorAwareWriteTransaction createTransaction(boolean isolated);
/**
* {@inheritDoc}
--- /dev/null
+/*
+ * 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.
+ */
+ <T extends DataObject> 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.
+ */
+ <T extends DataObject> void write(PathArgument child, T data);
+}
--- /dev/null
+/*
+ * 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<T extends DOMDataTreeCursorAwareTransaction> 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 <T extends DataObject> DataTreeWriteCursor createCursor(final DataTreeIdentifier<T> 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<Void, TransactionCommitFailedException> submit() {
+ return delegate.submit();
+ }
+
+}
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;
}
@Override
- public WriteTransaction createTransaction(final boolean isolated) {
- final DOMDataTreeWriteTransaction domTx = delegate.createTransaction(isolated);
- return new BindingDOMWriteTransactionAdapter<DOMDataTreeWriteTransaction>(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,
--- /dev/null
+/*
+ * 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<T extends DOMDataTreeWriteCursor> implements DataTreeWriteCursor {
+
+ private T delegate;
+ private BindingToNormalizedNodeCodec codec;
+ private final Deque<PathArgument> 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 <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> convertToNormalized(final PathArgument child, final T data) {
+ stack.push(child);
+ final InstanceIdentifier<?> iid = InstanceIdentifier.create(stack);
+ final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry = codec.toNormalizedNode(new SimpleEntry<>(iid, data));
+ stack.pop();
+ return entry;
+ }
+
+ @Override
+ public void delete(final PathArgument child) {
+ delegate.delete(convertToNormalized(child));
+ }
+
+ @Override
+ public <T extends DataObject> void merge(final PathArgument child, final T data) {
+ final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry = convertToNormalized(child, data);
+ delegate.merge(entry.getKey().getLastPathArgument(), entry.getValue());
+ }
+
+ @Override
+ public <T extends DataObject> void write(PathArgument child, T data) {
+ final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> 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<PathArgument> 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();
+ }
+}
*
* @param path Subtree path on which commit cohort operates.
* @param cohort Commit cohort
+ * @param <T> Cohort subclass
* @return Registaration object for DOM Data Three Commit cohort.
*/
<T extends DOMDataTreeCommitCohort> DOMDataTreeCommitCohortRegistration<T> registerCommitCohort(
--- /dev/null
+/*
+ * 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<Object> {
+
+ /**
+ * 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 <tt>false</tt> if the task could not be cancelled, typically because it has already
+ * completed normally; <tt>true</tt> 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.
+ * <p>
+ * <b>Note:</b> 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
+ * <code>createCursorCursor(DOMDataTreeIdentifier</code>
+ * 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<Void,TransactionCommitFailedException> submit();
+
+}
* 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}
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;
* @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<Void, TransactionCommitFailedException> f = tx.submit();
assertNotNull(f);
Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
final Map<DOMDataTreeIdentifier, DOMDataTreeShard> 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);
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<DOMDataTreeShard, DOMStoreTransactionChain> shardToChain;
private final Map<DOMDataTreeIdentifier, DOMDataTreeShard> idToShard;
private final ShardedDOMDataTree dataTree;
+ private final BiMap<DOMDataTreeShard, DOMDataTreeShardProducer> shardToProducer;
+ private final BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer;
+
@GuardedBy("this")
- private Map<DOMDataTreeIdentifier, DOMDataTreeProducer> children = Collections.emptyMap();
+ private DOMDataTreeCursorAwareTransaction openTx;
@GuardedBy("this")
- private DOMDataTreeWriteTransaction openTx;
+ private Map<DOMDataTreeIdentifier, DOMDataTreeProducer> children = Collections.emptyMap();
@GuardedBy("this")
private boolean closed;
@GuardedBy("this")
private ShardedDOMDataTreeListenerContext<?> attachedListener;
- ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree, final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap, final Set<DOMDataTreeShard> shards) {
+ ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree,
+ final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap,
+ final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToId) {
this.dataTree = Preconditions.checkNotNull(dataTree);
- // Create shard -> chain map
- final Builder<DOMDataTreeShard, DOMStoreTransactionChain> cb = ImmutableBiMap.builder();
- final Queue<Exception> 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<DOMDataTreeShard, DOMDataTreeShardProducer> builder = ImmutableBiMap.builder();
+ final Builder<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducerBuilder = ImmutableBiMap.builder();
+ for (final Entry<DOMDataTreeShard, Collection<DOMDataTreeIdentifier>> 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<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
+ final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToIdetifiers = ArrayListMultimap.create();
+ // map which identifier belongs to which shard
+ for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShard> 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<DOMDataTreeShard, DOMStoreWriteTransaction> shardToTx = new HashMap<>();
- for (final Entry<DOMDataTreeShard, DOMStoreTransactionChain> 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<DOMDataTreeIdentifier, DOMStoreWriteTransaction> b = ImmutableMap.builder();
- for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShard> 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")
return ret;
}
- boolean isDelegatedToChild(DOMDataTreeIdentifier path) {
+ boolean isDelegatedToChild(final DOMDataTreeIdentifier path) {
for (final DOMDataTreeIdentifier c : children.keySet()) {
if (c.contains(path)) {
return true;
}
}
- static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree, final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
- /*
- * FIXME: we do not allow multiple multiple shards in a producer because we do not implement the
- * synchronization primitives yet
- */
- final Set<DOMDataTreeShard> 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<DOMDataTreeIdentifier> 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;
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.",
--- /dev/null
+/*
+ * 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<DOMDataTreeIdentifier, DOMDataTreeShardWriteTransaction> idToTransaction;
+ private final ShardedDOMDataTreeProducer producer;
+ private final String identifier;
+ private final Set<YangInstanceIdentifier> childBoundaries = new HashSet<>();
+ @GuardedBy("this")
+ private boolean closed = false;
+
+ @GuardedBy("this")
+ private DOMDataTreeWriteCursor openCursor;
+
+ ShardedDOMDataTreeWriteTransaction(final ShardedDOMDataTreeProducer producer,
+ final Map<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer,
+ final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> 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<DOMDataTreeIdentifier, DOMDataTreeShardWriteTransaction> 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<Void, TransactionCommitFailedException> 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<DOMDataTreeShardWriteTransaction> 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<PathArgument> 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<PathArgument> 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<Void> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SubmitCoordinationTask.class);
+
+ private final String identifier;
+ private final Collection<DOMDataTreeShardWriteTransaction> transactions;
+
+ SubmitCoordinationTask(final String identifier,
+ final Collection<DOMDataTreeShardWriteTransaction> 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;
+ }
+ }
+}
+++ /dev/null
-/*
- * 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<DOMDataTreeIdentifier, DOMStoreWriteTransaction> idToTransaction;
- private final ShardedDOMDataTreeProducer producer;
- private final String identifier;
- @GuardedBy("this")
- private boolean closed = false;
-
- ShardedDOMDataWriteTransaction(final ShardedDOMDataTreeProducer producer, final Map<DOMDataTreeIdentifier, DOMStoreWriteTransaction> 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<DOMDataTreeIdentifier, DOMStoreWriteTransaction> 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<Void, TransactionCommitFailedException> submit() {
- Preconditions.checkState(!closed, "Transaction %s is already closed", identifier);
-
- final Set<DOMStoreWriteTransaction> txns = ImmutableSet.copyOf(idToTransaction.values());
- final List<DOMStoreThreePhaseCommitCohort> 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);
- }
-}
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<YangInstanceIdentifier, List<NormalizedNode<?, ?>>> 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<DOMDataTreeIdentifier, DOMStoreWriteTransaction> 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<NormalizedNode<?, ?>> 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<NormalizedNode<?, ?>> 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<Void> submit() {
+ return null;
}
@Override
- public DOMStoreThreePhaseCommitCohort ready() {
+ public ListenableFuture<Boolean> validate() {
return null;
}
@Override
- public Object getIdentifier() {
+ public ListenableFuture<Void> prepare() {
return null;
}
+ @Override
+ public ListenableFuture<Void> commit() {
+ return null;
+ }
+ }
+
+ private final class TestCursor implements DOMDataTreeWriteCursor {
+
+ private final Deque<PathArgument> stack = new ArrayDeque<>();
+
+ @Override
+ public void delete(final PathArgument child) {
+ final ArrayDeque<PathArgument> 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<PathArgument> newPath = new ArrayDeque<>(stack);
+ newPath.push(child);
+ final List<NormalizedNode<?, ?>> 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<PathArgument> 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<PathArgument> 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
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;
static {
try {
schemaContext = TestModel.createTestContext();
- } catch (ReactorException e) {
+ } catch (final ReactorException e) {
LOG.error("Unable to create schema context for TestModel", e);
}
}
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;
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 {
private static final Collection<DOMDataTreeIdentifier> SUBTREES_ROOT = Collections.singleton(ROOT_ID);
private static final Collection<DOMDataTreeIdentifier> SUBTREES_TEST = Collections.singleton(TEST_ID);
- interface MockTestShard extends DOMDataTreeShard, DOMStore {
+ interface MockTestShard extends WriteableDOMDataTreeShard {
}
@Mock(name = "storeWriteTx")
private DOMStoreWriteTransaction writeTxMock;
+ @Mock
+ private DOMDataTreeShardProducer producerMock;
+
+ @Mock
+ private DOMDataTreeShardWriteTransaction shardTxMock;
+
@Mock(name = "storeTxChain")
private DOMStoreTransactionChain txChainMock;
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);
}
@Test
public void closeWithTxSubmitted() throws DOMDataTreeProducerException {
- DOMDataTreeWriteTransaction tx = producer.createTransaction(false);
+ DOMDataTreeCursorAwareTransaction tx = producer.createTransaction(false);
tx.submit();
producer.close();
}
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
--- /dev/null
+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<InMemoryDOMDataTreeShard> rootShardReg;
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ @Captor
+ private ArgumentCaptor<Collection<DataTreeCandidate>> captorForChanges;
+ @Captor
+ private ArgumentCaptor<Map<DOMDataTreeIdentifier, NormalizedNode<?, ?>>> 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<Collection<DataTreeCandidate>> 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<String> shardedValue1 =
+ ImmutableLeafNodeBuilder.<String>create().withNodeIdentifier(new NodeIdentifier(TestModel.SHARDED_VALUE_1)).withValue("sharded value 1").build();
+ final LeafNode<String> shardedValue2 =
+ ImmutableLeafNodeBuilder.<String>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;
+ }
+}
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;
@Override
public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> 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<DataTreeCandidate> changes) {
- onDataTreeChanged(changes.stream()
+ final List<DataTreeCandidate> 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) {
}
void close() {
- for (ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
+ for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
registration.close();
}
registrations.clear();
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.
*
* @throws IllegalStateException if a previous cursor has not been closed.
* @throws NullPointerException if prefix is null.
*/
+ @Override
@Nonnull DOMDataTreeWriteCursor createCursor(@Nonnull DOMDataTreeIdentifier prefix);
/**
ListenableFuture<Void> submit();
- //FIXME: remove these from the public api?
ListenableFuture<Boolean> validate();
ListenableFuture<Void> prepare();
private static final class SubshardProducerSpecification {
private final Collection<DOMDataTreeIdentifier> prefixes = new ArrayList<>(1);
private final ChildShardContext shard;
-
SubshardProducerSpecification(final ChildShardContext subshard) {
this.shard = Preconditions.checkNotNull(subshard);
}
private final DOMDataTreePrefixTable<ChildShardContext> childShardsTable = DOMDataTreePrefixTable.create();
private final Map<DOMDataTreeIdentifier, ChildShardContext> 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,
@Override
public void onGlobalContextUpdated(final SchemaContext context) {
dataTree.setSchemaContext(context);
+ schemaContext = context;
}
@Override
@Nonnull
@Override
- public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerTreeChangeListener(@Nonnull YangInstanceIdentifier treeId, @Nonnull L listener) {
+ public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerTreeChangeListener(@Nonnull final YangInstanceIdentifier treeId, @Nonnull final L listener) {
return shardChangePublisher.registerTreeChangeListener(treeId, listener);
}
}
@Override
- protected void notifyListeners(@Nonnull Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> registrations, @Nonnull YangInstanceIdentifier path, @Nonnull DataTreeCandidateNode node) {
+ protected void notifyListeners(@Nonnull final Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> 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);
}
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;
void apply(final DOMDataTreeWriteCursor cursor, final YangInstanceIdentifier path,
final NormalizedNode<?, ?> data) {
int enterCount = 0;
- Iterator<PathArgument> it = path.getPathArguments().iterator();
+ final Iterator<PathArgument> 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);
private final ShardDataModification modification;
private DOMDataTreeWriteCursor cursor;
- private DataTree rootShardDataTree;
+ private final DataTree rootShardDataTree;
private DataTreeModification rootModification = null;
- private ArrayList<DOMStoreThreePhaseCommitCohort> cohorts = new ArrayList<>();
- private InMemoryDOMDataTreeShardChangePublisher changePublisher;
+ private final ArrayList<DOMStoreThreePhaseCommitCohort> 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,
}
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);
}
private YangInstanceIdentifier toRelative(final YangInstanceIdentifier path) {
- Optional<YangInstanceIdentifier> relative =
+ final Optional<YangInstanceIdentifier> relative =
path.relativeTo(modification.getPrefix().getRootIdentifier());
Preconditions.checkArgument(relative.isPresent());
return relative.get();
}
public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(final YangInstanceIdentifier path) {
- // FIXME: Implement this
- return null;
+ throw new UnsupportedOperationException("Not implemented yet");
}
public CheckedFuture<Boolean, ReadFailedException> 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;
}
rootModification = modification.seal();
cohorts.add(new InMemoryDOMDataTreeShardThreePhaseCommitCohort(rootShardDataTree, rootModification, changePublisher));
- for (Entry<DOMDataTreeIdentifier, ForeignShardModificationContext> entry : modification.getChildShards().entrySet()) {
+ for (final Entry<DOMDataTreeIdentifier, ForeignShardModificationContext> entry : modification.getChildShards().entrySet()) {
cohorts.add(new ForeignShardThreePhaseCommitCohort(entry.getKey(), entry.getValue()));
}
finished = true;
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;
}