BUG-4684: validate changes against effective state 75/33475/1
authorRobert Varga <robert.varga@pantheon.sk>
Tue, 19 Jan 2016 20:50:35 +0000 (21:50 +0100)
committerTony Tkacik <ttkacik@cisco.com>
Mon, 25 Jan 2016 12:21:57 +0000 (12:21 +0000)
In order to deal with merges, we would have to concoct a very fragile
machinery, which would perform the equivalent of apply(), except it
would not produce merged data.

Instead of that let us pass down version, which is all we need to run
the apply operation. Once applied, we will have a preliminary result of
apply, which we can reuse under some circumstances -- which is if the
observed current metadata node does not change and if the SchemaContext
(and hence the associated SchemaAwareApplyOperation object) does not
change.

If either does, we re-calculate the result -- but that may not be
accurate at this point.

Change-Id: I145969e47136b324c07868bd00ded0764ef634f4
Signed-off-by: Robert Varga <robert.varga@pantheon.sk>
Signed-off-by: Filip.Gregor <fgregor@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractDataTreeTip.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractNodeContainerModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractValueNodeModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AlwaysFailOperation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationApplyOperation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/RootModificationApplyOperation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/UnkeyedListModificationStrategy.java

index 677135ef78f91cbaf96f4e6afa4b938bcd6b70a3..76e41886ebd062324799086762e8f2c11bc7be93 100644 (file)
@@ -35,7 +35,7 @@ abstract class AbstractDataTreeTip implements DataTreeTip {
         final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification;
         Preconditions.checkArgument(m.isSealed(), "Attempted to verify unsealed modification %s", m);
 
-        m.getStrategy().checkApplicable(PUBLIC_ROOT_PATH, m.getRootModification(), Optional.of(getTipRoot()));
+        m.getStrategy().checkApplicable(PUBLIC_ROOT_PATH, m.getRootModification(), Optional.of(getTipRoot()), m.getVersion());
     }
 
     @Override
index 59ed52d04a9d242c3149a006b858233da2f21c13..831c818647ff333b89366e02032f23126ae179ee 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
@@ -282,7 +281,7 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl
 
     @Override
     protected void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws DataValidationFailedException {
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         if (!modification.getOriginal().isPresent() && !current.isPresent()) {
             throw new ModifiedNodeDoesNotExistException(path, String.format("Node %s does not exist. Cannot apply modification to its children.", path));
         }
@@ -291,7 +290,7 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl
             throw new ConflictingModificationAppliedException(path, "Node was deleted by other transaction.");
         }
 
-        checkChildPreconditions(path, modification, current.get());
+        checkChildPreconditions(path, modification, current.get(), version);
     }
 
     /**
@@ -301,21 +300,22 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl
      * @param modification current modification
      * @param current Current data tree node.
      */
-    private void checkChildPreconditions(final YangInstanceIdentifier path, final NodeModification modification, final TreeNode current) throws DataValidationFailedException {
+    private void checkChildPreconditions(final YangInstanceIdentifier path, final NodeModification modification,
+            final TreeNode current, final Version version) throws DataValidationFailedException {
         for (final NodeModification childMod : modification.getChildren()) {
             final YangInstanceIdentifier.PathArgument childId = childMod.getIdentifier();
             final Optional<TreeNode> childMeta = current.getChild(childId);
 
             final YangInstanceIdentifier childPath = path.node(childId);
-            resolveChildOperation(childId).checkApplicable(childPath, childMod, childMeta);
+            resolveChildOperation(childId).checkApplicable(childPath, childMod, childMeta, version);
         }
     }
 
     @Override
     protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws DataValidationFailedException {
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         if (current.isPresent()) {
-            checkChildPreconditions(path, modification, current.get());
+            checkChildPreconditions(path, modification, current.get(), version);
         }
     }
 
index e4b80b2fb5beb1372a447ff7146ab8a843e643bb..ae7edda82b93c3f158700076d4913078846f6cf4 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -69,7 +68,7 @@ abstract class AbstractValueNodeModificationStrategy<T extends DataSchemaNode> e
 
     @Override
     protected final void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws IncorrectDataStructureException {
+            final Optional<TreeNode> current, final Version version) throws IncorrectDataStructureException {
         throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
     }
 
index cd2455695ae8cda0ddddb55de1cffcff7cc95af0..42bef4314a12b588f3f081116d597498908cfa9f 100644 (file)
@@ -34,8 +34,8 @@ final class AlwaysFailOperation extends ModificationApplyOperation {
     }
 
     @Override
-    void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification,
-            final Optional<TreeNode> storeMetadata) {
+    void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> storeMetadata, final Version version) {
         throw new IllegalStateException("Schema Context is not available.");
     }
 
