From e3c31cee916480b2d9d169c1f5afb1c42efaabe1 Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Fri, 29 Jul 2016 16:27:12 +0200 Subject: HONEYCOMB-130: Rename infra packages(remove vpp/v3po) Change-Id: Ic5b90e397e3743623d01b206bc60bc5c7df6b981 Signed-off-by: Maros Marsalek --- .../java/io/fd/honeycomb/data/impl/DataBroker.java | 198 ++++++++++ .../data/impl/ModifiableDataTreeDelegator.java | 236 ++++++++++++ .../data/impl/ModifiableDataTreeManager.java | 122 ++++++ .../fd/honeycomb/data/impl/ModificationDiff.java | 278 ++++++++++++++ .../data/impl/PersistingDataTreeAdapter.java | 151 ++++++++ .../honeycomb/data/impl/ReadOnlyTransaction.java | 119 ++++++ .../honeycomb/data/impl/ReadWriteTransaction.java | 101 +++++ .../data/impl/ReadableDataTreeDelegator.java | 241 ++++++++++++ .../fd/honeycomb/data/impl/WriteTransaction.java | 177 +++++++++ .../io/fd/honeycomb/v3po/data/impl/DataBroker.java | 198 ---------- .../data/impl/ModifiableDataTreeDelegator.java | 236 ------------ .../v3po/data/impl/ModifiableDataTreeManager.java | 122 ------ .../honeycomb/v3po/data/impl/ModificationDiff.java | 278 -------------- .../v3po/data/impl/PersistingDataTreeAdapter.java | 151 -------- .../v3po/data/impl/ReadOnlyTransaction.java | 119 ------ .../v3po/data/impl/ReadWriteTransaction.java | 101 ----- .../v3po/data/impl/ReadableDataTreeDelegator.java | 242 ------------ .../honeycomb/v3po/data/impl/WriteTransaction.java | 177 --------- .../data/impl/rev160411/ConfigDataTreeModule.java | 6 +- .../impl/rev160411/OperationalDataTreeModule.java | 4 +- .../rev160411/PersistingDataTreeAdapterModule.java | 2 +- .../io/fd/honeycomb/data/impl/DataBrokerTest.java | 111 ++++++ .../data/impl/ModifiableDataTreeDelegatorTest.java | 269 +++++++++++++ .../honeycomb/data/impl/ModificationDiffTest.java | 427 +++++++++++++++++++++ .../data/impl/PersistingDataTreeAdapterTest.java | 69 ++++ .../data/impl/ReadOnlyTransactionTest.java | 68 ++++ .../data/impl/ReadWriteTransactionTest.java | 111 ++++++ .../data/impl/ReadableDataTreeDelegatorTest.java | 192 +++++++++ .../honeycomb/data/impl/WriteTransactionTest.java | 128 ++++++ .../honeycomb/v3po/data/impl/DataBrokerTest.java | 111 ------ .../data/impl/ModifiableDataTreeDelegatorTest.java | 269 ------------- .../v3po/data/impl/ModificationDiffTest.java | 427 --------------------- .../data/impl/PersistingDataTreeAdapterTest.java | 69 ---- .../v3po/data/impl/ReadOnlyTransactionTest.java | 68 ---- .../v3po/data/impl/ReadWriteTransactionTest.java | 111 ------ .../data/impl/ReadableDataTreeDelegatorTest.java | 192 --------- .../v3po/data/impl/WriteTransactionTest.java | 128 ------ 37 files changed, 3004 insertions(+), 3005 deletions(-) create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DataBroker.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeManager.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/PersistingDataTreeAdapter.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadOnlyTransaction.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadWriteTransaction.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadableDataTreeDelegator.java create mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/WriteTransaction.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeManager.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapter.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransaction.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransaction.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegator.java delete mode 100644 infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/WriteTransaction.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/DataBrokerTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/PersistingDataTreeAdapterTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ReadOnlyTransactionTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ReadWriteTransactionTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ReadableDataTreeDelegatorTest.java create mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/WriteTransactionTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapterTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransactionTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransactionTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegatorTest.java delete mode 100644 infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/WriteTransactionTest.java (limited to 'infra/data-impl/src') diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DataBroker.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DataBroker.java new file mode 100644 index 000000000..eb19c9dd1 --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.data.impl; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.fd.honeycomb.data.DataModification; +import io.fd.honeycomb.data.ModifiableDataManager; +import io.fd.honeycomb.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 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, 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/data/impl/ModifiableDataTreeDelegator.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java new file mode 100644 index 000000000..e9a616eea --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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.data.DataModification; +import io.fd.honeycomb.data.ReadableDataManager; +import io.fd.honeycomb.translate.TranslationException; +import io.fd.honeycomb.translate.util.RWUtils; +import io.fd.honeycomb.translate.util.TransactionMappingContext; +import io.fd.honeycomb.translate.util.write.TransactionWriteContext; +import io.fd.honeycomb.translate.write.DataObjectUpdate; +import io.fd.honeycomb.translate.write.WriteContext; +import io.fd.honeycomb.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 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 biNodes) { + return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer); + } + } + + @VisibleForTesting + static WriterRegistry.DataObjectUpdates toBindingAware( + final Map biNodes, + final BindingNormalizedNodeSerializer serializer) { + + final Multimap, DataObjectUpdate> dataObjectUpdates = HashMultimap.create(); + final Multimap, DataObjectUpdate.DataObjectDelete> dataObjectDeletes = + HashMultimap.create(); + + for (Map.Entry 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, 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/data/impl/ModifiableDataTreeManager.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeManager.java new file mode 100644 index 000000000..61ccf185c --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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.data.ModifiableDataManager; +import io.fd.honeycomb.data.DataModification; +import io.fd.honeycomb.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>, 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>, ReadFailedException> read( + @Nonnull final YangInstanceIdentifier path) { + final Optional> 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/data/impl/ModificationDiff.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java new file mode 100644 index 000000000..710f8a053 --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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 updates; + + private ModificationDiff(@Nonnull Map updates) { + this.updates = updates; + } + + /** + * Get processed modifications. + * + * @return mapped modifications, where key is keyed {@link YangInstanceIdentifier}. + */ + Map 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 join(Map first, + Map second) { + final Map 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/data/impl/PersistingDataTreeAdapter.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/PersistingDataTreeAdapter.java new file mode 100644 index 000000000..94c5e4446 --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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.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> 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/data/impl/ReadOnlyTransaction.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadOnlyTransaction.java new file mode 100644 index 000000000..00d105e26 --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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.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>, 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 exists(final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + LOG.debug("ReadOnlyTransaction.exists() store={}, path={}", store, path); + + ListenableFuture 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>, ? extends Boolean> IS_NODE_PRESENT = + (Function>, Boolean>) input -> input == null ? Boolean.FALSE : input.isPresent(); + + private static final Function ANY_EX_TO_READ_FAILED_EXCEPTION_MAPPER = + (Function) e -> new ReadFailedException("Exists failed", e); +} \ No newline at end of file diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadWriteTransaction.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadWriteTransaction.java new file mode 100644 index 000000000..30035d99e --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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 submit() { + return delegateWriteTx.submit(); + } + + @Override + public ListenableFuture> commit() { + return delegateWriteTx.commit(); + } + + @Override + public CheckedFuture>, ReadFailedException> read(final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + return delegateReadTx.read(store, path); + } + + @Override + public CheckedFuture 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/data/impl/ReadableDataTreeDelegator.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadableDataTreeDelegator.java new file mode 100644 index 000000000..775f6326c --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ReadableDataTreeDelegator.java @@ -0,0 +1,241 @@ +/* + * 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.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.data.ReadableDataManager; +import io.fd.honeycomb.translate.MappingContext; +import io.fd.honeycomb.translate.ModificationCache; +import io.fd.honeycomb.translate.read.ReadContext; +import io.fd.honeycomb.translate.read.ReadFailedException; +import io.fd.honeycomb.translate.read.registry.ReaderRegistry; +import io.fd.honeycomb.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>, + 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> value; + if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) { + value = readRoot(ctx); + } else { + value = readNode(yangInstanceIdentifier, ctx); + } + + // Submit context mapping updates + final CheckedFuture 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 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> 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 dataObject; + + dataObject = readerRegistry.read(path, ctx); + if (dataObject.isPresent()) { + final NormalizedNode value = toNormalizedNodeFunction(path).apply(dataObject.get()); + return Optional.>fromNullable(value); + } else { + return Optional.absent(); + } + } + + private Optional> readRoot(final ReadContext ctx) throws ReadFailedException { + LOG.debug("OperationalDataTree.readRoot()"); + + final DataContainerNodeAttrBuilder dataNodeBuilder = + Builders.containerBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME)); + + final Multimap, ? extends DataObject> dataObjects = + readerRegistry.readAll(ctx); + + for (final InstanceIdentifier instanceIdentifier : dataObjects.keySet()) { + final YangInstanceIdentifier rootElementId = serializer.toYangInstanceIdentifier(instanceIdentifier); + final NormalizedNode node = + wrapDataObjects(rootElementId, instanceIdentifier, dataObjects.get(instanceIdentifier)); + dataNodeBuilder.withChild((DataContainerChild) node); + } + return Optional.>of(dataNodeBuilder.build()); + } + + private NormalizedNode wrapDataObjects(final YangInstanceIdentifier yangInstanceIdentifier, + final InstanceIdentifier instanceIdentifier, + final Collection dataObjects) { + final Collection> 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> normalizedRootElements, final ListSchemaNode listSchema) { + if (listSchema.getKeyDefinition().isEmpty()) { + final CollectionNodeBuilder listBuilder = + Builders.unkeyedListBuilder(); + for (NormalizedNode normalizedRootElement : normalizedRootElements) { + listBuilder.withChild((UnkeyedListEntryNode) normalizedRootElement); + } + return listBuilder.build(); + } else { + final CollectionNodeBuilder listBuilder = + listSchema.isUserOrdered() + ? Builders.orderedMapBuilder() + : Builders.mapBuilder(); + + for (NormalizedNode normalizedRootElement : normalizedRootElements) { + listBuilder.withChild((MapEntryNode) normalizedRootElement); + } + return listBuilder.build(); + } + } + + @SuppressWarnings("unchecked") + private Function> toNormalizedNodeFunction(final InstanceIdentifier path) { + return dataObject -> { + LOG.trace("OperationalDataTree.toNormalizedNode(), path={}, dataObject={}", path, dataObject); + final Map.Entry> 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/data/impl/WriteTransaction.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/WriteTransaction.java new file mode 100644 index 000000000..6bc6b1b4b --- /dev/null +++ b/infra/data-impl/src/main/java/io/fd/honeycomb/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.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.data.DataModification; +import io.fd.honeycomb.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 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 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> 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/io/fd/honeycomb/v3po/data/impl/DataBroker.java b/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java deleted file mode 100644 index c418ed332..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataBroker.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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 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, 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 deleted file mode 100644 index 2c2581ec0..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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 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 biNodes) { - return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer); - } - } - - @VisibleForTesting - static WriterRegistry.DataObjectUpdates toBindingAware( - final Map biNodes, - final BindingNormalizedNodeSerializer serializer) { - - final Multimap, DataObjectUpdate> dataObjectUpdates = HashMultimap.create(); - final Multimap, DataObjectUpdate.DataObjectDelete> dataObjectDeletes = - HashMultimap.create(); - - for (Map.Entry 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, 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 deleted file mode 100644 index 1082c479b..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeManager.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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>, 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>, ReadFailedException> read( - @Nonnull final YangInstanceIdentifier path) { - final Optional> 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 deleted file mode 100644 index abc0062de..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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 updates; - - private ModificationDiff(@Nonnull Map updates) { - this.updates = updates; - } - - /** - * Get processed modifications. - * - * @return mapped modifications, where key is keyed {@link YangInstanceIdentifier}. - */ - Map 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 join(Map first, - Map second) { - final Map 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 deleted file mode 100644 index 9b71dfd62..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapter.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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> 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 deleted file mode 100644 index 2850a0d9a..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransaction.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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>, 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 exists(final LogicalDatastoreType store, - final YangInstanceIdentifier path) { - LOG.debug("ReadOnlyTransaction.exists() store={}, path={}", store, path); - - ListenableFuture 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>, ? extends Boolean> IS_NODE_PRESENT = - (Function>, Boolean>) input -> input == null ? Boolean.FALSE : input.isPresent(); - - private static final Function ANY_EX_TO_READ_FAILED_EXCEPTION_MAPPER = - (Function) 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 deleted file mode 100644 index 88b46437e..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransaction.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 submit() { - return delegateWriteTx.submit(); - } - - @Override - public ListenableFuture> commit() { - return delegateWriteTx.commit(); - } - - @Override - public CheckedFuture>, ReadFailedException> read(final LogicalDatastoreType store, - final YangInstanceIdentifier path) { - return delegateReadTx.read(store, path); - } - - @Override - public CheckedFuture 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 deleted file mode 100644 index aff023ebc..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegator.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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>, - 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> value; - if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) { - value = readRoot(ctx); - } else { - value = readNode(yangInstanceIdentifier, ctx); - } - - // Submit context mapping updates - final CheckedFuture 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> 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 dataObject; - - dataObject = readerRegistry.read(path, ctx); - if (dataObject.isPresent()) { - final NormalizedNode value = toNormalizedNodeFunction(path).apply(dataObject.get()); - return Optional.>fromNullable(value); - } else { - return Optional.absent(); - } - } - - private Optional> readRoot(final ReadContext ctx) throws ReadFailedException { - LOG.debug("OperationalDataTree.readRoot()"); - - final DataContainerNodeAttrBuilder dataNodeBuilder = - Builders.containerBuilder() - .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME)); - - final Multimap, ? extends DataObject> dataObjects = - readerRegistry.readAll(ctx); - - for (final InstanceIdentifier instanceIdentifier : dataObjects.keySet()) { - final YangInstanceIdentifier rootElementId = serializer.toYangInstanceIdentifier(instanceIdentifier); - final NormalizedNode node = - wrapDataObjects(rootElementId, instanceIdentifier, dataObjects.get(instanceIdentifier)); - dataNodeBuilder.withChild((DataContainerChild) node); - } - return Optional.>of(dataNodeBuilder.build()); - } - - private NormalizedNode wrapDataObjects(final YangInstanceIdentifier yangInstanceIdentifier, - final InstanceIdentifier instanceIdentifier, - final Collection dataObjects) { - final Collection> 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> normalizedRootElements, final ListSchemaNode listSchema) { - if (listSchema.getKeyDefinition().isEmpty()) { - final CollectionNodeBuilder listBuilder = - Builders.unkeyedListBuilder(); - for (NormalizedNode normalizedRootElement : normalizedRootElements) { - listBuilder.withChild((UnkeyedListEntryNode) normalizedRootElement); - } - return listBuilder.build(); - } else { - final CollectionNodeBuilder listBuilder = - listSchema.isUserOrdered() - ? Builders.orderedMapBuilder() - : Builders.mapBuilder(); - - for (NormalizedNode normalizedRootElement : normalizedRootElements) { - listBuilder.withChild((MapEntryNode) normalizedRootElement); - } - return listBuilder.build(); - } - } - - @SuppressWarnings("unchecked") - private Function> toNormalizedNodeFunction(final InstanceIdentifier path) { - return dataObject -> { - LOG.trace("OperationalDataTree.toNormalizedNode(), path={}, dataObject={}", path, dataObject); - final Map.Entry> 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 deleted file mode 100644 index c8f9bd3db..000000000 --- a/infra/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/WriteTransaction.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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 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 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> 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 index eabcdcbc8..8db0d5404 100644 --- 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 @@ -2,9 +2,9 @@ package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl. 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 io.fd.honeycomb.data.DataModification; +import io.fd.honeycomb.data.ModifiableDataManager; +import io.fd.honeycomb.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; 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 index 1526fddca..dcb0053ca 100644 --- 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 @@ -2,8 +2,8 @@ package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.data.impl. 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 io.fd.honeycomb.data.ReadableDataManager; +import io.fd.honeycomb.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; 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 index 145fc4345..c15feace9 100644 --- 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 @@ -29,7 +29,7 @@ public class PersistingDataTreeAdapterModule extends @Override public java.lang.AutoCloseable createInstance() { - return new io.fd.honeycomb.v3po.data.impl.PersistingDataTreeAdapter( + return new io.fd.honeycomb.data.impl.PersistingDataTreeAdapter( getDelegateDependency(), getSchemaServiceDependency(), Paths.get(getPersistFilePath())); diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/DataBrokerTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/DataBrokerTest.java new file mode 100644 index 000000000..08cb3320f --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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.data.ReadableDataManager; +import io.fd.honeycomb.data.ModifiableDataManager; +import io.fd.honeycomb.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, 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/data/impl/ModifiableDataTreeDelegatorTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java new file mode 100644 index 000000000..dc962662d --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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.data.DataModification; +import io.fd.honeycomb.translate.TranslationException; +import io.fd.honeycomb.translate.write.DataObjectUpdate; +import io.fd.honeycomb.translate.write.WriteContext; +import io.fd.honeycomb.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, 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>, ReadFailedException> read = + configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID); + final CheckedFuture>, ReadFailedException> read2 = + configDataTree.newModification().read(ModificationDiffTest.TOP_CONTAINER_ID); + final Optional> normalizedNodeOptional = read.get(); + final Optional> 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, 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 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 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 mockDataObject(final YangInstanceIdentifier yid1, + final InstanceIdentifier iid1, + final NormalizedNode nn1B, + final Class 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 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/data/impl/ModificationDiffTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java new file mode 100644 index 000000000..2fa82043b --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java @@ -0,0 +1,427 @@ +package io.fd.honeycomb.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 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 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 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 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 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 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 updates, + final Class> containerNodeClass) { + return updates.values().stream() + .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass())) + .findFirst().get(); + } + + private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType( + final Map updates, + final Class> 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 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 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/data/impl/PersistingDataTreeAdapterTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/PersistingDataTreeAdapterTest.java new file mode 100644 index 000000000..305166421 --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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/data/impl/ReadOnlyTransactionTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ReadOnlyTransactionTest.java new file mode 100644 index 000000000..77470d17c --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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.data.ReadableDataManager; +import io.fd.honeycomb.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>, 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/data/impl/ReadWriteTransactionTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ReadWriteTransactionTest.java new file mode 100644 index 000000000..820d499be --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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/data/impl/ReadableDataTreeDelegatorTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ReadableDataTreeDelegatorTest.java new file mode 100644 index 000000000..2b4cc2a06 --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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.translate.read.ReadContext; +import io.fd.honeycomb.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 id; + @Mock + private Map.Entry> 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>, ReadFailedException> future = operationalData.read(yangId); + + verify(serializer).fromYangInstanceIdentifier(yangId); + verify(reader).read(same(id), any(ReadContext.class)); + final Optional> 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>, ReadFailedException> future = operationalData.read(yangId); + + verify(serializer).fromYangInstanceIdentifier(yangId); + verify(reader).read(same(id), any(ReadContext.class)); + final Optional> result = future.get(); + assertFalse(result.isPresent()); + } + + @Test + public void testReadFailed() throws Exception { + doThrow(io.fd.honeycomb.translate.read.ReadFailedException.class).when(reader).readAll(any(ReadContext.class)); + + final CheckedFuture>, ReadFailedException> future = + operationalData.read( YangInstanceIdentifier.EMPTY); + + try { + future.checkedGet(); + } catch (ReadFailedException e) { + assertTrue(e.getCause() instanceof io.fd.honeycomb.translate.read.ReadFailedException); + return; + } + fail("ReadFailedException was expected"); + } + + @Test + public void testReadRootWithOneNonListElement() throws Exception { + // Prepare data + final InstanceIdentifier vppStateII = InstanceIdentifier.create(DataObject.class); + final DataObject vppState = mock(DataObject.class); + Multimap, 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>, 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> 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/data/impl/WriteTransactionTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/WriteTransactionTest.java new file mode 100644 index 000000000..1dab5524d --- /dev/null +++ b/infra/data-impl/src/test/java/io/fd/honeycomb/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.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.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 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/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java deleted file mode 100644 index 55b92b50b..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataBrokerTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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, 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 deleted file mode 100644 index 915d738e9..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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, 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>, ReadFailedException> read = - configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID); - final CheckedFuture>, ReadFailedException> read2 = - configDataTree.newModification().read(ModificationDiffTest.TOP_CONTAINER_ID); - final Optional> normalizedNodeOptional = read.get(); - final Optional> 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, 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 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 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 mockDataObject(final YangInstanceIdentifier yid1, - final InstanceIdentifier iid1, - final NormalizedNode nn1B, - final Class 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 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 deleted file mode 100644 index bc7582e93..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java +++ /dev/null @@ -1,427 +0,0 @@ -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 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 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 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 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 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 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 updates, - final Class> containerNodeClass) { - return updates.values().stream() - .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass())) - .findFirst().get(); - } - - private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType( - final Map updates, - final Class> 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 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 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 deleted file mode 100644 index 523d9dd70..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/PersistingDataTreeAdapterTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 deleted file mode 100644 index a13621725..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadOnlyTransactionTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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>, 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 deleted file mode 100644 index 1b67cd967..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadWriteTransactionTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 deleted file mode 100644 index 455050ab0..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ReadableDataTreeDelegatorTest.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 id; - @Mock - private Map.Entry> 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>, ReadFailedException> future = operationalData.read(yangId); - - verify(serializer).fromYangInstanceIdentifier(yangId); - verify(reader).read(same(id), any(ReadContext.class)); - final Optional> 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>, ReadFailedException> future = operationalData.read(yangId); - - verify(serializer).fromYangInstanceIdentifier(yangId); - verify(reader).read(same(id), any(ReadContext.class)); - final Optional> 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>, 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 vppStateII = InstanceIdentifier.create(DataObject.class); - final DataObject vppState = mock(DataObject.class); - Multimap, 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>, 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> 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 deleted file mode 100644 index 9cde27d2b..000000000 --- a/infra/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/WriteTransactionTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 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 -- cgit 1.2.3-korg