Bug 509: Added support for merge operation to InMemoryData Store 38/6338/3
authorTony Tkacik <ttkacik@cisco.com>
Wed, 23 Apr 2014 14:17:31 +0000 (16:17 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Thu, 24 Apr 2014 08:38:04 +0000 (10:38 +0200)
Merge operation support is critical for backwards compatibility
and for Binding-Aware mapping and Restconf, which are not aware
of Mixin nodes representing lists, leaflists and choices.

Semantics of merge operation are similar to subtree modified,
but when node is not existing it creates one based on
state provided by user of the Data Broker APIs.

Merge operation also fixes concurrent creations of mixin nodes
as demonstrated in ConcurrentImplicitCreateTest,
where two separate applications may indirectly create same
root node.

Change-Id: I9ab290b512601bd99750a68ee53564e67ef0c879
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
12 files changed:
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedTransaction.java
opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMDataBrokerImpl.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/compat/BackwardsCompatibleTransaction.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/OperationWithModification.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ModificationType.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/NodeModification.java
opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/sal/core/spi/data/DOMStoreWriteTransaction.java

index 6334457..6bd6e6a 100644 (file)
@@ -132,7 +132,7 @@ public class AbstractForwardedTransaction<T extends AsyncTransaction<org.openday
             }
 
             if (!d.isPresent() && iterator.hasNext()) {
-                writeTransaction.put(store, currentPath, currentOp.createDefault(currentArg));
+                writeTransaction.merge(store, currentPath, currentOp.createDefault(currentArg));
             }
         }
         //LOG .info("Tx: {} : Putting data {}",getDelegate().getIdentifier(),normalized.getKey());
diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java
new file mode 100644 (file)
index 0000000..61a73d6
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2014 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.controller.md.sal.binding.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
+import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+
+public class ConcurrentImplicitCreateTest extends AbstractDataServiceTest {
+
+    private static final NodeKey NODE_FOO_KEY = new NodeKey(new NodeId("foo"));
+    private static final NodeKey NODE_BAR_KEY = new NodeKey(new NodeId("foo"));
+    private static InstanceIdentifier<Nodes> NODES_PATH = InstanceIdentifier.builder(Nodes.class).build();
+    private static InstanceIdentifier<Node> NODE_FOO_PATH = InstanceIdentifier.builder(NODES_PATH)
+            .child(Node.class, NODE_FOO_KEY).build();
+    private static InstanceIdentifier<Node> NODE_BAR_PATH = InstanceIdentifier.builder(NODES_PATH)
+            .child(Node.class, NODE_FOO_KEY).build();
+
+    @Test
+    public void testConcurrentCreate() throws InterruptedException, ExecutionException {
+
+        DataModificationTransaction fooTx = baDataService.beginTransaction();
+        DataModificationTransaction barTx = baDataService.beginTransaction();
+
+        fooTx.putOperationalData(NODE_FOO_PATH, new NodeBuilder().setKey(NODE_FOO_KEY).build());
+        barTx.putOperationalData(NODE_BAR_PATH, new NodeBuilder().setKey(NODE_BAR_KEY).build());
+
+        Future<RpcResult<TransactionStatus>> fooFuture = fooTx.commit();
+        Future<RpcResult<TransactionStatus>> barFuture = barTx.commit();
+
+        RpcResult<TransactionStatus> fooResult = fooFuture.get();
+        RpcResult<TransactionStatus> barResult = barFuture.get();
+
+        assertTrue(fooResult.isSuccessful());
+        assertTrue(barResult.isSuccessful());
+
+        assertEquals(TransactionStatus.COMMITED, fooResult.getResult());
+        assertEquals(TransactionStatus.COMMITED, barResult.getResult());
+
+    }
+}
index 64a5606..608ac9b 100644 (file)
@@ -217,8 +217,7 @@ public class DOMDataBrokerImpl implements DOMDataBroker, AutoCloseable {
         @Override
         public void merge(final LogicalDatastoreType store, final InstanceIdentifier path,
                 final NormalizedNode<?, ?> data) {
-            // TODO Auto-generated method stub
-            throw new UnsupportedOperationException("Not implemented yet.");
+            getSubtransaction(store).merge(path,data);
         }
 
         @Override
@@ -251,12 +250,6 @@ public class DOMDataBrokerImpl implements DOMDataBroker, AutoCloseable {
                 final InstanceIdentifier path) {
             return getSubtransaction(store).read(path);
         }
-
-        @Override
-        public void merge(final LogicalDatastoreType store, final InstanceIdentifier path,
-                final NormalizedNode<?, ?> data) {
-
-        }
     }
 
     private final class CommitCoordination implements Callable<RpcResult<TransactionStatus>> {
index b905d2f..29f2af5 100644 (file)
@@ -32,21 +32,13 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.Iterables;
 import com.google.common.util.concurrent.ListenableFuture;
 
 public abstract class BackwardsCompatibleTransaction<T extends DOMDataReadTransaction> implements
@@ -249,7 +241,7 @@ public abstract class BackwardsCompatibleTransaction<T extends DOMDataReadTransa
                 InstanceIdentifier currentPath = new InstanceIdentifier(currentArguments);
                 boolean isPresent = getDelegate().read(store, currentPath).get().isPresent();
                 if(isPresent == false && iterator.hasNext()) {
-                    getDelegate().put(store, currentPath, currentOp.createDefault(currentArg));
+                    getDelegate().merge(store, currentPath, currentOp.createDefault(currentArg));
                 }
             }
             } catch (InterruptedException | ExecutionException e) {
@@ -259,48 +251,6 @@ public abstract class BackwardsCompatibleTransaction<T extends DOMDataReadTransa
             getDelegate().put(store, normalizedPath, normalizedData);
         }
 
