From 2ebdfa93c5a5bfddf359160d839424f13c93ae2a Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Tue, 18 Mar 2014 17:10:14 +0100 Subject: [PATCH] Bug 499: Initial implementation of data tree modifications Initial implementation of data tree modifications using StoreMetadataNode and NodeModification. - DataAndMetadataSnapshot - helper class, which contains immutable snapshot of data and metadata - ModificationApplyOperation - interface definition of composite hierarchical operation, which is responsible for applying changes to provided subtree - SchemaAwareApplyOperation - implementations of ModificationApplyOperation based on parsed SchemaContext - MutableDataTree - class which integrates Data tree, Metadata tree, Node Modification tree and ModificationApplyOperations into mutable logical tree, which will serves as a basis for read-write transactions. - Unit test for MutableDataTree in following scenarios - Create empty MutableDataTree, write subtree, read nested nodes - Create empty MutableDataTree, write subtree, modify subtree - Create MutableDataTree with existing date, write changes and deletes Change-Id: I5c0c84764f93d150eac6d227c4bc367b67652f9d Signed-off-by: Tony Tkacik --- opendaylight/md-sal/sal-dom-broker/pom.xml | 10 + .../store/impl/DataAndMetadataSnapshot.java | 94 +++++ .../impl/ModificationApplyOperation.java | 69 ++++ .../sal/dom/store/impl/MutableDataTree.java | 112 ++++++ .../store/impl/SchemaAwareApplyOperation.java | 343 ++++++++++++++++++ .../impl/SchemaAwareApplyOperationRoot.java | 40 ++ .../impl/ModificationMetadataTreeTest.java | 255 +++++++++++++ .../md/sal/dom/store/impl/TestModel.java | 49 +++ .../test/resources/odl-datastore-test.yang | 42 +++ 9 files changed, 1014 insertions(+) create mode 100644 opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang diff --git a/opendaylight/md-sal/sal-dom-broker/pom.xml b/opendaylight/md-sal/sal-dom-broker/pom.xml index d192bea540..e3e5043e91 100644 --- a/opendaylight/md-sal/sal-dom-broker/pom.xml +++ b/opendaylight/md-sal/sal-dom-broker/pom.xml @@ -15,6 +15,10 @@ + + junit + junit + org.opendaylight.controller sal-core-api @@ -35,6 +39,12 @@ org.slf4j slf4j-api + + org.slf4j + slf4j-simple + ${slf4j.version} + test + com.google.guava guava diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java new file mode 100644 index 0000000000..52f0051897 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataAndMetadataSnapshot.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +import com.google.common.base.Optional; + +class DataAndMetadataSnapshot { + + private final StoreMetadataNode metadataTree; + private final Optional schemaContext; + + + + + private DataAndMetadataSnapshot(final StoreMetadataNode metadataTree, final Optional schemaCtx) { + this.metadataTree = metadataTree; + this.schemaContext = schemaCtx; + } + + public static Builder builder() { + return new Builder(); + } + + public static DataAndMetadataSnapshot createEmpty(final NodeIdentifier rootNode) { + NormalizedNode data = Builders.containerBuilder().withNodeIdentifier(rootNode).build(); + StoreMetadataNode metadata = StoreMetadataNode.builder() + .setData(data) + .build(); + return new DataAndMetadataSnapshot(metadata,Optional.absent()); + } + + public static DataAndMetadataSnapshot createEmpty(final SchemaContext ctx) { + NodeIdentifier rootNodeIdentifier = new NodeIdentifier(ctx.getQName()); + NormalizedNode data = Builders.containerBuilder().withNodeIdentifier(rootNodeIdentifier).build(); + StoreMetadataNode metadata = StoreMetadataNode.builder() + .setData(data) + .build(); + return new DataAndMetadataSnapshot(metadata, Optional.of(ctx)); + } + + public Optional getSchemaContext() { + return schemaContext; + } + + public NormalizedNode getDataTree() { + return metadataTree.getData(); + } + + public StoreMetadataNode getMetadataTree() { + return metadataTree; + } + + public Optional read(final InstanceIdentifier path) { + return TreeNodeUtils.findNode(metadataTree, path); + } + + public static class Builder { + private NormalizedNode dataTree; + private StoreMetadataNode metadataTree; + private SchemaContext schemaContext; + + public NormalizedNode getDataTree() { + return dataTree; + } + + public Builder setMetadataTree(final StoreMetadataNode metadataTree) { + this.metadataTree = metadataTree; + return this; + } + + public Builder setSchemaContext(final SchemaContext schemaContext) { + this.schemaContext = schemaContext; + return this; + } + + public DataAndMetadataSnapshot build() { + return new DataAndMetadataSnapshot(metadataTree, Optional.fromNullable(schemaContext)); + } + + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java new file mode 100644 index 0000000000..3b9557c6d4 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationApplyOperation.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreTreeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; + +import com.google.common.base.Optional; + +/** + * + * Operation responsible for applying {@link NodeModification} on tree. + * + * Operation is composite - operation on top level node consists of + * suboperations on child nodes. This allows to walk operation hierarchy and + * invoke suboperations independently. + * + * Implementation notes + *
    + *
  • + * Implementations MUST expose all nested suboperations which operates on child + * nodes expose via {@link #getChild(PathArgument)} method. + *
  • Same suboperations SHOULD be used when invoked via + * {@link #apply(NodeModification, Optional)} if applicable. + * + * + * Hierarchical composite operation which is responsible for applying + * modification on particular subtree and creating updated subtree + * + * + */ +public interface ModificationApplyOperation extends StoreTreeNode { + + /** + * + * Implementation of this operation must be stateless and must not change + * state of this object. + * + * @param modification + * NodeModification to be applied + * @param storeMeta + * Store Metadata Node on which NodeModification should be + * applied + * @throws IllegalArgumentException + * If it is not possible to apply Operation on provided Metadata + * node + * @return new {@link StoreMetadataNode} if operation resulted in updating + * node, {@link Optional#absent()} if {@link NodeModification} + * resulted in deletion of this node. + */ + Optional apply(NodeModification modification, Optional storeMeta); + + /** + * Returns a suboperation for specified tree node + * + * @return Reference to suboperation for specified tree node, {@link Optional#absent()} + * if suboperation is not supported for specified tree node. + */ + @Override + public Optional getChild(PathArgument child); + +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java new file mode 100644 index 0000000000..ddf65b6d27 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/MutableDataTree.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import java.util.Map.Entry; + +import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Optional; + +class MutableDataTree { + + private static final Logger log = LoggerFactory.getLogger(MutableDataTree.class); + + final DataAndMetadataSnapshot snapshot; + final NodeModification rootModification; + final ModificationApplyOperation strategyTree; + + private boolean sealed = false; + + private MutableDataTree(final DataAndMetadataSnapshot snapshot, final ModificationApplyOperation strategyTree) { + this.snapshot = snapshot; + this.strategyTree = strategyTree; + this.rootModification = NodeModification.createUnmodified(snapshot.getMetadataTree()); + } + + public void write(final InstanceIdentifier path, final NormalizedNode value) { + checkSealed(); + resolveModificationFor(path).write(value); + } + + public void delete(final InstanceIdentifier path) { + checkSealed(); + resolveModificationFor(path).delete(); + } + + public Optional> read(final InstanceIdentifier path) { + Entry modification = TreeNodeUtils.findClosest(rootModification, path); + return getModifiedVersion(path, modification); + } + + private Optional> getModifiedVersion(final InstanceIdentifier path, final Entry modification) { + Optional result = resolveSnapshot(modification); + if(result.isPresent()) { + NormalizedNode data = result.get().getData(); + return NormalizedNodeUtils.findNode(modification.getKey(),data, path); + } + return Optional.absent(); + + } + + private Optional resolveSnapshot(final Entry keyModification) { + InstanceIdentifier path = keyModification.getKey(); + NodeModification modification = keyModification.getValue(); + return resolveSnapshot(path,modification); + } + + private Optional resolveSnapshot(final InstanceIdentifier path, final NodeModification modification) { + try { + return resolveModificationStrategy(path).apply(modification,modification.getOriginal()); + } catch (Exception e) { + log.error("Could not create snapshot for {},",e); + throw e; + } + } + + private ModificationApplyOperation resolveModificationStrategy(final InstanceIdentifier path) { + log.trace("Resolving modification apply strategy for {}",path); + Optional strategy = TreeNodeUtils.findNode(strategyTree, path); + checkArgument(strategy.isPresent(),"Provided path %s is not supported by data store. No schema available for it.",path); + return strategy.get(); + } + + private NodeModification resolveModificationFor(final InstanceIdentifier path) { + NodeModification modification = rootModification; + // We ensure strategy is present. + resolveModificationStrategy(path); + for (PathArgument pathArg : path.getPath()) { + modification = modification.modifyChild(pathArg); + } + return modification; + } + + public static MutableDataTree from(final DataAndMetadataSnapshot snapshot, final ModificationApplyOperation resolver) { + return new MutableDataTree(snapshot, resolver); + } + + public void seal() { + sealed = true; + rootModification.seal(); + } + + private void checkSealed() { + checkState(!sealed , "Data Tree is sealed. No further modifications allowed."); + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java new file mode 100644 index 0000000000..114595f75b --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperation.java @@ -0,0 +1,343 @@ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Set; + +import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreNodeCompositeBuilder; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.primitives.UnsignedLong; + +public abstract class SchemaAwareApplyOperation implements ModificationApplyOperation { + + public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode) { + if (schemaNode instanceof ContainerSchemaNode) { + return new ContainerModificationStrategy((ContainerSchemaNode) schemaNode); + } else if (schemaNode instanceof ListSchemaNode) { + return new ListMapModificationStrategy((ListSchemaNode) schemaNode); + } else if (schemaNode instanceof ChoiceNode) { + return new ChoiceModificationStrategy((ChoiceNode) schemaNode); + } else if (schemaNode instanceof LeafListSchemaNode) { + return new LeafSetEntryModificationStrategy((LeafListSchemaNode) schemaNode); + } else if (schemaNode instanceof LeafSchemaNode) { + return new LeafModificationStrategy((LeafSchemaNode) schemaNode); + } + throw new IllegalArgumentException("Not supported schema node type for " + schemaNode.getClass()); + } + + @Override + public Optional getChild(final PathArgument child) { + throw new IllegalArgumentException(); + } + + protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) { + Optional potential = getChild(child); + checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child); + return potential.get(); + } + + @Override + public final Optional apply(final NodeModification modification, + final Optional currentMeta) { + switch (modification.getModificationType()) { + case DELETE: + return Optional.absent(); + case SUBTREE_MODIFIED: + return Optional.of(applySubtreeChange(modification, currentMeta.get())); + case WRITE: + return Optional.of(applyWrite(modification, currentMeta)); + case UNMODIFIED: + return currentMeta; + default: + throw new IllegalArgumentException("Provided modification type is not supported."); + } + } + + protected abstract StoreMetadataNode applyWrite(NodeModification modification, + Optional currentMeta); + + protected abstract StoreMetadataNode applySubtreeChange(NodeModification modification, StoreMetadataNode currentMeta); + + public static abstract class ValueNodeModificationStrategy extends + SchemaAwareApplyOperation { + + private final T schema; + + protected ValueNodeModificationStrategy(final T schema) { + super(); + this.schema = schema; + } + + @Override + public Optional getChild(final PathArgument child) { + throw new UnsupportedOperationException("Node " + schema.getPath() + + "is leaf type node. Child nodes not allowed"); + } + + @Override + protected StoreMetadataNode applySubtreeChange(final NodeModification modification, final StoreMetadataNode currentMeta) { + throw new UnsupportedOperationException("Node " + schema.getPath() + + "is leaf type node. Subtree change is not allowed."); + } + + @Override + protected StoreMetadataNode applyWrite(final NodeModification modification, final Optional currentMeta) { + return StoreMetadataNode.builder() + // FIXME Add .increaseNodeVersion() + .setData(modification.getWritenValue()).build(); + } + + } + + public static class LeafSetEntryModificationStrategy extends ValueNodeModificationStrategy { + + protected LeafSetEntryModificationStrategy(final LeafListSchemaNode schema) { + super(schema); + } + } + + public static class LeafModificationStrategy extends ValueNodeModificationStrategy { + + protected LeafModificationStrategy(final LeafSchemaNode schema) { + super(schema); + } + } + + public static abstract class NormalizedNodeContainerModificationStrategy extends SchemaAwareApplyOperation { + + @Override + protected StoreMetadataNode applyWrite(final NodeModification modification, final Optional currentMeta) { + // + NormalizedNode newValue = modification.getWritenValue(); + + StoreMetadataNode newValueMeta = StoreMetadataNode.createRecursivelly(newValue, UnsignedLong.valueOf(0)); + + if(!modification.hasAdditionalModifications()) { + return newValueMeta; + } + StoreNodeCompositeBuilder builder = StoreNodeCompositeBuilder.from(newValueMeta, + createBuilder(modification.getIdentifier())); + + Set processedPreexisting = applyPreexistingChildren(modification, newValueMeta.getChildren(), builder); + applyNewChildren(modification, processedPreexisting, builder); + + return builder.build(); + + } + + @Override + @SuppressWarnings("rawtypes") + public StoreMetadataNode applySubtreeChange(final NodeModification modification, final StoreMetadataNode currentMeta) { + + StoreNodeCompositeBuilder builder = StoreNodeCompositeBuilder.from(currentMeta, + createBuilder(modification.getIdentifier())); + builder.setIdentifier(modification.getIdentifier()); + + // We process preexisting nodes + Set processedPreexisting = applyPreexistingChildren(modification, + currentMeta.getChildren(), builder); + applyNewChildren(modification, processedPreexisting, builder); + return builder.build(); + } + + private void applyNewChildren(final NodeModification modification, final Set ignore, + final StoreNodeCompositeBuilder builder) { + for (NodeModification childModification : modification.getModifications()) { + PathArgument childIdentifier = childModification.getIdentifier(); + // We skip allready processed modifications + if (ignore.contains(childIdentifier)) { + continue; + } + Optional childResult = resolveChildOperation(childIdentifier) // + .apply(childModification, Optional. absent()); + if (childResult.isPresent()) { + builder.add(childResult.get()); + } + } + } + + private Set applyPreexistingChildren(final NodeModification modification, + final Iterable children, final StoreNodeCompositeBuilder nodeBuilder) { + Builder processedModifications = ImmutableSet. builder(); + for (StoreMetadataNode childMeta : children) { + PathArgument childIdentifier = childMeta.getIdentifier(); + // We retrieve Child modification metadata + Optional childModification = modification.getChild(childIdentifier); + // Node is modified + if (childModification.isPresent()) { + processedModifications.add(childIdentifier); + Optional change = resolveChildOperation(childIdentifier) // + .apply(childModification.get(), Optional.of(childMeta)); + } else { + // Child is unmodified - reuse existing metadata and data + // snapshot + nodeBuilder.add(childMeta); + } + } + return processedModifications.build(); + } + + @SuppressWarnings("rawtypes") + protected abstract NormalizedNodeContainerBuilder createBuilder(PathArgument identifier); + } + + public static abstract class DataNodeContainerModificationStrategy extends + NormalizedNodeContainerModificationStrategy { + + private final T schema; + + protected DataNodeContainerModificationStrategy(final T schema) { + super(); + this.schema = schema; + } + + protected T getSchema() { + return schema; + } + + @Override + public Optional getChild(final PathArgument identifier) { + DataSchemaNode child = schema.getDataChildByName(identifier.getNodeType()); + if (child == null || child.isAugmenting()) { + return Optional.absent(); + } + return Optional. of(from(child)); + } + + @Override + @SuppressWarnings("rawtypes") + protected abstract DataContainerNodeBuilder createBuilder(PathArgument identifier); + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + schema + "]"; + } + + } + + public static class ContainerModificationStrategy extends + DataNodeContainerModificationStrategy { + + public ContainerModificationStrategy(final ContainerSchemaNode schemaNode) { + super(schemaNode); + } + + @Override + @SuppressWarnings("rawtypes") + protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) { + // TODO Auto-generated method stub + checkArgument(identifier instanceof NodeIdentifier); + return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier); + } + + } + + public static class ChoiceModificationStrategy extends NormalizedNodeContainerModificationStrategy { + + private final ChoiceNode schema; + + public ChoiceModificationStrategy(final ChoiceNode schemaNode) { + this.schema = schemaNode; + } + + @Override + @SuppressWarnings("rawtypes") + protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) { + checkArgument(identifier instanceof NodeIdentifier); + return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier); + } + + } + + public static class ListEntryModificationStrategy extends DataNodeContainerModificationStrategy { + + protected ListEntryModificationStrategy(final ListSchemaNode schema) { + super(schema); + } + + @Override + @SuppressWarnings("rawtypes") + protected final DataContainerNodeBuilder createBuilder(final PathArgument identifier) { + return ImmutableMapEntryNodeBuilder.create().withNodeIdentifier((NodeIdentifierWithPredicates) identifier); + } + + } + + public static class LeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy { + + private final Optional entryStrategy; + + protected LeafSetModificationStrategy(final LeafListSchemaNode schema) { + entryStrategy = Optional. of(new LeafSetEntryModificationStrategy(schema)); + } + + @Override + protected NormalizedNodeContainerBuilder createBuilder(final PathArgument identifier) { + return ImmutableLeafSetNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier); + } + + @Override + public Optional getChild(final PathArgument identifier) { + if (identifier instanceof NodeWithValue) { + return entryStrategy; + } + return Optional.absent(); + } + + } + + public static class ListMapModificationStrategy extends NormalizedNodeContainerModificationStrategy { + + private final Optional entryStrategy; + + protected ListMapModificationStrategy(final ListSchemaNode schema) { + entryStrategy = Optional. of(new ListEntryModificationStrategy(schema)); + } + + @Override + protected NormalizedNodeContainerBuilder createBuilder(final PathArgument identifier) { + return ImmutableMapNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier); + } + + @Override + public Optional getChild(final PathArgument identifier) { + if (identifier instanceof NodeIdentifierWithPredicates) { + return entryStrategy; + } + return Optional.absent(); + } + + @Override + public String toString() { + return "ListMapModificationStrategy [entry=" + entryStrategy + "]"; + } + } + + public void verifyIdentifier(final PathArgument identifier) { + + } + +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java new file mode 100644 index 0000000000..bca506938f --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaAwareApplyOperationRoot.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class SchemaAwareApplyOperationRoot extends SchemaAwareApplyOperation.DataNodeContainerModificationStrategy { + + private final SchemaContext context; + + public SchemaAwareApplyOperationRoot(final SchemaContext context) { + super(context); + this.context = context; + } + + @Override + protected DataContainerNodeBuilder createBuilder(final PathArgument identifier) { + return ImmutableContainerNodeBuilder.create().withNodeIdentifier((NodeIdentifier) identifier); + } + + public SchemaContext getContext() { + return context; + } + + @Override + public String toString() { + return "SchemaAwareApplyOperationRoot [context=" + context + "]"; + } + +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java new file mode 100644 index 0000000000..7610a78364 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/ModificationMetadataTreeTest.java @@ -0,0 +1,255 @@ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.ID_QNAME; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.INNER_LIST_QNAME; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.NAME_QNAME; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.OUTER_LIST_PATH; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.OUTER_LIST_QNAME; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.TEST_PATH; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.TEST_QNAME; +import static org.opendaylight.controller.md.sal.dom.store.impl.TestModel.VALUE_QNAME; +import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntry; +import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntryBuilder; +import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapNodeBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier; +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.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +import com.google.common.base.Optional; +import com.google.common.primitives.UnsignedLong; + +/** + * + * Schema structure of document is + * + *
    + * container root { 
    + *      list list-a {
    + *              key leaf-a;
    + *              leaf leaf-a;
    + *              choice choice-a {
    + *                      case one {
    + *                              leaf one;
    + *                      }
    + *                      case two-three {
    + *                              leaf two;
    + *                              leaf three;
    + *                      }
    + *              }
    + *              list list-b {
    + *                      key leaf-b;
    + *                      leaf leaf-b;
    + *              }
    + *      }
    + * }
    + * 
    + * + */ +public class ModificationMetadataTreeTest { + + private static final Short ONE_ID = 1; + private static final Short TWO_ID = 2; + private static final String TWO_ONE_NAME = "one"; + private static final String TWO_TWO_NAME = "two"; + + private static final InstanceIdentifier OUTER_LIST_1_PATH = InstanceIdentifier.builder(OUTER_LIST_PATH) + .nodeWithKey(OUTER_LIST_QNAME, ID_QNAME, ONE_ID) // + .build(); + + private static final InstanceIdentifier OUTER_LIST_2_PATH = InstanceIdentifier.builder(OUTER_LIST_PATH) + .nodeWithKey(OUTER_LIST_QNAME, ID_QNAME, TWO_ID) // + .build(); + + private static final InstanceIdentifier TWO_TWO_PATH = InstanceIdentifier.builder(OUTER_LIST_2_PATH) + .node(INNER_LIST_QNAME) // + .nodeWithKey(INNER_LIST_QNAME, NAME_QNAME, TWO_TWO_NAME) // + .build(); + + private static final InstanceIdentifier TWO_TWO_VALUE_PATH = InstanceIdentifier.builder(TWO_TWO_PATH) + .node(VALUE_QNAME) // + .build(); + + private static final MapEntryNode BAR_NODE = mapEntryBuilder(OUTER_LIST_QNAME, ID_QNAME, TWO_ID) // + .withChild(mapNodeBuilder(INNER_LIST_QNAME) // + .withChild(mapEntry(INNER_LIST_QNAME, NAME_QNAME, TWO_ONE_NAME)) // + .withChild(mapEntry(INNER_LIST_QNAME, NAME_QNAME, TWO_TWO_NAME)) // + .build()) // + .build(); + + private SchemaContext schemaContext; + + @Before + public void prepare() { + schemaContext = TestModel.createTestContext(); + assertNotNull("Schema context must not be null.", schemaContext); + } + + /** + * Returns a test document + * + *
    +     * test
    +     *     outer-list
    +     *          id 1
    +     *     outer-list
    +     *          id 2
    +     *          inner-list
    +     *                  name "one"
    +     *          inner-list
    +     *                  name "two"
    +     *
    +     * 
    + * + * @return + */ + public NormalizedNode createDocumentOne() { + return ImmutableContainerNodeBuilder + .create() + .withNodeIdentifier(new NodeIdentifier(schemaContext.getQName())) + .withChild(createTestContainer()).build(); + + } + + private ContainerNode createTestContainer() { + return ImmutableContainerNodeBuilder + .create() + .withNodeIdentifier(new NodeIdentifier(TEST_QNAME)) + .withChild( + mapNodeBuilder(OUTER_LIST_QNAME) + .withChild(mapEntry(OUTER_LIST_QNAME, ID_QNAME, ONE_ID)) + .withChild(BAR_NODE).build()).build(); + } + + private StoreMetadataNode createDocumentOneMetadata() { + UnsignedLong version = UnsignedLong.valueOf(0); + return StoreMetadataNode.createRecursivelly(createDocumentOne(), version); + } + + @Test + public void basicReadWrites() { + MutableDataTree modificationTree = MutableDataTree.from( + DataAndMetadataSnapshot.builder() // + .setMetadataTree(createDocumentOneMetadata()) // + .setSchemaContext(schemaContext) // + .build(), new SchemaAwareApplyOperationRoot(schemaContext)); + Optional> originalBarNode = modificationTree.read(OUTER_LIST_2_PATH); + assertTrue(originalBarNode.isPresent()); + assertSame(BAR_NODE, originalBarNode.get()); + + // writes node to /outer-list/1/inner_list/two/value + modificationTree.write(TWO_TWO_VALUE_PATH, ImmutableNodes.leafNode(VALUE_QNAME, "test")); + + // reads node to /outer-list/1/inner_list/two/value + // and checks if node is already present + Optional> barTwoCModified = modificationTree.read(TWO_TWO_VALUE_PATH); + assertTrue(barTwoCModified.isPresent()); + assertEquals(ImmutableNodes.leafNode(VALUE_QNAME, "test"), barTwoCModified.get()); + + // delete node to /outer-list/1/inner_list/two/value + modificationTree.delete(TWO_TWO_VALUE_PATH); + Optional> barTwoCAfterDelete = modificationTree.read(TWO_TWO_VALUE_PATH); + assertFalse(barTwoCAfterDelete.isPresent()); + } + + + public MutableDataTree createEmptyModificationTree() { + /** + * Creates empty Snapshot with associated schema context. + */ + DataAndMetadataSnapshot emptySnapshot = DataAndMetadataSnapshot.createEmpty(schemaContext); + + /** + * + * Creates Mutable Data Tree based on provided snapshot and schema + * context. + * + */ + MutableDataTree modificationTree = MutableDataTree.from(emptySnapshot, new SchemaAwareApplyOperationRoot( + schemaContext)); + return modificationTree; + } + + @Test + public void createFromEmptyState() { + + MutableDataTree modificationTree = createEmptyModificationTree(); + /** + * Writes empty container node to /test + * + */ + modificationTree.write(TEST_PATH, ImmutableNodes.containerNode(TEST_QNAME)); + + /** + * Writes empty list node to /test/outer-list + */ + modificationTree.write(OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(OUTER_LIST_QNAME).build()); + + /** + * Reads list node from /test/outer-list + */ + Optional> potentialOuterList = modificationTree.read(OUTER_LIST_PATH); + assertTrue(potentialOuterList.isPresent()); + + /** + * Reads container node from /test and verifies that it contains test + * node + */ + Optional> potentialTest = modificationTree.read(TEST_PATH); + ContainerNode containerTest = assertPresentAndType(potentialTest, ContainerNode.class); + + /** + * + * Gets list from returned snapshot of /test and verifies it contains + * outer-list + * + */ + assertPresentAndType(containerTest.getChild(new NodeIdentifier(OUTER_LIST_QNAME)), MapNode.class); + + } + + @Test + public void writeSubtreeReadChildren() { + MutableDataTree modificationTree = createEmptyModificationTree(); + modificationTree.write(TEST_PATH, createTestContainer()); + Optional> potential = modificationTree.read(TWO_TWO_PATH); + MapEntryNode node = assertPresentAndType(potential, MapEntryNode.class); + } + + @Test + public void writeSubtreeDeleteChildren() { + MutableDataTree modificationTree = createEmptyModificationTree(); + modificationTree.write(TEST_PATH, createTestContainer()); + + // We verify data are present + Optional> potentialBeforeDelete = modificationTree.read(TWO_TWO_PATH); + MapEntryNode node = assertPresentAndType(potentialBeforeDelete, MapEntryNode.class); + + modificationTree.delete(TWO_TWO_PATH); + Optional> potentialAfterDelete = modificationTree.read(TWO_TWO_PATH); + assertFalse(potentialAfterDelete.isPresent()); + + } + + private static T assertPresentAndType(final Optional potential, final Class type) { + assertNotNull(potential); + assertTrue(potential.isPresent()); + assertTrue(type.isInstance(potential.get())); + return type.cast(potential.get()); + } + +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java new file mode 100644 index 0000000000..cab7e57500 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestModel.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.store.impl; + +import java.io.InputStream; +import java.util.Collections; +import java.util.Set; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; + +public class TestModel { + + public static final QName TEST_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test", "2014-03-13", + "test"); + public static final QName OUTER_LIST_QNAME = QName.create(TEST_QNAME, "outer-list"); + public static final QName INNER_LIST_QNAME = QName.create(TEST_QNAME, "inner-list"); + public static final QName OUTER_CHOICE_QNAME = QName.create(TEST_QNAME, "outer-choice"); + public static final QName ID_QNAME = QName.create(TEST_QNAME, "id"); + public static final QName NAME_QNAME = QName.create(TEST_QNAME, "name"); + public static final QName VALUE_QNAME = QName.create(TEST_QNAME, "value"); + private static final String DATASTORE_TEST_YANG = "/odl-datastore-test.yang"; + + public static final InstanceIdentifier TEST_PATH = InstanceIdentifier.of(TEST_QNAME); + public static final InstanceIdentifier OUTER_LIST_PATH = InstanceIdentifier.builder(TEST_PATH).node(OUTER_LIST_QNAME).build(); + + + public static final InputStream getDatastoreTestInputStream() { + return getInputStream(DATASTORE_TEST_YANG); + } + + private static InputStream getInputStream(final String resourceName) { + return TestModel.class.getResourceAsStream(DATASTORE_TEST_YANG); + } + + public static SchemaContext createTestContext() { + YangParserImpl parser = new YangParserImpl(); + Set modules = parser.parseYangModelsFromStreams(Collections.singletonList(getDatastoreTestInputStream())); + return parser.resolveSchemaContext(modules); + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang b/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang new file mode 100644 index 0000000000..17541fecab --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang @@ -0,0 +1,42 @@ +module odl-datastore-test { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test"; + prefix "store-test"; + + revision "2014-03-13" { + description "Initial revision."; + } + + container test { + list outer-list { + key id; + leaf id { + type uint16; + } + choice outer-choice { + case one { + leaf one { + type string; + } + } + case two-three { + leaf two { + type string; + } + leaf three { + type string; + } + } + } + list inner-list { + key name; + leaf name { + type string; + } + leaf value { + type string; + } + } + } + } +} \ No newline at end of file -- 2.36.6