index ede5bcc067b227719a92655359c82df1a013ad75..032e1d8f450ca4d171bde31e603a11e7f24b2a11 100644 (file)
@@ -8,7 +8,6 @@
 
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
-
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
@@ -48,53 +47,45 @@ final class MinMaxElementsValidation extends SchemaAwareApplyOperation {
 
     }
 
-    private static int findChildrenBefore(final Optional<TreeNode> current) {
-        if (current.isPresent()) {
-            return numOfChildrenFromValue(current.get().getData());
-        } else {
-            return 0;
+    private void validateMinMaxElements(final YangInstanceIdentifier path, final PathArgument id,
+            final NormalizedNode<?, ?> data) throws DataValidationFailedException {
+        final int children = numOfChildrenFromValue(data);
+        if (minElements != null && minElements > children) {
+            throw new DataValidationFailedException(path, String.format(
+                    "%s does not have enough elements (%s), needs at least %s", id,
+                    children, minElements));
         }
-    }
-
-    private static int findChildrenAfter(final ModifiedNode modification) {
-        if (modification.getWrittenValue() != null) {
-            return numOfChildrenFromValue(modification.getWrittenValue());
-        } else {
-            return 0;
+        if (maxElements != null && maxElements < children) {
+            throw new DataValidationFailedException(path, String.format(
+                    "%s has too many elements (%s), can have at most %s", id, children,
+                    maxElements));
         }
     }
 
     private void checkMinMaxElements(final YangInstanceIdentifier path, final NodeModification nodeMod,
-            final Optional<TreeNode> current) throws DataValidationFailedException {
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         if (!(nodeMod instanceof ModifiedNode)) {
             LOG.debug("Could not validate {}, does not implement expected class {}", nodeMod, ModifiedNode.class);
             return;
         }
 
         final ModifiedNode modification = (ModifiedNode) nodeMod;
-        final int childrenBefore = (modification.getOperation() == LogicalOperation.WRITE) ? 0 : findChildrenBefore
-                (current);
-        Verify.verify(childrenBefore >= 0, "Child count before is %s (from %s)", childrenBefore, current);
 
-        final int childrenAfter = findChildrenAfter(modification);
-        Verify.verify(childrenAfter >= 0, "Child count after is %s (from %s)", childrenAfter, modification);
+        // We need to actually perform the operation to get deal with merge in a sane manner. We know the modification
+        // is immutable, so the result of validation will probably not change.
+        final Optional<TreeNode> maybeApplied = delegate.apply(modification, current, version);
+        Verify.verify(maybeApplied.isPresent());
 
-        final int childrenModified = numOfChildrenFromChildMods(modification, current);
-        LOG.debug("Modified child count is %s (from %s and %s)", childrenModified, modification, current);
+        final TreeNode applied = maybeApplied.get();
+        validateMinMaxElements(path, modification.getIdentifier(), applied.getData());
 
-        final int childrenTotal = childrenBefore + childrenAfter + childrenModified;
-        Verify.verify(childrenTotal >= 0, "Total child count is %s (from %s and %s)", childrenTotal, modification, current);
-
-        if (minElements != null && minElements > childrenTotal) {
-            throw new DataValidationFailedException(path, String.format(
-                    "%s does not have enough elements (%s), needs at least %s", modification.getIdentifier(),
-                    childrenTotal, minElements));
-        }
-        if (maxElements != null && maxElements < childrenTotal) {
-            throw new DataValidationFailedException(path, String.format(
-                    "%s has too many elements (%s), can have at most %s", modification.getIdentifier(), childrenTotal,
-                    maxElements));
-        }
+        // Everything passed. We now have a snapshot of the result node, it would be too bad if we just threw it out.
+        // We know what the result of an apply operation is going to be *if* the following are kept unchanged:
+        // - the 'current' node
+        // - the schemacontext (therefore, the fact this object is associated with the modification)
+        //
+        // So let's stash the result. We will pick it up during apply operation.
+        modification.setValidatedNode(this, current, applied);
     }
 
     private static int numOfChildrenFromValue(final NormalizedNode<?, ?> value) {
@@ -109,62 +100,31 @@ final class MinMaxElementsValidation extends SchemaAwareApplyOperation {
                 value.getClass()));
     }
 
-    private static int numOfChildrenFromChildMods(final ModifiedNode modification, final Optional<TreeNode> current) {
-        int result = 0;
-        for (final ModifiedNode modChild : modification.getChildren()) {
-            switch (modChild.getOperation()) {
-                case WRITE:
-                    if (!checkOriginalPresent(modChild)) {
-                        result++;
-                    }
-                    break;
-                case MERGE:
-                    if (!checkOriginalPresent(modChild)) {
-                        result++;
-                    }
-                    break;
-                case DELETE:
-                    if (checkOriginalPresent(modChild)) {
-                        result--;
-                    }
-                    break;
-                case NONE:
-                case TOUCH:
-                    // NOOP
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unsupported operation type: " + modChild.getOperation());
-            }
-        }
-        return result;
-    }
-
     private static boolean checkOriginalPresent(ModifiedNode child) {
         return child.getOriginal().isPresent();
     }
 
     @Override
     protected void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws DataValidationFailedException {
-        delegate.checkTouchApplicable(path, modification, current);
-        checkMinMaxElements(path, modification, current);
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
+        delegate.checkTouchApplicable(path, modification, current, version);
+        checkMinMaxElements(path, modification, current, version);
     }
 
     @Override
     protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws DataValidationFailedException {
-        delegate.checkMergeApplicable(path, modification, current);
-        checkMinMaxElements(path, modification, current);
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
+        delegate.checkMergeApplicable(path, modification, current, version);
+        checkMinMaxElements(path, modification, current, version);
     }
 
     @Override
     protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws DataValidationFailedException {
-        delegate.checkWriteApplicable(path, modification, current);
-        checkMinMaxElements(path, modification, current);
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
+        delegate.checkWriteApplicable(path, modification, current, version);
+        checkMinMaxElements(path, modification, current, version);
     }
 
-
     @Override
     public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
         return delegate.getChild(child);
@@ -177,17 +137,35 @@ final class MinMaxElementsValidation extends SchemaAwareApplyOperation {
 
     @Override
     protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+        final TreeNode validated = modification.getValidatedNode(this, Optional.of(currentMeta));
+        if (validated != null) {
+            return validated;
+        }
+
+        // FIXME: the result moved, make sure we enforce again
         return delegate.applyMerge(modification, currentMeta, version);
     }
 
     @Override
     protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
+        final TreeNode validated = modification.getValidatedNode(this, Optional.of(currentMeta));
+        if (validated != null) {
+            return validated;
+        }
+
+        // FIXME: the result moved, make sure we enforce again
         return delegate.applyTouch(modification, currentMeta, version);
     }
 
     @Override
     protected TreeNode applyWrite(final ModifiedNode modification, final Optional<TreeNode> currentMeta,
             final Version version) {
+        final TreeNode validated = modification.getValidatedNode(this, currentMeta);
+        if (validated != null) {
+            return validated;
+        }
+
+        // FIXME: the result moved, make sure we enforce again
         return delegate.applyWrite(modification, currentMeta, version);
     }
 
index 933bf9c5704a9f788d0882dd2b1e6d4d06966bd5..83c9b6d0411c5146ba156a03a9f7376e9ebebffa 100644 (file)
@@ -57,16 +57,17 @@ abstract class ModificationApplyOperation implements StoreTreeNode<ModificationA
     abstract Optional<TreeNode> apply(ModifiedNode modification, Optional<TreeNode> storeMeta, Version version);
 
     /**
-    *
-    * Checks if provided node modification could be applied to current metadata node.
-    *
-    * @param modification Modification
-    * @param current Metadata Node to which modification should be applied
-    * @return true if modification is applicable
-    *         false if modification is no applicable
-    * @throws DataValidationFailedException
-    */
-   abstract void checkApplicable(YangInstanceIdentifier path, NodeModification modification, Optional<TreeNode> current) throws DataValidationFailedException;
+     *
+     * Checks if provided node modification could be applied to current metadata node.
+     *
+     * @param modification Modification
+     * @param current Metadata Node to which modification should be applied
+     * @param version
+     * @return true if modification is applicable
+     *         false if modification is no applicable
+     * @throws DataValidationFailedException
+     */
+   abstract void checkApplicable(YangInstanceIdentifier path, NodeModification modification, Optional<TreeNode> current, Version version) throws DataValidationFailedException;
 
     /**
      *
index be0ed682adf111f79b9148bdfdb4953fc5045dcc..299a52b5770f23439e71c6f6252f7119c7edef28 100644 (file)
@@ -66,6 +66,11 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
     // Alternative history introduced in WRITE nodes. Instantiated when we touch any child underneath such a node.
     private TreeNode writtenOriginal;
 
+    // Internal cache for TreeNodes created as part of validation
+    private SchemaAwareApplyOperation validatedOp;
+    private Optional<TreeNode> validatedCurrent;
+    private TreeNode validatedNode;
+
     private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final ChildTrackingPolicy childPolicy) {
         this.identifier = identifier;
         this.original = original;
@@ -346,4 +351,14 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
     public static ModifiedNode createUnmodified(final TreeNode metadataTree, final ChildTrackingPolicy childPolicy) {
         return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), childPolicy);
     }
+
+    void setValidatedNode(final SchemaAwareApplyOperation op, final Optional<TreeNode> current, final TreeNode node) {
+        this.validatedOp = Preconditions.checkNotNull(op);
+        this.validatedCurrent = Preconditions.checkNotNull(current);
+        this.validatedNode = Preconditions.checkNotNull(node);
+    }
+
+    TreeNode getValidatedNode(final SchemaAwareApplyOperation op, final Optional<TreeNode> current) {
+        return op.equals(validatedOp) && current.equals(validatedCurrent) ? validatedNode : null;
+    }
 }
index 6fc704c053c32bb38212673580bbb88948955bba..ead1ebff6d3aef49162092dd04712760d427e739 100644 (file)
@@ -67,9 +67,9 @@ abstract class RootModificationApplyOperation extends ModificationApplyOperation
     }
 
     @Override
-    final void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current)
-            throws DataValidationFailedException {
-        getDelegate().checkApplicable(path, modification, current);
+    final void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
+        getDelegate().checkApplicable(path, modification, current, version);
     }
 
     @Override
index a511ddf064b719cb1c0f1320b6da2ec21b98e959..22a056fca3c0fa4f33854103864cf76135a82891 100644 (file)
@@ -115,19 +115,19 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation {
     }
 
     @Override
-    final void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    final void checkApplicable(final YangInstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         switch (modification.getOperation()) {
         case DELETE:
             checkDeleteApplicable(modification, current);
             break;
         case TOUCH:
-            checkTouchApplicable(path, modification, current);
+            checkTouchApplicable(path, modification, current, version);
             break;
         case WRITE:
-            checkWriteApplicable(path, modification, current);
+            checkWriteApplicable(path, modification, current, version);
             break;
         case MERGE:
-            checkMergeApplicable(path, modification, current);
+            checkMergeApplicable(path, modification, current, version);
             break;
         case NONE:
             break;
@@ -136,7 +136,8 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation {
         }
     }
 
-    protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         final Optional<TreeNode> original = modification.getOriginal();
         if (original.isPresent() && current.isPresent()) {
             /*
@@ -162,7 +163,7 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation {
      * @throws DataValidationFailedException
      */
     protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-        final Optional<TreeNode> current) throws DataValidationFailedException {
+        final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         final Optional<TreeNode> original = modification.getOriginal();
         if (original.isPresent() && current.isPresent()) {
             checkNotConflicting(path, original.get(), current.get());
@@ -256,8 +257,8 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation {
      * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
      * @throws IncorrectDataStructureException If subtree modification is not applicable (e.g. leaf node).
      */
-    protected abstract void checkTouchApplicable(YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws DataValidationFailedException;
+    protected abstract void checkTouchApplicable(YangInstanceIdentifier path, NodeModification modification,
+            Optional<TreeNode> current, Version version) throws DataValidationFailedException;
 
     /**
      * Checks if supplied schema node belong to specified Data Tree type. All nodes belong to the operational tree,
index be35b5aa673402019e2b2210365194577f223c42..55784e06b4577144d873f446cb6d48accfa5df5e 100644 (file)
@@ -89,12 +89,13 @@ final class StructuralContainerModificationStrategy extends ModificationApplyOpe
     }
 
     @Override
-    void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+    void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current, final Version version) throws DataValidationFailedException {
         if (modification.getOperation() == LogicalOperation.TOUCH && !current.isPresent()) {
             // Structural containers are created as needed, so we pretend this container is here
-            delegate.checkApplicable(path, modification, fakeMeta(FAKE_VERSION));
+            delegate.checkApplicable(path, modification, fakeMeta(FAKE_VERSION), version);
         } else {
-            delegate.checkApplicable(path, modification, current);
+            delegate.checkApplicable(path, modification, current, version);
         }
     }
 
index d93d4971d1b12d09bc0ace4355d723a8a346d5ff..4b569cef0ee9b9b48d0145a23036b16d485a604f 100644 (file)
@@ -132,7 +132,7 @@ final class UnkeyedListModificationStrategy extends SchemaAwareApplyOperation {
 
     @Override
     protected void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification,
-            final Optional<TreeNode> current) throws IncorrectDataStructureException {
+            final Optional<TreeNode> current, final Version version) throws IncorrectDataStructureException {
         throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
     }