-        private boolean isAugmentationChild(final InstanceIdentifier normalizedPath) {
-            List<PathArgument> parentArgs = parentPath(normalizedPath).getPath();
-            if(parentArgs.isEmpty()) {
-                return false;
-            }
-            return Iterables.getLast(parentArgs) instanceof AugmentationIdentifier;
-        }
-
-        private void ensureParentNode(final LogicalDatastoreType store, final InstanceIdentifier normalizedPath,
-                final NormalizedNode<?, ?> normalizedData) {
-            InstanceIdentifier parentPath = parentPath(normalizedPath);
-            PathArgument parentType = Iterables.getLast(parentPath.getPath());
-            if(parentType instanceof AugmentationIdentifier) {
-                AugmentationNode node = Builders.augmentationBuilder()
-                        .withNodeIdentifier((AugmentationIdentifier) parentType)
-                        .build();
-                getDelegate().put(store, parentPath, node);
-            }
-            if(normalizedData instanceof MapEntryNode) {
-                MapNode mapNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(normalizedData.getNodeType())).build();
-                getDelegate().put(store, parentPath, mapNode);
-            } else if (normalizedData instanceof LeafSetNode<?>){
-                LeafSetNode<Object> leafNode = Builders.leafSetBuilder().withNodeIdentifier(new NodeIdentifier(normalizedData.getNodeType())).build();
-                getDelegate().put(store, parentPath, leafNode);
-            }
-
-
-        }
-
-        private InstanceIdentifier parentPath(final InstanceIdentifier normalizedPath) {
-            List<PathArgument> childArgs = normalizedPath.getPath();
-            return new InstanceIdentifier(childArgs.subList(0, childArgs.size() -1));
-        }
-
-        private boolean parentNodeDoesNotExists(final LogicalDatastoreType store, final InstanceIdentifier normalizedPath) {
-            try {
-                return !getDelegate().read(store, parentPath(normalizedPath)).get().isPresent();
-            } catch (InterruptedException | ExecutionException e) {
-                throw new IllegalStateException(e);
-            }
-        }
-
         @Override
         public void removeConfigurationData(final InstanceIdentifier legacyPath) {
             checkNotNull(legacyPath, "Path MUST NOT be null.");
index 6c0e582..9bbba1e 100644 (file)
@@ -258,6 +258,18 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
             }
         }
 
