summaryrefslogtreecommitdiffstats
path: root/infra/data-impl
diff options
context:
space:
mode:
authorMaros Marsalek <mmarsale@cisco.com>2016-07-27 11:05:51 +0200
committerMaros Marsalek <mmarsale@cisco.com>2016-07-29 16:32:07 +0200
commit0578156b721fa01c8c645b8f9625ecebdb6449e4 (patch)
tree49d36f24e5d984a8c9f151b1440de88619f8b7de /infra/data-impl
parent007d4542388ca89be409ce1a4a4c7a36ddcb538f (diff)
HONEYCOMB-130: Separate v3po plugin from HC infra
Creating folders: - common/ - infra/ - v3po/ - vpp-common/ Change-Id: I2c39e1b17e39e7c0f0628f44aa5fe08563fa06e4 Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
Diffstat (limited to 'infra/data-impl')
-rw-r--r--infra/data-impl/Readme.adoc43
-rw-r--r--infra/data-impl/pom.xml106
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java198
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java236
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeManager.java122
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java278
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapter.java151
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransaction.java119
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransaction.java101
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegator.java242
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/WriteTransaction.java177
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java71
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModuleFactory.java13
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModule.java79
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModuleFactory.java13
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModule.java65
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModuleFactory.java13
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModule.java38
-rw-r--r--infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModuleFactory.java13
-rw-r--r--infra/data-impl/src/main/yang/data-impl.yang179
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java111
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java269
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java427
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapterTest.java69
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransactionTest.java68
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransactionTest.java111
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegatorTest.java192
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/WriteTransactionTest.java128
-rw-r--r--infra/data-impl/src/test/resources/test-diff.yang54
29 files changed, 3686 insertions, 0 deletions
diff --git a/infra/data-impl/Readme.adoc b/infra/data-impl/Readme.adoc
new file mode 100644
index 000000000..61b2653b3
--- /dev/null
+++ b/infra/data-impl/Readme.adoc
@@ -0,0 +1,43 @@
+= Honeycomb Data layer. Implementation
+
+Models CONFIG data store as a DataTree.
+
+Every read transaction reads directly from the DataTree.
+Every write transaction is validated, then passed to the translation layer.
+After successful update, config data tree is updated.
+
+OPERATIONAL data store reads are passed directly to the translation layer.
+
+Data transaction functionality for higher layers (i.e NETCONF/RESTCONF layer) is provided by DataBroker.
+
+[ditaa, "data-layer-architecture"]
+....
+ /------------------\
+ | RESTCONF/NETCONF |
+ \--------+---------/
+ |
+----------------------------|------------------------------------
+ v
+ /------------------\
+ | DataBroker |
+ \-+--------------+-/
+ | |
+ | read | read/write
+ Honeycomb v v
+ data layer /------+------\ /--+--------\ /----------\
+ | | | | | |
+ | Operational | | Config +------>+ DataTree +
+ | DataTree | | DataTree | | |
+ | | | | \----------/
+ \------+------/ \--+--------/
+ | |
+---------------------|--------------|----------------------------
+ v v
+ /-----+--------------+-----\
+ | Translation layer |
+ \--------------------------/
+....
+
+
+
+
diff --git a/infra/data-impl/pom.xml b/infra/data-impl/pom.xml
new file mode 100644
index 000000000..b80c796a5
--- /dev/null
+++ b/infra/data-impl/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2016 Cisco and/or its affiliates.
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>impl-parent</artifactId>
+ <groupId>io.fd.honeycomb.common</groupId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>../../common/impl-parent</relativePath>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>io.fd.honeycomb</groupId>
+ <artifactId>data-impl</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>data-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>translate-utils</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-binding-dom-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-core-api</artifactId>
+ <!-- FIXME use dependency management -->
+ <version>1.3.2-Beryllium-SR2</version>
+ </dependency>
+
+ <!-- FIXME workaround for https://git.opendaylight.org/gerrit/#/c/37499/-->
+ <dependency>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>ietf-netconf-monitoring</artifactId>
+ <version>1.0.2-Beryllium-SR2</version>
+ </dependency>
+ <!-- FIXME workaround for https://git.opendaylight.org/gerrit/#/c/37499/-->
+ <dependency>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>ietf-netconf-monitoring-extension</artifactId>
+ <version>1.0.2-Beryllium-SR2</version>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+ <Export-Package>
+ io.fd.honeycomb.v3po.data.impl.*
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project> \ No newline at end of file
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java
new file mode 100644
index 000000000..c418ed332
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.fd.honeycomb.v3po.data.DataModification;
+import io.fd.honeycomb.v3po.data.ModifiableDataManager;
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+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;
+
+/**
+ * Data Broker which provides data transaction functionality for YANG capable data provider using {@link NormalizedNode}
+ * data format.
+ */
+public class DataBroker implements DOMDataBroker, Closeable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DataBroker.class);
+ private final TransactionFactory transactionFactory;
+
+ /**
+ * Creates DataBroker instance.
+ *
+ * @param transactionFactory transaction producing factory
+ */
+ public DataBroker(final TransactionFactory transactionFactory) {
+ this.transactionFactory = transactionFactory;
+ }
+
+ @Override
+ public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+ LOG.trace("DataBroker({}).newReadOnlyTransaction()", this);
+ return transactionFactory.newReadOnlyTransaction();
+ }
+
+ @Override
+ public DOMDataReadWriteTransaction newReadWriteTransaction() {
+ LOG.trace("DataBroker({}).newReadWriteTransaction()", this);
+ return transactionFactory.newReadWriteTransaction();
+ }
+
+ @Override
+ public DOMDataWriteTransaction newWriteOnlyTransaction() {
+ LOG.trace("DataBroker({}).newWriteOnlyTransaction()", this);
+ return transactionFactory.newWriteOnlyTransaction();
+ }
+
+ @Override
+ public ListenerRegistration<DOMDataChangeListener> registerDataChangeListener(final LogicalDatastoreType store,
+ final YangInstanceIdentifier path,
+ final DOMDataChangeListener listener,
+ final DataChangeScope triggeringScope) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Nonnull
+ @Override
+ public Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> getSupportedExtensions() {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Create DataBroker for a modifiable config DT, but only readable Operational
+ */
+ @Nonnull
+ public static DataBroker create(@Nonnull final ModifiableDataManager configDataTree,
+ @Nonnull final ReadableDataManager operationalDataTree) {
+ checkNotNull(operationalDataTree, "operationalDataTree should not be null");
+ checkNotNull(configDataTree, "configDataTree should not be null");
+ return new DataBroker(new MainPipelineTxFactory(configDataTree, operationalDataTree));
+ }
+
+ /**
+ * Create DataBroker for modifiable operational DT, but no support for config
+ */
+ @Nonnull
+ public static DataBroker create(@Nonnull final ModifiableDataManager operationalDataTree) {
+ checkNotNull(operationalDataTree, "operationalDataTree should not be null");
+ return new DataBroker(new ContextPipelineTxFactory(operationalDataTree));
+ }
+
+ @Override
+ public void close() throws IOException {
+ // NOOP
+ }
+
+ /**
+ * Transaction provider factory to be used by {@link DataBroker}
+ */
+ public interface TransactionFactory {
+
+ DOMDataReadOnlyTransaction newReadOnlyTransaction();
+
+ DOMDataReadWriteTransaction newReadWriteTransaction();
+
+ DOMDataWriteTransaction newWriteOnlyTransaction();
+ }
+
+ /**
+ * Transaction factory specific for Honeycomb's main pipeline (config: read+write, operational: read-only)
+ */
+ private static class MainPipelineTxFactory implements TransactionFactory {
+ private final ReadableDataManager operationalDataTree;
+ private final ModifiableDataManager configDataTree;
+
+ MainPipelineTxFactory(@Nonnull final ModifiableDataManager configDataTree,
+ @Nonnull final ReadableDataManager operationalDataTree) {
+ this.operationalDataTree = operationalDataTree;
+ this.configDataTree = configDataTree;
+ }
+
+ @Override
+ public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+ return ReadOnlyTransaction.create(configDataTree.newModification(), operationalDataTree);
+ }
+
+ @Override
+ public DOMDataReadWriteTransaction newReadWriteTransaction() {
+ final DataModification configModification = configDataTree.newModification();
+ return new ReadWriteTransaction(
+ ReadOnlyTransaction.create(configModification, operationalDataTree),
+ WriteTransaction.createConfigOnly(configModification));
+ }
+
+ @Override
+ public DOMDataWriteTransaction newWriteOnlyTransaction() {
+ return WriteTransaction.createConfigOnly(configDataTree.newModification());
+ }
+ }
+
+ /**
+ * Transaction factory specific for Honeycomb's context pipeline (config: none, operational: read+write)
+ */
+ private static class ContextPipelineTxFactory implements TransactionFactory {
+ private final ModifiableDataManager operationalDataTree;
+
+ ContextPipelineTxFactory(@Nonnull final ModifiableDataManager operationalDataTree) {
+ this.operationalDataTree = operationalDataTree;
+ }
+
+ @Override
+ public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+ return ReadOnlyTransaction.createOperationalOnly(operationalDataTree);
+ }
+
+ @Override
+ public DOMDataReadWriteTransaction newReadWriteTransaction() {
+ final DataModification dataModification = operationalDataTree.newModification();
+ return new ReadWriteTransaction(
+ ReadOnlyTransaction.createOperationalOnly(dataModification),
+ WriteTransaction.createOperationalOnly(dataModification));
+ }
+
+ @Override
+ public DOMDataWriteTransaction newWriteOnlyTransaction() {
+ return WriteTransaction.createOperationalOnly(operationalDataTree.newModification());
+ }
+ }
+}
+
+
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java
new file mode 100644
index 000000000..2c2581ec0
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.util.concurrent.Futures.immediateCheckedFuture;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.data.DataModification;
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import io.fd.honeycomb.v3po.translate.TranslationException;
+import io.fd.honeycomb.v3po.translate.util.RWUtils;
+import io.fd.honeycomb.v3po.translate.util.TransactionMappingContext;
+import io.fd.honeycomb.v3po.translate.util.write.TransactionWriteContext;
+import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import io.fd.honeycomb.v3po.translate.write.registry.WriterRegistry;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extension of {@link ModifiableDataTreeManager} that propagates data changes to underlying writer layer before they
+ * are fully committed in the backing data tree. Data changes are propagated in BA format.
+ */
+public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeDelegator.class);
+ private static final ReadableDataManager EMPTY_OPERATIONAL = p -> immediateCheckedFuture(Optional.absent());
+
+ private final WriterRegistry writerRegistry;
+ private final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker;
+ // TODO what to use instead of deprecated BindingNormalizedNodeSerializer ?
+ private final BindingNormalizedNodeSerializer serializer;
+
+ /**
+ * Creates configuration data tree instance.
+ * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
+ * representation.
+ * @param dataTree data tree for configuration data representation
+ * @param writerRegistry service for translation between Java Binding Data and data provider, capable of performing
+ * @param contextBroker BA broker providing full access to mapping context data
+ */
+ public ModifiableDataTreeDelegator(@Nonnull final BindingNormalizedNodeSerializer serializer,
+ @Nonnull final DataTree dataTree,
+ @Nonnull final WriterRegistry writerRegistry,
+ @Nonnull final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker) {
+ super(dataTree);
+ this.contextBroker = checkNotNull(contextBroker, "contextBroker should not be null");
+ this.serializer = checkNotNull(serializer, "serializer should not be null");
+ this.writerRegistry = checkNotNull(writerRegistry, "writerRegistry should not be null");
+ }
+
+ @Override
+ public DataModification newModification() {
+ return new ConfigSnapshot(super.newModification());
+ }
+
+ private final class ConfigSnapshot extends ModifiableDataTreeManager.ConfigSnapshot {
+
+ private final DataModification untouchedModification;
+
+ /**
+ * @param untouchedModification DataModification captured while this modification/snapshot was created.
+ * To be used later while invoking writers to provide them with before state
+ * (state without current modifications).
+ * It must be captured as close as possible to when current modification started.
+ */
+ ConfigSnapshot(final DataModification untouchedModification) {
+ this.untouchedModification = untouchedModification;
+ }
+
+ /**
+ * Pass the changes to underlying writer layer.
+ * Transform from BI to BA.
+ * Revert(Write data before to subtrees that have been successfully modified before failure) in case of failure.
+ */
+ @Override
+ protected void processCandidate(final DataTreeCandidate candidate)
+ throws TranslationException {
+
+ final DataTreeCandidateNode rootNode = candidate.getRootNode();
+ final YangInstanceIdentifier rootPath = candidate.getRootPath();
+ LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
+ rootPath, rootNode, rootNode.getDataBefore(), rootNode.getDataAfter());
+
+ final ModificationDiff modificationDiff =
+ ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, rootNode);
+ LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
+
+ // Distinguish between updates (create + update) and deletes
+ final WriterRegistry.DataObjectUpdates baUpdates = toBindingAware(modificationDiff.getUpdates());
+ LOG.debug("ConfigDataTree.modify() extracted updates={}", baUpdates);
+
+ try (final WriteContext ctx = getTransactionWriteContext()) {
+ writerRegistry.update(baUpdates, ctx);
+
+ final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
+ ((TransactionMappingContext) ctx.getMappingContext()).submit();
+ // Blocking on context data update
+ contextUpdateResult.checkedGet();
+
+ } catch (WriterRegistry.BulkUpdateException e) {
+ LOG.warn("Failed to apply all changes", e);
+ LOG.info("Trying to revert successful changes for current transaction");
+
+ try {
+ e.revertChanges();
+ LOG.info("Changes successfully reverted");
+ } catch (WriterRegistry.Reverter.RevertFailedException revertFailedException) {
+ // fail with failed revert
+ LOG.error("Failed to revert successful changes", revertFailedException);
+ throw revertFailedException;
+ }
+
+ throw e; // fail with success revert
+ } catch (TransactionCommitFailedException e) {
+ // FIXME revert should probably occur when context is not written successfully
+ final String msg = "Error while updating mapping context data";
+ LOG.error(msg, e);
+ throw new TranslationException(msg, e);
+ } catch (TranslationException e) {
+ LOG.error("Error while processing data change (updates={})", baUpdates, e);
+ throw e;
+ }
+ }
+
+ private TransactionWriteContext getTransactionWriteContext() {
+ // Before Tx must use modification
+ final DOMDataReadOnlyTransaction beforeTx = ReadOnlyTransaction.create(untouchedModification, EMPTY_OPERATIONAL);
+ // After Tx must use current modification
+ final DOMDataReadOnlyTransaction afterTx = ReadOnlyTransaction.create(this, EMPTY_OPERATIONAL);
+ final TransactionMappingContext mappingContext = new TransactionMappingContext(
+ contextBroker.newReadWriteTransaction());
+ return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext);
+ }
+
+ private WriterRegistry.DataObjectUpdates toBindingAware(
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes) {
+ return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
+ }
+ }
+
+ @VisibleForTesting
+ static WriterRegistry.DataObjectUpdates toBindingAware(
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes,
+ final BindingNormalizedNodeSerializer serializer) {
+
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
+ HashMultimap.create();
+
+ for (Map.Entry<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
+ final InstanceIdentifier<?> unkeyedIid =
+ RWUtils.makeIidWildcarded(serializer.fromYangInstanceIdentifier(biEntry.getKey()));
+
+ ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
+ final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
+ if (dataObjectUpdate != null) {
+ if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
+ dataObjectDeletes.put(unkeyedIid, ((DataObjectUpdate.DataObjectDelete) dataObjectUpdate));
+ } else {
+ dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
+ }
+ }
+ }
+ return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes);
+ }
+
+ @Nullable
+ private static DataObjectUpdate toDataObjectUpdate(
+ final ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate,
+ final BindingNormalizedNodeSerializer serializer) {
+
+ InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
+ checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId());
+
+ DataObject dataObjectBefore = getDataObject(serializer,
+ normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId());
+ DataObject dataObjectAfter =
+ getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId());
+
+ return dataObjectBefore == null && dataObjectAfter == null
+ ? null
+ : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter);
+ }
+
+ @Nullable
+ private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer,
+ @Nullable final NormalizedNode<?, ?> data,
+ @Nonnull final YangInstanceIdentifier id) {
+ DataObject dataObject = null;
+ if (data != null) {
+ final Map.Entry<InstanceIdentifier<?>, DataObject> dataObjectEntry =
+ serializer.fromNormalizedNode(id, data);
+ if (dataObjectEntry != null) {
+ dataObject = dataObjectEntry.getValue();
+ }
+ }
+ return dataObject;
+ }
+
+}
+
+
+
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeManager.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeManager.java
new file mode 100644
index 000000000..1082c479b
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.util.concurrent.Futures.immediateCheckedFuture;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.data.DataModification;
+import io.fd.honeycomb.v3po.data.ModifiableDataManager;
+import io.fd.honeycomb.v3po.translate.TranslationException;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataTree backed implementation for modifiable data manager.
+ */
+public class ModifiableDataTreeManager implements ModifiableDataManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeManager.class);
+
+ private final DataTree dataTree;
+
+ public ModifiableDataTreeManager(@Nonnull final DataTree dataTree) {
+ this.dataTree = checkNotNull(dataTree, "dataTree should not be null");
+ }
+
+ @Override
+ public DataModification newModification() {
+ return new ConfigSnapshot();
+ }
+
+ @Override
+ public final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(@Nonnull final YangInstanceIdentifier path) {
+ return newModification().read(path);
+ }
+
+ protected class ConfigSnapshot implements DataModification {
+ private final DataTreeModification modification;
+ private boolean validated = false;
+
+ ConfigSnapshot() {
+ this(dataTree.takeSnapshot().newModification());
+ }
+
+ protected ConfigSnapshot(final DataTreeModification modification) {
+ this.modification = modification;
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+ @Nonnull final YangInstanceIdentifier path) {
+ final Optional<NormalizedNode<?, ?>> node = modification.readNode(path);
+ if (LOG.isTraceEnabled() && node.isPresent()) {
+ LOG.trace("ConfigSnapshot.read: {}", node.get());
+ }
+ return immediateCheckedFuture(node);
+ }
+
+ @Override
+ public final void delete(final YangInstanceIdentifier path) {
+ modification.delete(path);
+ }
+
+ @Override
+ public final void merge(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
+ modification.merge(path, data);
+ }
+
+ @Override
+ public final void write(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
+ modification.write(path, data);
+ }
+
+ @Override
+ public final void commit() throws DataValidationFailedException, TranslationException {
+ if(!validated) {
+ validate();
+ }
+ final DataTreeCandidate candidate = dataTree.prepare(modification);
+ processCandidate(candidate);
+ dataTree.commit(candidate);
+ }
+
+ protected void processCandidate(final DataTreeCandidate candidate) throws TranslationException {
+ // NOOP
+ }
+
+ @Override
+ public final void validate() throws DataValidationFailedException {
+ modification.ready();
+ dataTree.validate(modification);
+ validated = true;
+ }
+ }
+}
+
+
+
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java
new file mode 100644
index 000000000..abc0062de
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+
+/**
+ * Recursively collects and provides all unique and non-null modifications (modified normalized nodes).
+ */
+final class ModificationDiff {
+
+ private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap());
+ private static final EnumSet LEAF_MODIFICATIONS = EnumSet.of(ModificationType.WRITE, ModificationType.DELETE);
+
+ private final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates;
+
+ private ModificationDiff(@Nonnull Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates) {
+ this.updates = updates;
+ }
+
+ /**
+ * Get processed modifications.
+ *
+ * @return mapped modifications, where key is keyed {@link YangInstanceIdentifier}.
+ */
+ Map<YangInstanceIdentifier, NormalizedNodeUpdate> getUpdates() {
+ return updates;
+ }
+
+ private ModificationDiff merge(final ModificationDiff other) {
+ if (this == EMPTY_DIFF) {
+ return other;
+ }
+
+ if (other == EMPTY_DIFF) {
+ return this;
+ }
+
+ return new ModificationDiff(join(updates, other.updates));
+ }
+
+ private static Map<YangInstanceIdentifier, NormalizedNodeUpdate> join(Map<YangInstanceIdentifier, NormalizedNodeUpdate> first,
+ Map<YangInstanceIdentifier, NormalizedNodeUpdate> second) {
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> merged = new HashMap<>();
+ merged.putAll(first);
+ merged.putAll(second);
+ return merged;
+ }
+
+ private static ModificationDiff create(YangInstanceIdentifier id, DataTreeCandidateNode candidate) {
+ return new ModificationDiff(ImmutableMap.of(id, NormalizedNodeUpdate.create(id, candidate)));
+ }
+
+ /**
+ * Produce an aggregated diff from a candidate node recursively. MixinNodes are ignored as modifications and so
+ * are complex nodes which direct leaves were not modified.
+ */
+ @Nonnull
+ static ModificationDiff recursivelyFromCandidate(@Nonnull final YangInstanceIdentifier yangIid,
+ @Nonnull final DataTreeCandidateNode currentCandidate) {
+ // recursively process child nodes for exact modifications
+ return recursivelyChildrenFromCandidate(yangIid, currentCandidate)
+ // also add modification on current level, if elligible
+ .merge(isModification(currentCandidate)
+ ? ModificationDiff.create(yangIid, currentCandidate)
+ : EMPTY_DIFF);
+ }
+
+ /**
+ * Check whether current node was modified. {@link MixinNode}s are ignored
+ * and only nodes which direct leaves(or choices) are modified are considered a modification.
+ */
+ private static Boolean isModification(@Nonnull final DataTreeCandidateNode currentCandidate) {
+ // Mixin nodes are not considered modifications
+ if (isMixin(currentCandidate) && !isAugment(currentCandidate)) {
+ return false;
+ } else {
+ return isCurrentModified(currentCandidate);
+ }
+ }
+
+ private static Boolean isCurrentModified(final @Nonnull DataTreeCandidateNode currentCandidate) {
+ // Check if there are any modified leaves and if so, consider current node as modified
+ final Boolean directLeavesModified = currentCandidate.getChildNodes().stream()
+ .filter(ModificationDiff::isLeaf)
+ // For some reason, we get modifications on unmodified list keys TODO debug and report ODL bug
+ // and that messes up our modifications collection here, so we need to skip
+ .filter(ModificationDiff::isBeforeAndAfterDifferent)
+ .filter(child -> LEAF_MODIFICATIONS.contains(child.getModificationType()))
+ .findFirst()
+ .isPresent();
+
+ return directLeavesModified
+ // Also check choices (choices do not exist in BA world and if anything within a choice was modified,
+ // consider its parent as being modified)
+ || currentCandidate.getChildNodes().stream()
+ .filter(ModificationDiff::isChoice)
+ // Recursively check each choice if there was any change to it
+ .filter(ModificationDiff::isCurrentModified)
+ .findFirst()
+ .isPresent();
+ }
+
+ /**
+ * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}.
+ */
+ private static ModificationDiff recursivelyChildrenFromCandidate(final @Nonnull YangInstanceIdentifier yangIid,
+ final @Nonnull DataTreeCandidateNode currentCandidate) {
+ // recursively process child nodes for specific modifications
+ return currentCandidate.getChildNodes().stream()
+ // not interested in modifications to leaves
+ .filter(child -> !isLeaf(child))
+ .map(candidate -> recursivelyFromCandidate(yangIid.node(candidate.getIdentifier()), candidate))
+ .reduce(ModificationDiff::merge)
+ .orElse(EMPTY_DIFF);
+ }
+
+ /**
+ * Check whether candidate.before and candidate.after is different. If not return false.
+ */
+ private static boolean isBeforeAndAfterDifferent(@Nonnull final DataTreeCandidateNode candidateNode) {
+ if (candidateNode.getDataBefore().isPresent()) {
+ return !candidateNode.getDataBefore().get().equals(candidateNode.getDataAfter().orNull());
+ }
+
+ // considering not a modification if data after is also null
+ return candidateNode.getDataAfter().isPresent();
+ }
+
+ /**
+ * Check whether candidate node is for a leaf type node.
+ */
+ private static boolean isLeaf(final DataTreeCandidateNode candidateNode) {
+ // orNull intentional, some candidate nodes have both data after and data before null
+ return candidateNode.getDataAfter().orNull() instanceof LeafNode<?>
+ || candidateNode.getDataBefore().orNull() instanceof LeafNode<?>;
+ }
+
+ /**
+ * Check whether candidate node is for a Mixin type node.
+ */
+ private static boolean isMixin(final DataTreeCandidateNode candidateNode) {
+ // orNull intentional, some candidate nodes have both data after and data before null
+ return candidateNode.getDataAfter().orNull() instanceof MixinNode
+ || candidateNode.getDataBefore().orNull() instanceof MixinNode;
+ }
+
+ /**
+ * Check whether candidate node is for an Augmentation type node.
+ */
+ private static boolean isAugment(final DataTreeCandidateNode candidateNode) {
+ // orNull intentional, some candidate nodes have both data after and data before null
+ return candidateNode.getDataAfter().orNull() instanceof AugmentationNode
+ || candidateNode.getDataBefore().orNull() instanceof AugmentationNode;
+ }
+
+ /**
+ * Check whether candidate node is for a Choice type node.
+ */
+ private static boolean isChoice(final DataTreeCandidateNode candidateNode) {
+ // orNull intentional, some candidate nodes have both data after and data before null
+ return candidateNode.getDataAfter().orNull() instanceof ChoiceNode
+ || candidateNode.getDataBefore().orNull() instanceof ChoiceNode;
+ }
+
+ @Override
+ public String toString() {
+ return "ModificationDiff{updates=" + updates + '}';
+ }
+
+ /**
+ * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}.
+ */
+ static final class NormalizedNodeUpdate {
+
+ @Nonnull
+ private final YangInstanceIdentifier id;
+ @Nullable
+ private final NormalizedNode<?, ?> dataBefore;
+ @Nullable
+ private final NormalizedNode<?, ?> dataAfter;
+
+ private NormalizedNodeUpdate(@Nonnull final YangInstanceIdentifier id,
+ @Nullable final NormalizedNode<?, ?> dataBefore,
+ @Nullable final NormalizedNode<?, ?> dataAfter) {
+ this.id = checkNotNull(id);
+ this.dataAfter = dataAfter;
+ this.dataBefore = dataBefore;
+ }
+
+ @Nullable
+ public NormalizedNode<?, ?> getDataBefore() {
+ return dataBefore;
+ }
+
+ @Nullable
+ public NormalizedNode<?, ?> getDataAfter() {
+ return dataAfter;
+ }
+
+ @Nonnull
+ public YangInstanceIdentifier getId() {
+ return id;
+ }
+
+ static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
+ @Nonnull final DataTreeCandidateNode candidate) {
+ return create(id, candidate.getDataBefore().orNull(), candidate.getDataAfter().orNull());
+ }
+
+ static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
+ @Nullable final NormalizedNode<?, ?> dataBefore,
+ @Nullable final NormalizedNode<?, ?> dataAfter) {
+ checkArgument(!(dataBefore == null && dataAfter == null), "Both before and after data are null");
+ return new NormalizedNodeUpdate(id, dataBefore, dataAfter);
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ final NormalizedNodeUpdate that = (NormalizedNodeUpdate) other;
+
+ return id.equals(that.id);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "NormalizedNodeUpdate{" + "id=" + id
+ + ", dataBefore=" + dataBefore
+ + ", dataAfter=" + dataAfter
+ + '}';
+ }
+ }
+
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapter.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapter.java
new file mode 100644
index 000000000..9b71dfd62
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapter.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Optional;
+import io.fd.honeycomb.v3po.translate.util.JsonUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Adapter for a DataTree that stores current state of data in backing DataTree on each successful commit.
+ * Uses JSON format.
+ */
+public class PersistingDataTreeAdapter implements DataTree, AutoCloseable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PersistingDataTreeAdapter.class);
+
+ private final DataTree delegateDependency;
+ private final SchemaService schemaServiceDependency;
+ private final Path path;
+
+ /**
+ * Create new Persisting DataTree adapter
+ *
+ * @param delegate backing data tree that actually handles all the operations
+ * @param persistPath path to a file (existing or not) to be used as storage for persistence. Full control over
+ * a file at peristPath is expected
+ * @param schemaService schemaContext provier
+ */
+ public PersistingDataTreeAdapter(@Nonnull final DataTree delegate,
+ @Nonnull final SchemaService schemaService,
+ @Nonnull final Path persistPath) {
+ this.path = testPersistPath(checkNotNull(persistPath, "persistPath is null"));
+ this.delegateDependency = checkNotNull(delegate, "delegate is null");
+ this.schemaServiceDependency = checkNotNull(schemaService, "schemaService is null");
+ }
+
+ /**
+ * Test whether file at persistPath exists and is readable or create it along with its parent structure
+ */
+ private Path testPersistPath(final Path persistPath) {
+ try {
+ checkArgument(!Files.isDirectory(persistPath), "Path %s points to a directory", persistPath);
+ if(Files.exists(persistPath)) {
+ checkArgument(Files.isReadable(persistPath),
+ "Provided path %s points to existing, but non-readable file", persistPath);
+ return persistPath;
+ }
+ Files.createDirectories(persistPath.getParent());
+ Files.write(persistPath, new byte[]{}, StandardOpenOption.CREATE);
+ } catch (IOException | UnsupportedOperationException e) {
+ LOG.warn("Provided path for persistence: {} is not usable", persistPath, e);
+ throw new IllegalArgumentException("Path " + persistPath + " cannot be used as ");
+ }
+
+ return persistPath;
+ }
+
+ @Override
+ public DataTreeSnapshot takeSnapshot() {
+ return delegateDependency.takeSnapshot();
+ }
+
+ @Override
+ public void setSchemaContext(final SchemaContext schemaContext) {
+ delegateDependency.setSchemaContext(schemaContext);
+ }
+
+ @Override
+ public void commit(final DataTreeCandidate dataTreeCandidate) {
+ LOG.trace("Commit detected");
+ delegateDependency.commit(dataTreeCandidate);
+ LOG.debug("Delegate commit successful. Persisting data");
+
+ // FIXME doing full read and full write might not be the fastest way of persisting data here
+ final DataTreeSnapshot dataTreeSnapshot = delegateDependency.takeSnapshot();
+
+ // TODO this can be handled in background by a dedicated thread + a limited blocking queue
+ // TODO enable configurable granularity for persists. Maybe doing write on every modification is too much
+ // and we could do bulk persist
+ persistCurrentData(dataTreeSnapshot.readNode(YangInstanceIdentifier.EMPTY));
+ }
+
+ private void persistCurrentData(final Optional<NormalizedNode<?, ?>> currentRoot) {
+ if(currentRoot.isPresent()) {
+ try {
+ LOG.trace("Persisting current data: {} into: {}", currentRoot.get(), path);
+ JsonUtils.writeJsonRoot(currentRoot.get(), schemaServiceDependency.getGlobalContext(),
+ Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ LOG.trace("Data persisted successfully in {}", path);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to persist current data", e);
+ }
+ } else {
+ LOG.debug("Skipping persistence, since there's no data to persist");
+ }
+ }
+
+ @Override
+ public YangInstanceIdentifier getRootPath() {
+ return delegateDependency.getRootPath();
+ }
+
+ @Override
+ public void validate(final DataTreeModification dataTreeModification) throws DataValidationFailedException {
+ delegateDependency.validate(dataTreeModification);
+ }
+
+ @Override
+ public DataTreeCandidate prepare(
+ final DataTreeModification dataTreeModification) {
+ return delegateDependency.prepare(dataTreeModification);
+ }
+
+ @Override
+ public void close() throws Exception {
+ LOG.trace("Closing {} for {}", getClass().getSimpleName(), path);
+ // NOOP
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransaction.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransaction.java
new file mode 100644
index 000000000..2850a0d9a
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransaction.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+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;
+
+final class ReadOnlyTransaction implements DOMDataReadOnlyTransaction {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ReadOnlyTransaction.class);
+
+ @Nullable
+ private ReadableDataManager operationalData;
+ @Nullable
+ private ReadableDataManager configSnapshot;
+
+ private boolean closed = false;
+
+ /**
+ * @param configData config data tree manager. Null if config reads are not to be supported
+ * @param operationalData operational data tree manager. Null if operational reads are not to be supported
+ */
+ private ReadOnlyTransaction(@Nullable final ReadableDataManager configData,
+ @Nullable final ReadableDataManager operationalData) {
+ this.configSnapshot = configData;
+ this.operationalData = operationalData;
+ }
+
+ @Override
+ public synchronized void close() {
+ closed = true;
+ configSnapshot = null;
+ operationalData = null;
+ }
+
+ @Override
+ public synchronized CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+ final LogicalDatastoreType store,
+ final YangInstanceIdentifier path) {
+ LOG.debug("ReadOnlyTransaction.read(), store={}, path={}", store, path);
+ checkState(!closed, "Transaction has been closed");
+
+ if (store == LogicalDatastoreType.OPERATIONAL) {
+ checkArgument(operationalData != null, "{} reads not supported", store);
+ return operationalData.read(path);
+ } else {
+ checkArgument(configSnapshot != null, "{} reads not supported", store);
+ return configSnapshot.read(path);
+ }
+ }
+
+ @Override
+ public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store,
+ final YangInstanceIdentifier path) {
+ LOG.debug("ReadOnlyTransaction.exists() store={}, path={}", store, path);
+
+ ListenableFuture<Boolean> listenableFuture = Futures.transform(read(store, path), IS_NODE_PRESENT);
+ return Futures.makeChecked(listenableFuture, ANY_EX_TO_READ_FAILED_EXCEPTION_MAPPER);
+ }
+
+ @Nonnull
+ @Override
+ public Object getIdentifier() {
+ return this;
+ }
+
+ @Nonnull
+ static ReadOnlyTransaction createOperationalOnly(@Nonnull final ReadableDataManager operationalData) {
+ return new ReadOnlyTransaction(null, requireNonNull(operationalData));
+ }
+
+ @Nonnull
+ static ReadOnlyTransaction createConfigOnly(@Nonnull final ReadableDataManager configData) {
+ return new ReadOnlyTransaction(requireNonNull(configData), null);
+ }
+
+ @Nonnull
+ static ReadOnlyTransaction create(@Nonnull final ReadableDataManager configData,
+ @Nonnull final ReadableDataManager operationalData) {
+ return new ReadOnlyTransaction(requireNonNull(configData), requireNonNull(operationalData));
+ }
+
+ private static final Function<? super Optional<NormalizedNode<?, ?>>, ? extends Boolean> IS_NODE_PRESENT =
+ (Function<Optional<NormalizedNode<?, ?>>, Boolean>) input -> input == null ? Boolean.FALSE : input.isPresent();
+
+ private static final Function<? super Exception, ReadFailedException> ANY_EX_TO_READ_FAILED_EXCEPTION_MAPPER =
+ (Function<Exception, ReadFailedException>) e -> new ReadFailedException("Exists failed", e);
+} \ No newline at end of file
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransaction.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransaction.java
new file mode 100644
index 000000000..88b46437e
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransaction.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Composite DOM transaction that delegates reads to a {@link DOMDataReadTransaction} delegate and writes to a {@link
+ * DOMDataWriteTransaction} delegate.
+ */
+final class ReadWriteTransaction implements DOMDataReadWriteTransaction {
+
+ private final DOMDataReadOnlyTransaction delegateReadTx;
+ private final DOMDataWriteTransaction delegateWriteTx;
+
+ ReadWriteTransaction(@Nonnull final DOMDataReadOnlyTransaction delegateReadTx,
+ @Nonnull final DOMDataWriteTransaction delegateWriteTx) {
+ this.delegateReadTx = Preconditions.checkNotNull(delegateReadTx, "delegateReadTx should not be null");
+ this.delegateWriteTx = Preconditions.checkNotNull(delegateWriteTx, "delegateWriteTx should not be null");
+ }
+
+ @Override
+ public boolean cancel() {
+ delegateReadTx.close();
+ return delegateWriteTx.cancel();
+ }
+
+ @Override
+ public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data) {
+ delegateWriteTx.put(store, path, data);
+ }
+
+ @Override
+ public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data) {
+ delegateWriteTx.merge(store, path, data);
+ }
+
+ @Override
+ public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ delegateWriteTx.delete(store, path);
+ }
+
+ @Override
+ public CheckedFuture<Void, TransactionCommitFailedException> submit() {
+ return delegateWriteTx.submit();
+ }
+
+ @Override
+ public ListenableFuture<RpcResult<TransactionStatus>> commit() {
+ return delegateWriteTx.commit();
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(final LogicalDatastoreType store,
+ final YangInstanceIdentifier path) {
+ return delegateReadTx.read(store, path);
+ }
+
+ @Override
+ public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store,
+ final YangInstanceIdentifier path) {
+ return delegateReadTx.exists(store, path);
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return this;
+ }
+}
+
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegator.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegator.java
new file mode 100644
index 000000000..aff023ebc
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegator.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import io.fd.honeycomb.v3po.translate.MappingContext;
+import io.fd.honeycomb.v3po.translate.ModificationCache;
+import io.fd.honeycomb.v3po.translate.read.ReadContext;
+import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
+import io.fd.honeycomb.v3po.translate.read.registry.ReaderRegistry;
+import io.fd.honeycomb.v3po.translate.util.TransactionMappingContext;
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+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.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ReadableDataTree implementation for operational data.
+ */
+public final class ReadableDataTreeDelegator implements ReadableDataManager {
+ private static final Logger LOG = LoggerFactory.getLogger(ReadableDataTreeDelegator.class);
+
+ private final BindingNormalizedNodeSerializer serializer;
+ private final ReaderRegistry readerRegistry;
+ private final SchemaContext globalContext;
+ private final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker;
+
+ /**
+ * Creates operational data tree instance.
+ * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
+ * representation.
+ * @param globalContext service for obtaining top level context data from all yang modules.
+ * @param readerRegistry service responsible for translation between DataObjects and data provider.
+ * @param contextBroker BA broker for context data
+ */
+ public ReadableDataTreeDelegator(@Nonnull BindingNormalizedNodeSerializer serializer,
+ @Nonnull final SchemaContext globalContext,
+ @Nonnull final ReaderRegistry readerRegistry,
+ @Nonnull final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker) {
+ this.contextBroker = checkNotNull(contextBroker, "contextBroker should not be null");
+ this.globalContext = checkNotNull(globalContext, "globalContext should not be null");
+ this.serializer = checkNotNull(serializer, "serializer should not be null");
+ this.readerRegistry = checkNotNull(readerRegistry, "reader should not be null");
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>,
+ org.opendaylight.controller.md.sal.common.api.data.ReadFailedException> read(
+ @Nonnull final YangInstanceIdentifier yangInstanceIdentifier) {
+
+ try(TransactionMappingContext mappingContext = new TransactionMappingContext(contextBroker.newReadWriteTransaction());
+ ReadContext ctx = new ReadContextImpl(mappingContext)) {
+
+ final Optional<NormalizedNode<?, ?>> value;
+ if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) {
+ value = readRoot(ctx);
+ } else {
+ value = readNode(yangInstanceIdentifier, ctx);
+ }
+
+ // Submit context mapping updates
+ final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
+ ((TransactionMappingContext) ctx.getMappingContext()).submit();
+ // Blocking on context data update
+ contextUpdateResult.checkedGet();
+
+ return Futures.immediateCheckedFuture(value);
+
+ } catch (ReadFailedException e) {
+ return Futures.immediateFailedCheckedFuture(
+ new org.opendaylight.controller.md.sal.common.api.data.ReadFailedException(
+ "Failed to read VPP data", e));
+ } catch (TransactionCommitFailedException e) {
+ // FIXME revert should probably occur when context is not written successfully
+ final String msg = "Error while updating mapping context data";
+ LOG.error(msg, e);
+ return Futures.immediateFailedCheckedFuture(
+ new org.opendaylight.controller.md.sal.common.api.data.ReadFailedException(msg, e)
+ );
+ }
+ }
+
+ private Optional<NormalizedNode<?, ?>> readNode(final YangInstanceIdentifier yangInstanceIdentifier,
+ final ReadContext ctx) throws ReadFailedException {
+ LOG.debug("OperationalDataTree.readNode(), yangInstanceIdentifier={}", yangInstanceIdentifier);
+ final InstanceIdentifier<?> path = serializer.fromYangInstanceIdentifier(yangInstanceIdentifier);
+ checkNotNull(path, "Invalid instance identifier %s. Cannot create BA equivalent.", yangInstanceIdentifier);
+ LOG.debug("OperationalDataTree.readNode(), path={}", path);
+
+ final Optional<? extends DataObject> dataObject;
+
+ dataObject = readerRegistry.read(path, ctx);
+ if (dataObject.isPresent()) {
+ final NormalizedNode<?, ?> value = toNormalizedNodeFunction(path).apply(dataObject.get());
+ return Optional.<NormalizedNode<?, ?>>fromNullable(value);
+ } else {
+ return Optional.absent();
+ }
+ }
+
+ private Optional<NormalizedNode<?, ?>> readRoot(final ReadContext ctx) throws ReadFailedException {
+ LOG.debug("OperationalDataTree.readRoot()");
+
+ final DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> dataNodeBuilder =
+ Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME));
+
+ final Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> dataObjects =
+ readerRegistry.readAll(ctx);
+
+ for (final InstanceIdentifier<? extends DataObject> instanceIdentifier : dataObjects.keySet()) {
+ final YangInstanceIdentifier rootElementId = serializer.toYangInstanceIdentifier(instanceIdentifier);
+ final NormalizedNode<?, ?> node =
+ wrapDataObjects(rootElementId, instanceIdentifier, dataObjects.get(instanceIdentifier));
+ dataNodeBuilder.withChild((DataContainerChild<?, ?>) node);
+ }
+ return Optional.<NormalizedNode<?, ?>>of(dataNodeBuilder.build());
+ }
+
+ private NormalizedNode<?, ?> wrapDataObjects(final YangInstanceIdentifier yangInstanceIdentifier,
+ final InstanceIdentifier<? extends DataObject> instanceIdentifier,
+ final Collection<? extends DataObject> dataObjects) {
+ final Collection<NormalizedNode<?, ?>> normalizedRootElements = Collections2
+ .transform(dataObjects, toNormalizedNodeFunction(instanceIdentifier));
+
+ final DataSchemaNode schemaNode =
+ globalContext.getDataChildByName(yangInstanceIdentifier.getLastPathArgument().getNodeType());
+ if (schemaNode instanceof ListSchemaNode) {
+ // In case of a list, wrap all the values in a Mixin parent node
+ final ListSchemaNode listSchema = (ListSchemaNode) schemaNode;
+ return wrapListIntoMixinNode(normalizedRootElements, listSchema);
+ } else {
+ Preconditions.checkState(dataObjects.size() == 1, "Singleton list was expected");
+ return getOnlyElement(normalizedRootElements);
+ }
+ }
+
+ private static DataContainerChild<?, ?> wrapListIntoMixinNode(
+ final Collection<NormalizedNode<?, ?>> normalizedRootElements, final ListSchemaNode listSchema) {
+ if (listSchema.getKeyDefinition().isEmpty()) {
+ final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> listBuilder =
+ Builders.unkeyedListBuilder();
+ for (NormalizedNode<?, ?> normalizedRootElement : normalizedRootElements) {
+ listBuilder.withChild((UnkeyedListEntryNode) normalizedRootElement);
+ }
+ return listBuilder.build();
+ } else {
+ final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> listBuilder =
+ listSchema.isUserOrdered()
+ ? Builders.orderedMapBuilder()
+ : Builders.mapBuilder();
+
+ for (NormalizedNode<?, ?> normalizedRootElement : normalizedRootElements) {
+ listBuilder.withChild((MapEntryNode) normalizedRootElement);
+ }
+ return listBuilder.build();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Function<DataObject, NormalizedNode<?, ?>> toNormalizedNodeFunction(final InstanceIdentifier path) {
+ return dataObject -> {
+ LOG.trace("OperationalDataTree.toNormalizedNode(), path={}, dataObject={}", path, dataObject);
+ final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry =
+ serializer.toNormalizedNode(path, dataObject);
+
+ LOG.trace("OperationalDataTree.toNormalizedNode(), normalizedNodeEntry={}", entry);
+ return entry.getValue();
+ };
+ }
+
+ private static final class ReadContextImpl implements ReadContext {
+
+ private final ModificationCache ctx = new ModificationCache();
+ private final MappingContext mappingContext;
+
+ private ReadContextImpl(final MappingContext mappingContext) {
+ this.mappingContext = mappingContext;
+ }
+
+ @Nonnull
+ @Override
+ public ModificationCache getModificationCache() {
+ return ctx;
+ }
+
+ @Nonnull
+ @Override
+ public MappingContext getMappingContext() {
+ return mappingContext;
+ }
+
+ @Override
+ public void close() {
+ // Make sure to clear the storage in case some customizer stored a reference to it to prevent memory leaks
+ ctx.close();
+ }
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/WriteTransaction.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/WriteTransaction.java
new file mode 100644
index 000000000..c8f9bd3db
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/WriteTransaction.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.controller.md.sal.common.api.TransactionStatus.CANCELED;
+import static org.opendaylight.controller.md.sal.common.api.TransactionStatus.COMMITED;
+import static org.opendaylight.controller.md.sal.common.api.TransactionStatus.FAILED;
+import static org.opendaylight.controller.md.sal.common.api.TransactionStatus.NEW;
+import static org.opendaylight.controller.md.sal.common.api.TransactionStatus.SUBMITED;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import io.fd.honeycomb.v3po.data.DataModification;
+import io.fd.honeycomb.v3po.translate.TranslationException;
+import java.util.function.Consumer;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@NotThreadSafe
+final class WriteTransaction implements DOMDataWriteTransaction {
+
+ private static final Logger LOG = LoggerFactory.getLogger(WriteTransaction.class);
+
+ @Nullable
+ private DataModification operationalModification;
+ @Nullable
+ private DataModification configModification;
+ private TransactionStatus status = NEW;
+
+ private WriteTransaction(@Nullable final DataModification configModification,
+ @Nullable final DataModification operationalModification) {
+ this.operationalModification = operationalModification;
+ this.configModification = configModification;
+ }
+
+ private void checkIsNew() {
+ Preconditions.checkState(status == NEW, "Transaction was submitted or canceled");
+ }
+
+ @Override
+ public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data) {
+ LOG.debug("WriteTransaction.put() store={}, path={}, data={}", store, path, data);
+ checkIsNew();
+ handleOperation(store, (modification) -> modification.write(path, data));
+ }
+
+ private void handleOperation(final LogicalDatastoreType store,
+ final Consumer<DataModification> r) {
+ switch (store) {
+ case CONFIGURATION:
+ checkArgument(configModification != null, "Modification of %s is not supported", store);
+ r.accept(configModification);
+ break;
+ case OPERATIONAL:
+ checkArgument(operationalModification != null, "Modification of %s is not supported", store);
+ r.accept(operationalModification);
+ break;
+ }
+ }
+
+ @Override
+ public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data) {
+ LOG.debug("WriteTransaction.merge() store={}, path={}, data={}", store, path, data);
+ checkIsNew();
+ handleOperation(store, (modification) -> modification.merge(path, data));
+ }
+
+ @Override
+ public boolean cancel() {
+ if (status != NEW) {
+ // only NEW transactions can be cancelled
+ return false;
+ } else {
+ status = CANCELED;
+ return true;
+ }
+ }
+
+ @Override
+ public void delete(LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ LOG.debug("WriteTransaction.delete() store={}, path={}", store, path);
+ checkIsNew();
+ handleOperation(store, (modification) -> modification.delete(path));
+ }
+
+ @Override
+ public CheckedFuture<Void, TransactionCommitFailedException> submit() {
+ LOG.trace("WriteTransaction.submit()");
+ checkIsNew();
+
+ try {
+ status = SUBMITED;
+
+ // Validate first to catch any issues before attempting commit
+ if (configModification != null) {
+ configModification.validate();
+ }
+ if (operationalModification != null) {
+ operationalModification.validate();
+ }
+
+ if(configModification != null) {
+ configModification.commit();
+ }
+ if(operationalModification != null) {
+ operationalModification.commit();
+ }
+
+ status = COMMITED;
+ } catch (DataValidationFailedException | TranslationException e) {
+ status = FAILED;
+ LOG.error("Failed modify data tree", e);
+ return Futures.immediateFailedCheckedFuture(
+ new TransactionCommitFailedException("Failed to validate DataTreeModification", e));
+ }
+ return Futures.immediateCheckedFuture(null);
+ }
+
+ @Override
+ @Deprecated
+ public ListenableFuture<RpcResult<TransactionStatus>> commit() {
+ throw new UnsupportedOperationException("deprecated");
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return this;
+ }
+
+
+ @Nonnull
+ static WriteTransaction createOperationalOnly(@Nonnull final DataModification operationalData) {
+ return new WriteTransaction(null, requireNonNull(operationalData));
+ }
+
+ @Nonnull
+ static WriteTransaction createConfigOnly(@Nonnull final DataModification configData) {
+ return new WriteTransaction(requireNonNull(configData), null);
+ }
+
+ @Nonnull
+ static WriteTransaction create(@Nonnull final DataModification configData,
+ @Nonnull final DataModification operationalData) {
+ return new WriteTransaction(requireNonNull(configData), requireNonNull(operationalData));
+ }
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java
new file mode 100644
index 000000000..eabcdcbc8
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java
@@ -0,0 +1,71 @@
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.data.DataModification;
+import io.fd.honeycomb.v3po.data.ModifiableDataManager;
+import io.fd.honeycomb.v3po.data.impl.ModifiableDataTreeDelegator;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+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;
+
+public class ConfigDataTreeModule extends
+ org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractConfigDataTreeModule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ConfigDataTreeModule.class);
+
+ public ConfigDataTreeModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public ConfigDataTreeModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.ConfigDataTreeModule oldModule,
+ java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ // add custom validation form module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ LOG.debug("ConfigDataTreeModule.createInstance()");
+ return new CloseableConfigDataTree(
+ new ModifiableDataTreeDelegator(getSerializerDependency(), getDataTreeDependency(),
+ getWriterRegistryBuilderDependency().build(), getContextBindingBrokerDependency()));
+ }
+
+ private static final class CloseableConfigDataTree implements ModifiableDataManager, AutoCloseable {
+
+ private final ModifiableDataTreeDelegator delegate;
+
+ CloseableConfigDataTree(final ModifiableDataTreeDelegator delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void close() throws Exception {
+ LOG.debug("CloseableConfigDataTree.close()");
+ // NOP
+ }
+
+ @Override
+ public DataModification newModification() {
+ LOG.trace("CloseableConfigDataTree.newModification");
+ return delegate.newModification();
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+ @Nonnull final YangInstanceIdentifier path) {
+ return delegate.read(path);
+ }
+ }
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModuleFactory.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModuleFactory.java
new file mode 100644
index 000000000..19baab13a
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModuleFactory.java
@@ -0,0 +1,13 @@
+/*
+* Generated file
+*
+* Generated from: yang module name: data-impl yang module local name: config-data-tree
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Sun Apr 10 22:07:29 CEST 2016
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+public class ConfigDataTreeModuleFactory extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractConfigDataTreeModuleFactory {
+
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModule.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModule.java
new file mode 100644
index 000000000..99e5b396c
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModule.java
@@ -0,0 +1,79 @@
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+
+import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.api.rev160411.DatatreeType;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.*;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InMemoryDataTreeModule extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractInMemoryDataTreeModule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModule.class);
+
+ public InMemoryDataTreeModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public InMemoryDataTreeModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.InMemoryDataTreeModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ // add custom validation form module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ LOG.debug("InMemoryConfigDataTreeModule.createInstance()");
+ return new CloseableConfigDataTree(getSchemaServiceDependency().getGlobalContext(), getType());
+ }
+
+ private static class CloseableConfigDataTree implements AutoCloseable, DataTree {
+ private final DataTree dataTree;
+
+ CloseableConfigDataTree(final SchemaContext schemaContext, final DatatreeType type) {
+ this.dataTree = InMemoryDataTreeFactory.getInstance().create(
+ type == DatatreeType.Config ? TreeType.CONFIGURATION : TreeType.OPERATIONAL
+ );
+ dataTree.setSchemaContext(schemaContext);
+ }
+
+ @Override
+ public void close() throws Exception {
+ // NOP
+ }
+
+ @Override
+ public DataTreeSnapshot takeSnapshot() {
+ return dataTree.takeSnapshot();
+ }
+
+ @Override
+ public void setSchemaContext(final SchemaContext newSchemaContext) {
+ dataTree.setSchemaContext(newSchemaContext);
+ }
+
+ @Override
+ public void commit(final DataTreeCandidate candidate) {
+ dataTree.commit(candidate);
+ }
+
+ @Override
+ public YangInstanceIdentifier getRootPath() {
+ return dataTree.getRootPath();
+ }
+
+ @Override
+ public void validate(final DataTreeModification modification) throws DataValidationFailedException {
+ dataTree.validate(modification);
+ }
+
+ @Override
+ public DataTreeCandidate prepare(final DataTreeModification modification) {
+ return dataTree.prepare(modification);
+ }
+ }
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModuleFactory.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModuleFactory.java
new file mode 100644
index 000000000..34a543949
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/InMemoryDataTreeModuleFactory.java
@@ -0,0 +1,13 @@
+/*
+* Generated file
+*
+* Generated from: yang module name: data-impl yang module local name: inmemory-data-tree
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Fri May 13 11:10:56 CEST 2016
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+public class InMemoryDataTreeModuleFactory extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractInMemoryDataTreeModuleFactory {
+
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModule.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModule.java
new file mode 100644
index 000000000..1526fddca
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModule.java
@@ -0,0 +1,65 @@
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import io.fd.honeycomb.v3po.data.impl.ReadableDataTreeDelegator;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+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;
+
+public class OperationalDataTreeModule extends
+ org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractOperationalDataTreeModule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OperationalDataTreeModule.class);
+
+ public OperationalDataTreeModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public OperationalDataTreeModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.OperationalDataTreeModule oldModule,
+ java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ // add custom validation form module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ LOG.debug("OperationalDataTreeModule.createInstance()");
+ return new CloseableOperationalDataTree(
+ new ReadableDataTreeDelegator(getSerializerDependency(), getSchemaServiceDependency().getGlobalContext(),
+ getReaderRegistryBuilderDependency().build(), getContextBindingBrokerDependency()));
+ }
+
+ private static final class CloseableOperationalDataTree implements ReadableDataManager, AutoCloseable {
+
+ private final ReadableDataTreeDelegator delegate;
+
+ CloseableOperationalDataTree(final ReadableDataTreeDelegator delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void close() throws Exception {
+ LOG.debug("CloseableOperationalDataTree.close()");
+ // NOP
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+ @Nonnull final YangInstanceIdentifier path) {
+ LOG.trace("CloseableOperationalDataTree.read path={}", path);
+ return delegate.read(path);
+ }
+ }
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModuleFactory.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModuleFactory.java
new file mode 100644
index 000000000..221eabe5c
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/OperationalDataTreeModuleFactory.java
@@ -0,0 +1,13 @@
+/*
+* Generated file
+*
+* Generated from: yang module name: data-impl yang module local name: honeycomb-operational-data-tree
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Sun Apr 10 23:52:21 CEST 2016
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+public class OperationalDataTreeModuleFactory extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractOperationalDataTreeModuleFactory {
+
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModule.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModule.java
new file mode 100644
index 000000000..145fc4345
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModule.java
@@ -0,0 +1,38 @@
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+
+import java.nio.file.InvalidPathException;
+import java.nio.file.Paths;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+
+public class PersistingDataTreeAdapterModule extends
+ org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractPersistingDataTreeAdapterModule {
+ public PersistingDataTreeAdapterModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public PersistingDataTreeAdapterModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.PersistingDataTreeAdapterModule oldModule,
+ java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ try {
+ Paths.get(getPersistFilePath());
+ } catch (InvalidPathException e) {
+ throw new JmxAttributeValidationException("Invalid persist path", e, persistFilePathJmxAttribute);
+ }
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ return new io.fd.honeycomb.v3po.data.impl.PersistingDataTreeAdapter(
+ getDelegateDependency(),
+ getSchemaServiceDependency(),
+ Paths.get(getPersistFilePath()));
+ }
+
+}
diff --git a/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModuleFactory.java b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModuleFactory.java
new file mode 100644
index 000000000..0b7546c65
--- /dev/null
+++ b/infra/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/PersistingDataTreeAdapterModuleFactory.java
@@ -0,0 +1,13 @@
+/*
+* Generated file
+*
+* Generated from: yang module name: data-impl yang module local name: persisting-data-tree-adapter
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Thu May 12 14:07:24 CEST 2016
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411;
+public class PersistingDataTreeAdapterModuleFactory extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl.rev160411.AbstractPersistingDataTreeAdapterModuleFactory {
+
+}
diff --git a/infra/data-impl/src/main/yang/data-impl.yang b/infra/data-impl/src/main/yang/data-impl.yang
new file mode 100644
index 000000000..ee485538b
--- /dev/null
+++ b/infra/data-impl/src/main/yang/data-impl.yang
@@ -0,0 +1,179 @@
+module data-impl {
+ yang-version 1;
+ namespace "urn:honeycomb:params:xml:ns:yang:data:impl";
+ prefix "tutils";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import opendaylight-md-sal-binding { prefix md-sal-binding; revision-date 2013-10-28;}
+ import opendaylight-md-sal-dom { prefix dom; revision-date 2013-10-28;}
+ import data-api { prefix dapi; revision-date 2016-04-11; }
+ import translate-api { prefix tapi; revision-date 2016-04-06; }
+
+ description
+ "This module contains YANG module definitions
+ for honeycomd data layer";
+
+ revision "2016-04-11" {
+ description
+ "Initial revision.";
+ }
+
+ identity inmemory-data-tree {
+ base config:module-type;
+ config:provided-service dapi:data-tree;
+ config:java-name-prefix InMemoryDataTree;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case inmemory-data-tree {
+ when "/config:modules/config:module/config:type = 'inmemory-data-tree'";
+
+ container schema-service {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity dom:schema-service;
+ }
+ }
+ }
+
+ leaf type {
+ type dapi:datatree-type;
+ mandatory true;
+ }
+ }
+ }
+
+ identity persisting-data-tree-adapter {
+ base config:module-type;
+ config:provided-service dapi:data-tree;
+ config:java-name-prefix PersistingDataTreeAdapter;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case persisting-data-tree-adapter {
+ when "/config:modules/config:module/config:type = 'persisting-data-tree-adapter'";
+
+ container delegate {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity dapi:data-tree;
+ }
+ }
+ }
+
+ container schema-service {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity dom:schema-service;
+ }
+ }
+ }
+
+ leaf persist-file-path {
+ type string;
+ mandatory true;
+ description "Path to a file to be used as data storage";
+ }
+ }
+ }
+
+ identity honeycomb-config-data-tree {
+ base config:module-type;
+ config:provided-service dapi:honeycomb-modifiable-data-tree;
+ config:java-name-prefix ConfigDataTree;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case honeycomb-config-data-tree {
+ when "/config:modules/config:module/config:type = 'honeycomb-config-data-tree'";
+
+ container data-tree {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity dapi:data-tree;
+ }
+ }
+ }
+
+ container serializer {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity md-sal-binding:binding-normalized-node-serializer;
+ }
+ }
+ }
+
+ container writer-registry-builder {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity tapi:honeycomb-writer-registry-builder;
+ }
+ }
+ }
+
+ container context-binding-broker {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity md-sal-binding:binding-async-data-broker;
+ }
+ }
+ }
+ }
+ }
+
+ identity honeycomb-operational-data-tree {
+ base config:module-type;
+ config:provided-service dapi:honeycomb-readable-data-tree;
+ config:java-name-prefix OperationalDataTree;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case honeycomb-operational-data-tree {
+ when "/config:modules/config:module/config:type = 'honeycomb-operational-data-tree'";
+
+ container schema-service {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity dom:schema-service;
+ }
+ }
+ }
+
+ container serializer {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity md-sal-binding:binding-normalized-node-serializer;
+ }
+ }
+ }
+
+ container reader-registry-builder {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity tapi:honeycomb-reader-registry-builder;
+ }
+ }
+ }
+
+ container context-binding-broker {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity md-sal-binding:binding-async-data-broker;
+ }
+ }
+ }
+
+ }
+ }
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java
new file mode 100644
index 000000000..55b92b50b
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import io.fd.honeycomb.v3po.data.ModifiableDataManager;
+import io.fd.honeycomb.v3po.data.DataModification;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+public class DataBrokerTest {
+
+ @Mock
+ private ReadableDataManager operationalData;
+ @Mock
+ private ModifiableDataManager confiDataTree;
+ @Mock
+ private DataModification configSnapshot;
+ private DataBroker broker;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ when(confiDataTree.newModification()).thenReturn(configSnapshot);
+ broker = DataBroker.create(confiDataTree, operationalData);
+ }
+
+ @Test
+ public void testNewReadWriteTransaction() {
+ final DOMDataReadWriteTransaction readWriteTx = broker.newReadWriteTransaction();
+ final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class);
+ readWriteTx.read(LogicalDatastoreType.CONFIGURATION, path);
+
+ // verify that read and write transactions use the same config snapshot
+ verify(configSnapshot).read(path);
+ verify(confiDataTree).newModification();
+ }
+
+ @Test
+ public void testNewWriteOnlyTransaction() {
+ final DOMDataWriteTransaction writeTx = broker.newWriteOnlyTransaction();
+
+ // verify that write transactions use config snapshot
+ verify(confiDataTree).newModification();
+ }
+
+ @Test
+ public void testNewReadOnlyTransaction() {
+ final DOMDataReadOnlyTransaction readTx = broker.newReadOnlyTransaction();
+
+ final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class);
+ readTx.read(LogicalDatastoreType.CONFIGURATION, path);
+
+ // verify that read transactions use config snapshot
+ verify(configSnapshot).read(path);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testRegisterDataChangeListener() {
+ final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class);
+ final DOMDataChangeListener listener = mock(DOMDataChangeListener.class);
+ broker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, listener,
+ AsyncDataBroker.DataChangeScope.BASE);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testCreateTransactionChain() {
+ final TransactionChainListener listener = mock(TransactionChainListener.class);
+ broker.createTransactionChain(listener);
+ }
+
+ @Test
+ public void testGetSupportedExtensions() {
+ final Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> supportedExtensions =
+ broker.getSupportedExtensions();
+ assertTrue(supportedExtensions.isEmpty());
+ }
+
+
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java
new file mode 100644
index 000000000..915d738e9
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.data.DataModification;
+import io.fd.honeycomb.v3po.translate.TranslationException;
+import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import io.fd.honeycomb.v3po.translate.write.registry.WriterRegistry;
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+
+public class ModifiableDataTreeDelegatorTest {
+
+ @Mock
+ private WriterRegistry writer;
+ @Mock
+ private BindingNormalizedNodeSerializer serializer;
+ private DataTree dataTree;
+ @Mock
+ private org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification modification;
+ @Mock
+ private DataBroker contextBroker;
+ @Mock
+ private org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction tx;
+
+ private ModifiableDataTreeManager configDataTree;
+
+ static final InstanceIdentifier<?> DEFAULT_ID = InstanceIdentifier.create(DataObject.class);
+ static DataObject DEFAULT_DATA_OBJECT = mockDataObject("serialized", DataObject.class);
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ dataTree = ModificationDiffTest.getDataTree();
+ when(contextBroker.newReadWriteTransaction()).thenReturn(tx);
+ when(tx.submit()).thenReturn(Futures.immediateCheckedFuture(null));
+
+ when(serializer.fromYangInstanceIdentifier(any(YangInstanceIdentifier.class))).thenReturn(((InstanceIdentifier) DEFAULT_ID));
+ final Map.Entry<InstanceIdentifier<?>, DataObject> parsed = new AbstractMap.SimpleEntry<>(DEFAULT_ID, DEFAULT_DATA_OBJECT);
+ when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), any(NormalizedNode.class))).thenReturn(parsed);
+
+ configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, writer, contextBroker);
+ }
+
+ @Test
+ public void testRead() throws Exception {
+ final ContainerNode topContainer = ModificationDiffTest.getTopContainer("topContainer");
+ ModificationDiffTest.addNodeToTree(dataTree, topContainer, ModificationDiffTest.TOP_CONTAINER_ID);
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read =
+ configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID);
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read2 =
+ configDataTree.newModification().read(ModificationDiffTest.TOP_CONTAINER_ID);
+ final Optional<NormalizedNode<?, ?>> normalizedNodeOptional = read.get();
+ final Optional<NormalizedNode<?, ?>> normalizedNodeOptional2 = read2.get();
+
+ assertEquals(normalizedNodeOptional, normalizedNodeOptional2);
+ assertTrue(normalizedNodeOptional.isPresent());
+ assertEquals(topContainer, normalizedNodeOptional.get());
+ assertEquals(dataTree.takeSnapshot().readNode(ModificationDiffTest.TOP_CONTAINER_ID), normalizedNodeOptional);
+ }
+
+ @Test
+ public void testCommitSuccessful() throws Exception {
+ final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+
+ final DataModification dataModification = configDataTree.newModification();
+ dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+ dataModification.validate();
+ dataModification.commit();
+
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate> map = HashMultimap.create();
+ map.put(DEFAULT_ID, DataObjectUpdate.create(DEFAULT_ID, DEFAULT_DATA_OBJECT, DEFAULT_DATA_OBJECT));
+ verify(writer).update(eq(new WriterRegistry.DataObjectUpdates(map, ImmutableMultimap.of())), any(WriteContext.class));
+ assertEquals(nestedList, dataTree.takeSnapshot().readNode(ModificationDiffTest.NESTED_LIST_ID).get());
+ }
+
+ private static DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
+ final DataObject dataBefore = mock(classToMock, name);
+ doReturn(classToMock).when(dataBefore).getImplementedInterface();
+ return dataBefore;
+ }
+
+ @Test
+ public void testCommitUndoSuccessful() throws Exception {
+ final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+
+ // Fail on update:
+ final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
+ final TranslationException failedOnUpdateException = new TranslationException("update failed");
+ doThrow(new WriterRegistry.BulkUpdateException(Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException))
+ .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+
+ try {
+ // Run the test
+ final DataModification dataModification = configDataTree.newModification();
+ dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+ dataModification.validate();
+ dataModification.commit();
+ fail("WriterRegistry.BulkUpdateException was expected");
+ } catch (WriterRegistry.BulkUpdateException e) {
+ verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+ assertThat(e.getFailedIds(), hasItem(DEFAULT_ID));
+ verify(reverter).revert();
+ assertEquals(failedOnUpdateException, e.getCause());
+ }
+ }
+
+ @Test
+ public void testCommitUndoFailed() throws Exception {
+ final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+
+ // Fail on update:
+ final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
+ final TranslationException failedOnUpdateException = new TranslationException("update failed");
+ doThrow(new WriterRegistry.BulkUpdateException(Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException))
+ .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+
+ // Fail on revert:
+ final TranslationException failedOnRevertException = new TranslationException("revert failed");
+ doThrow(new WriterRegistry.Reverter.RevertFailedException(Collections.emptySet(), failedOnRevertException))
+ .when(reverter).revert();
+
+ try {
+ // Run the test
+ final DataModification dataModification = configDataTree.newModification();
+ dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+ dataModification.validate();
+ dataModification.commit();
+ fail("WriterRegistry.Reverter.RevertFailedException was expected");
+ } catch (WriterRegistry.Reverter.RevertFailedException e) {
+ verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+ verify(reverter).revert();
+ assertEquals(failedOnRevertException, e.getCause());
+ }
+ }
+
+ private abstract static class DataObject1 implements DataObject {}
+ private abstract static class DataObject2 implements DataObject {}
+ private abstract static class DataObject3 implements DataObject {}
+
+ @Test
+ public void testToBindingAware() throws Exception {
+ when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes = new HashMap<>();
+ // delete
+ final QName nn1 = QName.create("namespace", "nn1");
+ final YangInstanceIdentifier yid1 = mockYid(nn1);
+ final InstanceIdentifier iid1 = mockIid(yid1, DataObject1.class);
+ final NormalizedNode nn1B = mockNormalizedNode(nn1);
+ final DataObject1 do1B = mockDataObject(yid1, iid1, nn1B, DataObject1.class);
+ biNodes.put(yid1, ModificationDiff.NormalizedNodeUpdate.create(yid1, nn1B, null));
+
+ // create
+ final QName nn2 = QName.create("namespace", "nn1");
+ final YangInstanceIdentifier yid2 = mockYid(nn2);
+ final InstanceIdentifier iid2 = mockIid(yid2, DataObject2.class);;
+ final NormalizedNode nn2A = mockNormalizedNode(nn2);
+ final DataObject2 do2A = mockDataObject(yid2, iid2, nn2A, DataObject2.class);
+ biNodes.put(yid2, ModificationDiff.NormalizedNodeUpdate.create(yid2, null, nn2A));
+
+ // update
+ final QName nn3 = QName.create("namespace", "nn1");
+ final YangInstanceIdentifier yid3 = mockYid(nn3);
+ final InstanceIdentifier iid3 = mockIid(yid3, DataObject3.class);
+ final NormalizedNode nn3B = mockNormalizedNode(nn3);
+ final DataObject3 do3B = mockDataObject(yid3, iid3, nn3B, DataObject3.class);
+ final NormalizedNode nn3A = mockNormalizedNode(nn3);
+ final DataObject3 do3A = mockDataObject(yid3, iid3, nn3A, DataObject3.class);;
+ biNodes.put(yid3, ModificationDiff.NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
+
+ final WriterRegistry.DataObjectUpdates dataObjectUpdates =
+ ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
+
+ assertThat(dataObjectUpdates.getDeletes().size(), is(1));
+ assertThat(dataObjectUpdates.getDeletes().keySet(), hasItem(((InstanceIdentifier<?>) iid1)));
+ assertThat(dataObjectUpdates.getDeletes().values(), hasItem(
+ ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid1, do1B, null))));
+
+ assertThat(dataObjectUpdates.getUpdates().size(), is(2));
+ assertThat(dataObjectUpdates.getUpdates().keySet(), hasItems((InstanceIdentifier<?>) iid2, (InstanceIdentifier<?>) iid3));
+ assertThat(dataObjectUpdates.getUpdates().values(), hasItems(
+ DataObjectUpdate.create(iid2, null, do2A),
+ DataObjectUpdate.create(iid3, do3B, do3A)));
+
+ assertThat(dataObjectUpdates.getTypeIntersection().size(), is(3));
+ }
+
+ private <D extends DataObject> D mockDataObject(final YangInstanceIdentifier yid1,
+ final InstanceIdentifier iid1,
+ final NormalizedNode nn1B,
+ final Class<D> type) {
+ final D do1B = mock(type);
+ when(serializer.fromNormalizedNode(yid1, nn1B)).thenReturn(new AbstractMap.SimpleEntry<>(iid1, do1B));
+ return do1B;
+ }
+
+ private NormalizedNode mockNormalizedNode(final QName nn1) {
+ final NormalizedNode nn1B = mock(NormalizedNode.class);
+ when(nn1B.getNodeType()).thenReturn(nn1);
+ return nn1B;
+ }
+
+ private InstanceIdentifier mockIid(final YangInstanceIdentifier yid1,
+ final Class<? extends DataObject> type) {
+ final InstanceIdentifier iid1 = InstanceIdentifier.create(type);
+ when(serializer.fromYangInstanceIdentifier(yid1)).thenReturn(iid1);
+ return iid1;
+ }
+
+ private YangInstanceIdentifier mockYid(final QName nn1) {
+ final YangInstanceIdentifier yid1 = mock(YangInstanceIdentifier.class);
+ when(yid1.getLastPathArgument()).thenReturn(new YangInstanceIdentifier.NodeIdentifier(nn1));
+ return yid1;
+ }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java
new file mode 100644
index 000000000..bc7582e93
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java
@@ -0,0 +1,427 @@
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.Map;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+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.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+
+public class ModificationDiffTest {
+
+ static final QName TOP_CONTAINER_QNAME =
+ QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "top-container");
+ static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
+ static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
+ static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
+ static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
+ static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
+
+ static final QName WITH_CHOICE_CONTAINER_QNAME =
+ QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "with-choice");
+ static final QName CHOICE_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "choice");
+ static final QName IN_CASE1_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case1");
+ static final QName IN_CASE2_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case2");
+
+ static final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
+ static final YangInstanceIdentifier NESTED_LIST_ID = TOP_CONTAINER_ID.node(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+
+ @Test
+ public void testInitialWrite() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ final NormalizedNode<?, ?> topContainer = getTopContainer("string1");
+ final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
+ dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepare);
+
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ assertThat(modificationDiff.getUpdates().values().size(), is(1));
+ assertUpdate(modificationDiff.getUpdates().values().iterator().next(), TOP_CONTAINER_ID, null, topContainer);
+ }
+
+ @Test
+ public void testInitialWriteForContainerWithChoice() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ final ContainerNode containerWithChoice = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(WITH_CHOICE_CONTAINER_QNAME))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(CHOICE_QNAME))
+ .withChild(ImmutableNodes.leafNode(IN_CASE1_LEAF_QNAME, "withinCase1"))
+ .build())
+ .build();
+ final YangInstanceIdentifier WITH_CHOICE_CONTAINER_ID = YangInstanceIdentifier.of(WITH_CHOICE_CONTAINER_QNAME);
+ dataTreeModification.write(WITH_CHOICE_CONTAINER_ID, containerWithChoice);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+
+ assertThat(updates.size(), is(1));
+ assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class),
+ WITH_CHOICE_CONTAINER_ID, null, containerWithChoice);
+ }
+
+ private DataTreeModification getModification(final TipProducingDataTree dataTree) {
+ final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ return dataTreeSnapshot.newModification();
+ }
+
+ @Test
+ public void testWriteNonPresenceEmptyContainer() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ final NormalizedNode<?, ?> topContainer = ImmutableNodes.containerNode(TOP_CONTAINER_QNAME);
+ dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepare);
+
+ assertThat(modificationDiff.getUpdates().size(), is(0));
+ }
+
+ private DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree,
+ final DataTreeModification dataTreeModification)
+ throws DataValidationFailedException {
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ return dataTree.prepare(dataTreeModification);
+ }
+
+ @Test
+ public void testUpdateWrite() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final ContainerNode topContainer = getTopContainer("string1");
+ addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
+
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ final NormalizedNode<?, ?> topContainerAfter = getTopContainer("string2");
+ dataTreeModification.write(TOP_CONTAINER_ID, topContainerAfter);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+
+ assertThat(updates.size(), is(1));
+ assertThat(updates.values().size(), is(1));
+ assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
+ }
+
+ private ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) {
+ return ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+ }
+
+ @Test
+ public void testUpdateMerge() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final ContainerNode topContainer = getTopContainer("string1");
+ addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
+
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ final NormalizedNode<?, ?> topContainerAfter = getTopContainer("string2");
+ dataTreeModification.merge(TOP_CONTAINER_ID, topContainerAfter);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ assertThat(updates.size(), is(1));
+ assertThat(updates.values().size(), is(1));
+ assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
+ }
+
+ @Test
+ public void testUpdateDelete() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final ContainerNode topContainer = getTopContainer("string1");
+ addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
+
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ dataTreeModification.delete(TOP_CONTAINER_ID);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ assertThat(updates.size(), is(1));
+ assertThat(updates.values().size(), is(1));
+ assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, null);
+ }
+
+ @Test
+ public void testWriteAndUpdateInnerList() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ final MapNode mapNode = getNestedList("name1", "text");
+ final YangInstanceIdentifier listEntryId = listId.node(mapNode.getValue().iterator().next().getIdentifier());
+ dataTreeModification.write(listId, mapNode);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+
+ assertThat(updates.size(), is(1));
+ assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class),
+ listEntryId, null, mapNode.getValue().iterator().next());
+
+ // Commit so that update can be tested next
+ dataTree.commit(prepare);
+
+ YangInstanceIdentifier listItemId = listId.node(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, "name1"));
+ MapEntryNode mapEntryNode =
+ getNestedList("name1", "text-update").getValue().iterator().next();
+
+ dataTreeSnapshot = dataTree.takeSnapshot();
+ dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.write(listItemId, mapEntryNode);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ prepare = dataTree.prepare(dataTreeModification);
+
+ updates = getModificationDiff(prepare).getUpdates();
+ assertThat(updates.size(), is(1 /*Actual list entry*/));
+ }
+//
+ private void assertUpdate(final ModificationDiff.NormalizedNodeUpdate update,
+ final YangInstanceIdentifier idExpected,
+ final NormalizedNode<?, ?> beforeExpected,
+ final NormalizedNode<?, ?> afterExpected) {
+ assertThat(update.getId(), is(idExpected));
+ assertThat(update.getDataBefore(), is(beforeExpected));
+ assertThat(update.getDataAfter(), is(afterExpected));
+ }
+
+ @Test
+ public void testWriteTopContainerAndInnerList() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+
+ final ContainerNode topContainer = getTopContainer("string1");
+ dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
+
+ final YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ final MapNode mapNode = getNestedList("name1", "text");
+ final YangInstanceIdentifier listEntryId = listId.node(mapNode.getValue().iterator().next().getIdentifier());
+
+ dataTreeModification.write(listId, mapNode);
+
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+
+ assertThat(updates.size(), is(2));
+ assertThat(updates.values().size(), is(2));
+ assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class), TOP_CONTAINER_ID, null,
+ Builders.containerBuilder(topContainer).withChild(mapNode).build());
+ assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), listEntryId, null, mapNode.getValue().iterator().next());
+ // Assert that keys of the updates map are not wildcarded YID
+ assertThat(updates.keySet(), hasItems(
+ TOP_CONTAINER_ID,
+ listEntryId));
+ }
+
+ private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForAfterType(
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates,
+ final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
+ return updates.values().stream()
+ .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass()))
+ .findFirst().get();
+ }
+
+ private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType(
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates,
+ final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
+ return updates.values().stream()
+ .filter(update -> containerNodeClass.isAssignableFrom(update.getDataBefore().getClass()))
+ .findFirst().get();
+ }
+
+ @Test
+ public void testWriteDeepList() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+
+ YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ MapNode mapNode = getNestedList("name1", "text");
+ dataTreeModification.write(listId, mapNode);
+
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+
+ dataTreeSnapshot = dataTree.takeSnapshot();
+ dataTreeModification = dataTreeSnapshot.newModification();
+
+ final YangInstanceIdentifier.NodeIdentifierWithPredicates nestedListNodeId =
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, "name1");
+ listId = YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME),
+ nestedListNodeId);
+ final YangInstanceIdentifier deepListId =
+ listId.node(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME));
+ final YangInstanceIdentifier deepListEntryId = deepListId.node(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME, NAME_LEAF_QNAME, "name1"));
+
+ final MapEntryNode deepListEntry = getDeepList("name1").getValue().iterator().next();
+ // Merge parent list, just to see no modifications on it
+ dataTreeModification.merge(
+ listId,
+ Builders.mapEntryBuilder().withNodeIdentifier(nestedListNodeId)
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, "name1")).build());
+ dataTreeModification.merge(
+ deepListId,
+ Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
+ .build());
+ dataTreeModification.merge(
+ deepListEntryId,
+ deepListEntry);
+
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ assertThat(updates.size(), is(1));
+ assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), deepListEntryId, null, deepListEntry);
+ }
+
+ @Test
+ public void testDeleteInnerListItem() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ final MapNode mapNode = getNestedList("name1", "text");
+ dataTreeModification.write(listId, mapNode);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ // Commit so that update can be tested next
+ dataTree.commit(prepare);
+
+ YangInstanceIdentifier listItemId = listId.node(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, "name1"));
+
+ dataTreeSnapshot = dataTree.takeSnapshot();
+ dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.delete(listItemId);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ prepare = dataTree.prepare(dataTreeModification);
+
+ final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ assertThat(updates.size(), is(1));
+ assertUpdate(getNormalizedNodeUpdateForBeforeType(updates, MapEntryNode.class), listItemId, mapNode.getValue().iterator().next(), null);
+ }
+
+ static void addNodeToTree(final DataTree dataTree, final NormalizedNode<?, ?> node,
+ final YangInstanceIdentifier id)
+ throws DataValidationFailedException {
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.write(id, node);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidate prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+ }
+
+ static TipProducingDataTree getDataTree() throws ReactorException {
+ final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
+ dataTree.setSchemaContext(getSchemaCtx());
+ return dataTree;
+ }
+
+ static ContainerNode getTopContainer(final String stringValue) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
+ .build();
+ }
+
+ static MapNode getNestedList(final String listItemName, final String text) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
+ .withChild(
+ Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
+ NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
+ .build()
+ )
+ .build();
+ }
+
+ private MapNode getDeepList(final String listItemName) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
+ .withChild(
+ Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME,
+ NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .build()
+ )
+ .build();
+ }
+
+ private static SchemaContext getSchemaCtx() throws ReactorException {
+ final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+ buildAction.addSource(new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang")));
+ return buildAction.buildEffective();
+ }
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapterTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapterTest.java
new file mode 100644
index 000000000..523d9dd70
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapterTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+
+public class PersistingDataTreeAdapterTest {
+
+ @Mock
+ private DataTree delegatingDataTree;
+ @Mock
+ private SchemaService schemaService;
+ @Mock
+ private DataTreeSnapshot snapshot;
+
+ private Path tmpPersistFile;
+
+ private PersistingDataTreeAdapter persistingDataTreeAdapter;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ tmpPersistFile = Files.createTempFile("testing-hc-persistence", "json");
+ persistingDataTreeAdapter = new PersistingDataTreeAdapter(delegatingDataTree, schemaService, tmpPersistFile);
+ }
+
+ @Test
+ public void testNoPersistOnFailure() throws Exception {
+ doThrow(new IllegalStateException("testing errors")).when(delegatingDataTree).commit(any(DataTreeCandidate.class));
+
+ try {
+ persistingDataTreeAdapter.commit(null);
+ fail("Exception expected");
+ } catch (IllegalStateException e) {
+ verify(delegatingDataTree, times(0)).takeSnapshot();
+ verify(delegatingDataTree).commit(any(DataTreeCandidate.class));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransactionTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransactionTest.java
new file mode 100644
index 000000000..a13621725
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransactionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.data.ReadableDataManager;
+import io.fd.honeycomb.v3po.data.DataModification;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class ReadOnlyTransactionTest {
+
+ @Mock
+ private ReadableDataManager operationalData;
+ @Mock
+ private DataModification configSnapshot;
+
+ private ReadOnlyTransaction readOnlyTx;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ readOnlyTx = ReadOnlyTransaction.create(configSnapshot, operationalData);
+ }
+
+ @Test
+ public void testExists() {
+ final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class);
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
+ future = mock(CheckedFuture.class);
+ when(operationalData.read(path)).thenReturn(future);
+
+ readOnlyTx.exists(LogicalDatastoreType.OPERATIONAL, path);
+
+ verify(operationalData).read(path);
+ }
+
+ @Test
+ public void testGetIdentifier() {
+ assertNotNull(readOnlyTx.getIdentifier());
+ }
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransactionTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransactionTest.java
new file mode 100644
index 000000000..1b67cd967
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransactionTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class ReadWriteTransactionTest {
+
+ @Mock
+ private DOMDataReadOnlyTransaction readTx;
+
+ @Mock
+ private DOMDataWriteTransaction writeTx;
+
+ private LogicalDatastoreType store;
+
+ @Mock
+ private YangInstanceIdentifier path;
+
+ @Mock
+ private NormalizedNode<?, ?> data;
+
+ private ReadWriteTransaction readWriteTx;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ store = LogicalDatastoreType.CONFIGURATION;
+ readWriteTx = new ReadWriteTransaction(readTx, writeTx);
+ }
+
+ @Test
+ public void testCancel() {
+ readWriteTx.cancel();
+ verify(writeTx).cancel();
+ }
+
+ @Test
+ public void testPut() {
+ readWriteTx.put(store, path, data);
+ verify(writeTx).put(store, path, data);
+ }
+
+ @Test
+ public void testMerge() {
+ readWriteTx.merge(store, path, data);
+ verify(writeTx).merge(store, path, data);
+ }
+
+ @Test
+ public void testDelete() {
+ readWriteTx.delete(store, path);
+ verify(writeTx).delete(store, path);
+ }
+
+ @Test
+ public void testSubmit() throws Exception {
+ readWriteTx.submit();
+ verify(writeTx).submit();
+ }
+
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testCommit() throws Exception {
+ readWriteTx.commit();
+ verify(writeTx).commit();
+ }
+
+ @Test
+ public void testRead() {
+ readWriteTx.read(store, path);
+ verify(readTx).read(store, path);
+ }
+
+ @Test
+ public void testExists() {
+ readWriteTx.exists(store, path);
+ verify(readTx).exists(store, path);
+ }
+
+ @Test
+ public void testGetIdentifier() throws Exception {
+ assertNotNull(readWriteTx.getIdentifier());
+ }
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegatorTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegatorTest.java
new file mode 100644
index 000000000..455050ab0
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegatorTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.translate.read.ReadContext;
+import io.fd.honeycomb.v3po.translate.read.registry.ReaderRegistry;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class ReadableDataTreeDelegatorTest {
+
+ @Mock
+ private BindingNormalizedNodeSerializer serializer;
+ @Mock
+ private ReaderRegistry reader;
+
+ private ReadableDataTreeDelegator operationalData;
+
+ @Mock
+ private InstanceIdentifier<DataObject> id;
+ @Mock
+ private Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry;
+ @Mock
+ private SchemaContext globalContext;
+ @Mock
+ private DataSchemaNode schemaNode;
+ @Mock
+ private ReadContext readCtx;
+ @Mock
+ private DOMDataBroker netconfMonitoringBroker;
+ @Mock
+ private DOMDataReadOnlyTransaction domDataReadOnlyTransaction;
+ @Mock
+ private DataBroker contextBroker;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ operationalData = new ReadableDataTreeDelegator(serializer, globalContext, reader, contextBroker);
+ doReturn(schemaNode).when(globalContext).getDataChildByName(any(QName.class));
+
+ doReturn(domDataReadOnlyTransaction).when(netconfMonitoringBroker).newReadOnlyTransaction();
+ doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(domDataReadOnlyTransaction)
+ .read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class));
+
+ final org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction ctxTransaction = mock(
+ org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction.class);
+ doReturn(ctxTransaction).when(contextBroker).newReadWriteTransaction();
+ doReturn(Futures.immediateCheckedFuture(null)).when(ctxTransaction).submit();
+ }
+
+ @Test
+ public void testReadNode() throws Exception {
+ final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class);
+ final YangInstanceIdentifier.PathArgument pArg = mock(YangInstanceIdentifier.PathArgument.class);
+ doReturn(pArg).when(yangId).getLastPathArgument();
+ doReturn(Collections.singletonList(pArg)).when(yangId).getPathArguments();
+
+ doReturn(QName.create("namespace", "2012-12-12", "local")).when(pArg).getNodeType();
+ doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId);
+
+ final DataObject dataObject = mock(DataObject.class);
+ doReturn(Optional.of(dataObject)).when(reader).read(same(id), any(ReadContext.class));
+
+ when(serializer.toNormalizedNode(id, dataObject)).thenReturn(entry);
+ final DataContainerChild<?, ?> expectedValue = mock(DataContainerChild.class);
+ doReturn(expectedValue).when(entry).getValue();
+
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future = operationalData.read(yangId);
+
+ verify(serializer).fromYangInstanceIdentifier(yangId);
+ verify(reader).read(same(id), any(ReadContext.class));
+ final Optional<NormalizedNode<?, ?>> result = future.get();
+ assertTrue(result.isPresent());
+ assertEquals(expectedValue, result.get());
+ }
+
+ @Test
+ public void testReadNonExistingNode() throws Exception {
+ final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class);
+ doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId);
+ doReturn(Optional.absent()).when(reader).read(same(id), any(ReadContext.class));
+
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future = operationalData.read(yangId);
+
+ verify(serializer).fromYangInstanceIdentifier(yangId);
+ verify(reader).read(same(id), any(ReadContext.class));
+ final Optional<NormalizedNode<?, ?>> result = future.get();
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ public void testReadFailed() throws Exception {
+ doThrow(io.fd.honeycomb.v3po.translate.read.ReadFailedException.class).when(reader).readAll(any(ReadContext.class));
+
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
+ operationalData.read( YangInstanceIdentifier.EMPTY);
+
+ try {
+ future.checkedGet();
+ } catch (ReadFailedException e) {
+ assertTrue(e.getCause() instanceof io.fd.honeycomb.v3po.translate.read.ReadFailedException);
+ return;
+ }
+ fail("ReadFailedException was expected");
+ }
+
+ @Test
+ public void testReadRootWithOneNonListElement() throws Exception {
+ // Prepare data
+ final InstanceIdentifier<DataObject> vppStateII = InstanceIdentifier.create(DataObject.class);
+ final DataObject vppState = mock(DataObject.class);
+ Multimap<InstanceIdentifier<?>, DataObject> dataObjects = LinkedListMultimap.create();
+ dataObjects.put(vppStateII, vppState);
+ doReturn(dataObjects).when(reader).readAll(any(ReadContext.class));
+
+ // Init serializer
+ final YangInstanceIdentifier vppYangId = YangInstanceIdentifier.builder().node(QName.create("n", "d")).build();
+ when(serializer.toYangInstanceIdentifier(vppStateII)).thenReturn(vppYangId);
+ when(serializer.toNormalizedNode(vppStateII, vppState)).thenReturn(entry);
+ final DataContainerChild<?, ?> vppStateContainer = mock(DataContainerChild.class);
+ doReturn(vppStateContainer).when(entry).getValue();
+ doReturn(vppYangId.getLastPathArgument()).when(vppStateContainer).getIdentifier();
+
+ // Read root
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
+ operationalData.read(YangInstanceIdentifier.EMPTY);
+
+ verify(reader).readAll(any(ReadContext.class));
+ verify(serializer).toYangInstanceIdentifier(vppStateII);
+ verify(serializer).toNormalizedNode(vppStateII, vppState);
+
+ // Check the result is an ContainerNode with only one child
+ final Optional<NormalizedNode<?, ?>> result = future.get();
+ assertTrue(result.isPresent());
+
+ final ContainerNode rootNode = (ContainerNode) result.get();
+ assertEquals(SchemaContext.NAME, rootNode.getIdentifier().getNodeType());
+ assertEquals(vppStateContainer, Iterables.getOnlyElement(rootNode.getValue()));
+ }
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/WriteTransactionTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/WriteTransactionTest.java
new file mode 100644
index 000000000..9cde27d2b
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/WriteTransactionTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.data.impl;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.data.DataModification;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+
+public class WriteTransactionTest {
+
+ @Mock
+ private DataModification configSnapshot;
+ @Mock
+ private YangInstanceIdentifier path;
+ @Mock
+ private NormalizedNode<?,?> data;
+
+ private WriteTransaction writeTx;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ writeTx = WriteTransaction.createConfigOnly(configSnapshot);
+ }
+
+ @Test
+ public void testPut() {
+ writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
+ verify(configSnapshot).write(path, data);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPutOperational() {
+ writeTx.put(LogicalDatastoreType.OPERATIONAL, path, data);
+ verify(configSnapshot).write(path, data);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOnFinishedTx() {
+ writeTx.submit();
+ writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
+ verify(configSnapshot).write(path, data);
+ }
+
+ @Test
+ public void testMerge() {
+ writeTx.merge(LogicalDatastoreType.CONFIGURATION, path, data);
+ verify(configSnapshot).merge(path, data);
+ }
+
+ @Test
+ public void testCancel() {
+ assertTrue(writeTx.cancel());
+ }
+
+ @Test
+ public void testCancelFinished() {
+ writeTx.submit();
+ assertFalse(writeTx.cancel());
+ }
+
+ @Test
+ public void testDelete() {
+ writeTx.delete(LogicalDatastoreType.CONFIGURATION, path);
+ verify(configSnapshot).delete(path);
+ }
+
+ @Test
+ public void testSubmit() throws Exception {
+ writeTx.submit();
+ verify(configSnapshot).validate();
+ verify(configSnapshot).commit();
+ }
+
+ @Test
+ public void testSubmitFailed() throws Exception {
+ doThrow(mock(DataValidationFailedException.class)).when(configSnapshot).commit();
+ final CheckedFuture<Void, TransactionCommitFailedException> future = writeTx.submit();
+ try {
+ future.get();
+ } catch (Exception e) {
+ assertTrue(e.getCause() instanceof TransactionCommitFailedException);
+ return;
+ }
+ fail("Expected exception to be thrown");
+
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testCommit() {
+ writeTx.commit();
+ }
+
+ @Test
+ public void testGetIdentifier() {
+ assertNotNull(writeTx.getIdentifier());
+ }
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/resources/test-diff.yang b/infra/data-impl/src/test/resources/test-diff.yang
new file mode 100644
index 000000000..5cccc8718
--- /dev/null
+++ b/infra/data-impl/src/test/resources/test-diff.yang
@@ -0,0 +1,54 @@
+module test-diff {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:test:diff";
+ prefix "td";
+
+ revision "2015-01-05" {
+ description "Initial revision";
+ }
+
+ container top-container {
+ leaf string {
+ type string;
+ }
+
+ list nested-list {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+
+ leaf text {
+ type string;
+ }
+
+ list deep-list {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+
+ }
+ }
+ }
+
+ container with-choice {
+
+ choice choice {
+ case case1 {
+ leaf in-case1 {
+ type string;
+ }
+ }
+
+ case case2 {
+ leaf in-case2 {
+ type string;
+ }
+ }
+ }
+ }
+
+}