+        @Override
+        public void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+            checkNotReady();
+            try {
+                LOG.trace("Tx: {} Merge: {}:{}",getIdentifier(),path,data);
+                mutableTree.merge(path, data);
+              // FIXME: Add checked exception
+            } catch (Exception e) {
+                LOG.error("Tx: {}, failed to write {}:{} in {}",getIdentifier(),path,data,mutableTree,e);
+            }
+        }
+
         @Override
         public void delete(final InstanceIdentifier path) {
             checkNotReady();
index bc7fe7a..b711163 100644 (file)
@@ -18,6 +18,7 @@ import org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,6 +48,24 @@ class MutableDataTree {
         resolveModificationFor(path).write(value);
     }
 
+    public void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+        checkSealed();
+        mergeImpl(resolveModificationFor(path),data);
+    }
+
+    private void mergeImpl(final OperationWithModification op,final NormalizedNode<?,?> data) {
+
+        if(data instanceof NormalizedNodeContainer<?,?,?>) {
+            @SuppressWarnings({ "rawtypes", "unchecked" })
+            NormalizedNodeContainer<?,?,NormalizedNode<PathArgument, ?>> dataContainer = (NormalizedNodeContainer) data;
+            for(NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
+                PathArgument childId = child.getIdentifier();
+                mergeImpl(op.forChild(childId), child);
+            }
+        }
+        op.merge(data);
+    }
+
     public void delete(final InstanceIdentifier path) {
         checkSealed();
         resolveModificationFor(path).delete();
index eaf01ae..35864b6 100644 (file)
@@ -2,6 +2,7 @@ package org.opendaylight.controller.md.sal.dom.store.impl;
 
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
 import com.google.common.base.Optional;
@@ -10,6 +11,7 @@ import com.google.common.primitives.UnsignedLong;
 public class OperationWithModification {
 
     private final NodeModification modification;
+
     private final ModificationApplyOperation applyOperation;
 
     private OperationWithModification(final ModificationApplyOperation op, final NodeModification mod) {
@@ -28,6 +30,14 @@ public class OperationWithModification {
         return this;
     }
 
+    public NodeModification getModification() {
+        return modification;
+    }
+
+    public ModificationApplyOperation getApplyOperation() {
+        return applyOperation;
+    }
+
     public boolean isApplicable(final Optional<StoreMetadataNode> data) {
         return applyOperation.isApplicable(modification, data);
     }
@@ -41,4 +51,16 @@ public class OperationWithModification {
         return new OperationWithModification(operation, modification);
 
     }
+
+    public void merge(final NormalizedNode<?, ?> data) {
+        modification.merge(data);
+        applyOperation.verifyStructure(modification);
+
+    }
+
+    public OperationWithModification forChild(final PathArgument childId) {
+        NodeModification childMod = modification.modifyChild(childId);
+        Optional<ModificationApplyOperation> childOp = applyOperation.getChild(childId);
+        return from(childOp.get(),childMod);
+    }
 }
\ No newline at end of file
index 520bd1b..c62c27d 100644 (file)
@@ -308,6 +308,7 @@ public class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeList
         switch (modification.getModificationType()) {
         case SUBTREE_MODIFIED:
             return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
+        case MERGE:
         case WRITE:
             if (before.isPresent()) {
                 return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
@@ -487,6 +488,7 @@ public class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeList
 
             switch (childMod.getModificationType()) {
             case WRITE:
+            case MERGE:
             case DELETE:
                 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
                 break;
index b9b1ab0..a5c9b79 100644 (file)
@@ -131,6 +131,8 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
             return isSubtreeModificationApplicable(modification, current);
         case WRITE:
             return isWriteApplicable(modification, current);
+        case MERGE:
+            return isMergeApplicable(modification,current);
         case UNMODIFIED:
             return true;
         default:
@@ -138,6 +140,16 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
         }
     }
 
+    private boolean isMergeApplicable(final NodeModification modification, final Optional<StoreMetadataNode> current) {
+        Optional<StoreMetadataNode> original = modification.getOriginal();
+        if (original.isPresent() && current.isPresent()) {
+            return isNotConflicting(original.get(), current.get());
+        } else if (current.isPresent()) {
+            return true;
+        }
+        return true;
+    }
+
     protected boolean isWriteApplicable(final NodeModification modification, final Optional<StoreMetadataNode> current) {
         Optional<StoreMetadataNode> original = modification.getOriginal();
         if (original.isPresent() && current.isPresent()) {
@@ -174,6 +186,10 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
                     modification);
             return modification.storeSnapshot(Optional.of(applySubtreeChange(modification, currentMeta.get(),
                     subtreeVersion)));
+        case MERGE:
+            if(currentMeta.isPresent()) {
+                return modification.storeSnapshot(Optional.of(applyMerge(modification,currentMeta.get(),subtreeVersion)));
+            } // Fallback to write is intentional - if node is not preexisting merge is same as write
         case WRITE:
             return modification.storeSnapshot(Optional.of(applyWrite(modification, currentMeta, subtreeVersion)));
         case UNMODIFIED:
@@ -183,6 +199,9 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
         }
     }
 
+    protected abstract StoreMetadataNode applyMerge(NodeModification modification,
+            StoreMetadataNode currentMeta, UnsignedLong subtreeVersion);
+
     protected abstract StoreMetadataNode applyWrite(NodeModification modification,
             Optional<StoreMetadataNode> currentMeta, UnsignedLong subtreeVersion);
 
@@ -219,14 +238,16 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
                     + "is leaf type node. Subtree change is not allowed.");
         }
 
+        @Override
+        protected StoreMetadataNode applyMerge(final NodeModification modification, final StoreMetadataNode currentMeta,
+                final UnsignedLong subtreeVersion) {
+            return applyWrite(modification, Optional.of(currentMeta), subtreeVersion);
+        }
+
         @Override
         protected StoreMetadataNode applyWrite(final NodeModification modification,
                 final Optional<StoreMetadataNode> currentMeta, final UnsignedLong subtreeVersion) {
             UnsignedLong nodeVersion = subtreeVersion;
-            if (currentMeta.isPresent()) {
-                nodeVersion = StoreUtils.increase(currentMeta.get().getNodeVersion());
-            }
-
             return StoreMetadataNode.builder().setNodeVersion(nodeVersion).setSubtreeVersion(subtreeVersion)
                     .setData(modification.getWritenValue()).build();
         }
@@ -314,6 +335,13 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
 
         }
 
+        @Override
+        protected StoreMetadataNode applyMerge(final NodeModification modification, final StoreMetadataNode currentMeta,
+                final UnsignedLong subtreeVersion) {
+            // For Node Containers - merge is same as subtree change - we only replace children.
+            return applySubtreeChange(modification, currentMeta, subtreeVersion);
+        }
+
         @Override
         public StoreMetadataNode applySubtreeChange(final NodeModification modification,
                 final StoreMetadataNode currentMeta, final UnsignedLong subtreeVersion) {
@@ -569,7 +597,11 @@ public abstract class SchemaAwareApplyOperation implements ModificationApplyOper
             entryStrategy = Optional.<ModificationApplyOperation> of(new UnkeyedListItemModificationStrategy(schema));
         }
 
-
+        @Override
+        protected StoreMetadataNode applyMerge(final NodeModification modification, final StoreMetadataNode currentMeta,
+                final UnsignedLong subtreeVersion) {
+            return applyWrite(modification, Optional.of(currentMeta), subtreeVersion);
+        }
 
         @Override
         protected StoreMetadataNode applySubtreeChange(final NodeModification modification,
index 04fb3b7..4f650c1 100644 (file)
@@ -20,7 +20,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
-import com.google.common.primitives.UnsignedLong;
 
 /**
  * Node Modification Node and Tree
@@ -37,7 +36,9 @@ public class NodeModification implements StoreTreeNode<NodeModification>, Identi
     public static final Predicate<NodeModification> IS_TERMINAL_PREDICATE = new Predicate<NodeModification>() {
         @Override
         public boolean apply(final NodeModification input) {
-            return input.getModificationType() == ModificationType.WRITE || input.getModificationType() == ModificationType.DELETE;
+            return input.getModificationType() == ModificationType.WRITE //
+                    || input.getModificationType() == ModificationType.DELETE //
+                    || input.getModificationType() == ModificationType.MERGE;
         }
     };
     private final PathArgument identifier;
@@ -48,7 +49,6 @@ public class NodeModification implements StoreTreeNode<NodeModification>, Identi
 
     private NormalizedNode<?, ?> value;
 
-    private UnsignedLong subtreeVersion;
     private Optional<StoreMetadataNode> snapshotCache;
 
     private final Map<PathArgument, NodeModification> childModification;
@@ -176,6 +176,14 @@ public class NodeModification implements StoreTreeNode<NodeModification>, Identi
         this.value = value;
     }
 
+    public synchronized void merge(final NormalizedNode<?, ?> data) {
+        checkSealed();
+        clearSnapshot();
+        updateModificationType(ModificationType.MERGE);
+        // FIXME: Probably merge with previous value.
+        this.value = data;
+    }
+
     @GuardedBy("this")
     private void checkSealed() {
         checkState(!sealed, "Node Modification is sealed. No further changes allowed.");
index 6761bc1..ddabbc6 100644 (file)
@@ -33,6 +33,25 @@ public interface DOMStoreWriteTransaction extends DOMStoreTransaction {
      */
     void write(InstanceIdentifier path, NormalizedNode<?, ?> data);
 
+    /**
+     * Store a provided data at specified path. This acts as a add / replace
+     * operation, which is to say that whole subtree will be replaced by
+     * specified path.
+     *
+     * If you need add or merge of current object with specified use
+     * {@link #merge(LogicalDatastoreType, Path, Object)}
+     *
+     *
+     * @param path
+     * @param data
+     *            Data object to be written
+     *
+     * @throws IllegalStateException
+     *             if the client code already sealed transaction and invoked
+     *             {@link #ready()}
+     */
+    void merge(InstanceIdentifier path, NormalizedNode<?, ?> data);
+
     /**
      *
      * Deletes data and whole subtree located at provided path.

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.