Bug 8523: Add support for parsing restconf:yang-data extension 75/58175/6
authorIgor Foltin <igor.foltin@pantheon.tech>
Fri, 2 Jun 2017 12:41:49 +0000 (14:41 +0200)
committerRobert Varga <nite@hq.sk>
Tue, 6 Jun 2017 17:00:17 +0000 (17:00 +0000)
Add support for yang-data extension to the YANG statement parser.
This extension is defined in RFC8040:
https://tools.ietf.org/html/rfc8040#section-8

Config and if-feature statements are ignored
when placed within a yang-data extension body.

Change-Id: Iccc1ffc76cf1ba1032552e68a4591d44f586c889
Signed-off-by: Igor Foltin <igor.foltin@pantheon.tech>
18 files changed:
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/YangDataSchemaNode.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContext.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SubstatementContext.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/SupportedExtensionsMapping.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangDataStatementImpl.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangInferencePipeline.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/YangDataEffectiveStatementImpl.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/YangDataExtensionTest.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/bar.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/baz.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-1.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-2.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-3.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foobar.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/yang-data-extension-test/ietf-restconf.yang [new file with mode: 0644]

diff --git a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/YangDataSchemaNode.java b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/YangDataSchemaNode.java
new file mode 100644 (file)
index 0000000..92c1ab2
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.yangtools.yang.model.api;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Represents 'yang-data' extension statement defined in https://tools.ietf.org/html/rfc8040#section-8
+ * This statement must appear as a top-level statement, otherwise it is ignored and does not appear in the final
+ * schema context. It must contain exactly one top-level container node (directly or indirectly via a uses statement).
+ */
+@Beta
+public interface YangDataSchemaNode extends UnknownSchemaNode {
+
+    /**
+     * Returns container schema node.
+     *
+     * @return container schema node
+     */
+    ContainerSchemaNode getContainer();
+}
index bd0e1d83229b89b61582b26569d13e302aa65702..7396b0813d12f72261a34067911336de8afeb009 100644 (file)
@@ -61,6 +61,15 @@ public interface StmtContext<A, D extends DeclaredStatement<A>, E extends Effect
 
     boolean isConfiguration();
 
+    /**
+     * Checks whether this statement is placed within a 'yang-data' extension statement.
+     * Some YANG statements are constrained when used within a 'yang-data' statement.
+     * See the following link for more information - https://tools.ietf.org/html/rfc8040#section-8
+     *
+     * @return true if it is placed within a 'yang-data' extension statement, otherwise false
+     */
+    boolean isInYangDataExtensionBody();
+
     boolean isEnabledSemanticVersioning();
 
     @Nonnull
index 7725c743664aa288ae639baa65a624397ef5365a..da704e5df417057aae3d038f29ae419e5fe7594f 100644 (file)
@@ -34,6 +34,7 @@ import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamesp
 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.UnknownStatementImpl;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangDataStatementImpl;
 
 public final class StmtContextUtils {
     public static final Splitter LIST_KEY_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
@@ -205,6 +206,16 @@ public final class StmtContextUtils {
         return false;
     }
 
+    /**
+     * Checks if the statement context has a 'yang-data' extension node as its parent.
+     *
+     * @param stmtCtx statement context to be checked
+     * @return true if the parent node is a 'yang-data' node, otherwise false
+     */
+    public static boolean hasYangDataExtensionParent(final StmtContext<?, ?, ?> stmtCtx) {
+        return producesDeclared(stmtCtx.getParentContext(), YangDataStatementImpl.class);
+    }
+
     public static boolean isUnknownStatement(final StmtContext<?, ?, ?> stmtCtx) {
         return producesDeclared(stmtCtx, UnknownStatementImpl.class);
     }
@@ -264,6 +275,10 @@ public final class StmtContextUtils {
         boolean containsIfFeature = false;
         for (final StatementContextBase<?, ?, ?> stmt : stmtContext.declaredSubstatements()) {
             if (YangStmtMapping.IF_FEATURE.equals(stmt.getPublicDefinition())) {
+                if (stmtContext.isInYangDataExtensionBody()) {
+                    break;
+                }
+
                 containsIfFeature = true;
                 if (((Predicate<Set<QName>>) stmt.getStatementArgument()).test(supportedFeatures)) {
                     isSupported = true;
index f81050ff8ee74f6a90d3346489884086b07c8754..0ecbd93df1e1a8abaab8595edb7164e5ff2ee439 100644 (file)
@@ -189,6 +189,11 @@ public class RootStatementContext<A, D extends DeclaredStatement<A>, E extends E
         return true;
     }
 
+    @Override
+    public boolean isInYangDataExtensionBody() {
+        return false;
+    }
+
     @Override
     public boolean isEnabledSemanticVersioning() {
         return sourceContext.isEnabledSemanticVersioning();
index d2e5c733d3bf63c44bb213db9afc5d895746ce58..b1adcde96efe11a8e335bdf0766a2abaf482c039 100644 (file)
@@ -67,6 +67,8 @@ final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends Eff
      */
     private boolean haveConfiguration;
     private boolean configuration;
+    private boolean wasCheckedIfInYangDataExtensionBody;
+    private boolean isInYangDataExtensionBody;
 
     private volatile SchemaPath schemaPath;
 
@@ -295,6 +297,13 @@ final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends Eff
 
     @Override
     public boolean isConfiguration() {
+        // if this statement is within a 'yang-data' extension body, config substatements are ignored as if
+        // they were not declared. As 'yang-data' is always a top-level node, all configs that are within it are
+        // automatically true
+        if (isInYangDataExtensionBody()) {
+            return true;
+        }
+
         if (haveConfiguration) {
             return configuration;
         }
@@ -321,6 +330,23 @@ final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends Eff
         return isConfig;
     }
 
+    @Override
+    public boolean isInYangDataExtensionBody() {
+        if (wasCheckedIfInYangDataExtensionBody) {
+            return isInYangDataExtensionBody;
+        }
+
+        final boolean parentIsInYangDataExtensionBody = parent.isInYangDataExtensionBody();
+        if (parentIsInYangDataExtensionBody) {
+            isInYangDataExtensionBody = parentIsInYangDataExtensionBody;
+        } else {
+            isInYangDataExtensionBody = StmtContextUtils.hasYangDataExtensionParent(this);
+        }
+
+        wasCheckedIfInYangDataExtensionBody = true;
+        return isInYangDataExtensionBody;
+    }
+
     @Override
     public boolean isEnabledSemanticVersioning() {
         return parent.isEnabledSemanticVersioning();
index 8029cc75e137b75cc1e9a8ad5477583e77cdcf41..9d3eaf53de54e362076677240d4b79ed290b1008 100644 (file)
@@ -17,6 +17,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.AnyxmlSchemaLocationEffectiveStatementImpl;
 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.OpenconfigVersionEffectiveStatementImpl;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.YangDataEffectiveStatementImpl;
 
 @Beta
 public enum SupportedExtensionsMapping implements StatementDefinition {
@@ -25,7 +26,9 @@ public enum SupportedExtensionsMapping implements StatementDefinition {
         "anyxml-schema-location", "target-node", false),
     OPENCONFIG_VERSION("http://openconfig.net/yang/openconfig-ext",
         OpenconfigVersionStatementImpl.class, OpenconfigVersionEffectiveStatementImpl.class,
-        "openconfig-version", "semver", false);
+        "openconfig-version", "semver", false),
+    YANG_DATA("urn:ietf:params:xml:ns:yang:ietf-restconf", "2017-01-26", YangDataStatementImpl.class,
+            YangDataEffectiveStatementImpl.class, "yang-data", "name", true);
 
     private final Class<? extends DeclaredStatement<?>> type;
     private final Class<? extends EffectiveStatement<?, ?>> effectiveType;
diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangDataStatementImpl.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangDataStatementImpl.java
new file mode 100644 (file)
index 0000000..fbe699e
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.yangtools.yang.parser.stmt.rfc6020;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.UnknownStatement;
+import org.opendaylight.yangtools.yang.parser.spi.SubstatementValidator;
+import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractDeclaredStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.YangDataEffectiveStatementImpl;
+
+/**
+ * Declared statement representation of 'yang-data' extension defined in https://tools.ietf.org/html/rfc8040#section-8
+ */
+@Beta
+public class YangDataStatementImpl extends AbstractDeclaredStatement<String> implements UnknownStatement<String> {
+    private static final SubstatementValidator SUBSTATEMENT_VALIDATOR = SubstatementValidator.builder(
+            SupportedExtensionsMapping.YANG_DATA)
+            .addMandatory(YangStmtMapping.CONTAINER)
+            .addOptional(YangStmtMapping.USES)
+            .build();
+
+    YangDataStatementImpl(final StmtContext<String, UnknownStatement<String>, ?> ctx) {
+        super(ctx);
+    }
+
+    public static class YangDataSupport extends AbstractStatementSupport<String, UnknownStatement<String>,
+            EffectiveStatement<String, UnknownStatement<String>>> {
+
+        public YangDataSupport() {
+            super(SupportedExtensionsMapping.YANG_DATA);
+        }
+
+        @Override
+        protected SubstatementValidator getSubstatementValidator() {
+            return SUBSTATEMENT_VALIDATOR;
+        }
+
+        @Override
+        public String parseArgumentValue(final StmtContext<?, ?, ?> ctx, String value) {
+            return value;
+        }
+
+        @Override
+        public UnknownStatement<String> createDeclared(final StmtContext<String, UnknownStatement<String>, ?> ctx) {
+            return new YangDataStatementImpl(ctx);
+        }
+
+        @Override
+        public EffectiveStatement<String, UnknownStatement<String>> createEffective(final StmtContext<String,
+                UnknownStatement<String>, EffectiveStatement<String, UnknownStatement<String>>> ctx) {
+            // in case of yang-data node we need to perform substatement validation at the point when we have
+            // effective substatement contexts already available - if the node has only a uses statement declared in it,
+            // one top-level container node may very well be added to the yang-data as an effective statement
+            SUBSTATEMENT_VALIDATOR.validate(ctx);
+            return new YangDataEffectiveStatementImpl(ctx);
+        }
+
+        @Override
+        public void onFullDefinitionDeclared(final Mutable<String, UnknownStatement<String>,
+                EffectiveStatement<String, UnknownStatement<String>>> ctx) {
+            // as per https://tools.ietf.org/html/rfc8040#section-8,
+            // yang-data is ignored unless it appears as a top-level statement
+            if (!ctx.getParentContext().isRootContext()) {
+                ctx.setIsSupportedToBuildEffective(false);
+            }
+        }
+    }
+
+    @Override
+    public String getArgument() {
+        return argument();
+    }
+}
index 99a6e9a3c041d8f5bac3cc6c772e2ab763d6d445..11f603c463eceebb7f861b77990265084b79898f 100644 (file)
@@ -238,6 +238,7 @@ public final class YangInferencePipeline {
             .addSupport(new ValueStatementImpl.Definition())
             .addSupport(new AnyxmlSchemaLocationStatementImpl.AnyxmlSchemaLocationSupport())
             .addSupport(treeScoped(AnyxmlSchemaLocationNamespace.class))
+            .addSupport(new YangDataStatementImpl.YangDataSupport())
             .addSupport(global(StmtOrderingNamespace.class))
             .build();
 
diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/YangDataEffectiveStatementImpl.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/YangDataEffectiveStatementImpl.java
new file mode 100644 (file)
index 0000000..1e3c359
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.yangtools.yang.parser.stmt.rfc6020.effective;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import java.util.Objects;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.YangDataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.UnknownStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
+
+/**
+ * Effective statement representation of 'yang-data' extension defined in https://tools.ietf.org/html/rfc8040#section-8
+ */
+@Beta
+public final class YangDataEffectiveStatementImpl extends UnknownEffectiveStatementBase<String>
+        implements YangDataSchemaNode {
+
+    private final SchemaPath path;
+    private final QName maybeQNameArgument;
+    private final ContainerSchemaNode containerSchemaNode;
+
+    public YangDataEffectiveStatementImpl(final StmtContext<String, UnknownStatement<String>, ?> ctx) {
+        super(ctx);
+
+        QName maybeQNameArgumentInit;
+        try {
+            maybeQNameArgumentInit = Utils.qNameFromArgument(ctx, argument());
+        } catch (IllegalArgumentException e) {
+            maybeQNameArgumentInit = getNodeType();
+        }
+        this.maybeQNameArgument = maybeQNameArgumentInit;
+
+        path = ctx.getParentContext().getSchemaPath().get().createChild(maybeQNameArgument);
+        containerSchemaNode = Preconditions.checkNotNull(firstEffective(ContainerEffectiveStatementImpl.class));
+    }
+
+    @Nonnull
+    @Override
+    public QName getQName() {
+        return maybeQNameArgument;
+    }
+
+    @Nonnull
+    @Override
+    public SchemaPath getPath() {
+        return path;
+    }
+
+    @Nonnull
+    @Override
+    public ContainerSchemaNode getContainer() {
+        return containerSchemaNode;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(maybeQNameArgument, path);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof YangDataEffectiveStatementImpl)) {
+            return false;
+        }
+
+        final YangDataEffectiveStatementImpl other = (YangDataEffectiveStatementImpl) obj;
+        return Objects.equals(maybeQNameArgument, other.maybeQNameArgument) && Objects.equals(path, other.path);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this).add("qname", maybeQNameArgument).add("path", path).toString();
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/YangDataExtensionTest.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/YangDataExtensionTest.java
new file mode 100644 (file)
index 0000000..1acd69f
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.yangtools.yang.stmt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.opendaylight.yangtools.yang.stmt.StmtTestUtils.sourceForResource;
+
+import com.google.common.collect.ImmutableSet;
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.YangDataSchemaNode;
+import org.opendaylight.yangtools.yang.parser.spi.meta.InvalidSubstatementException;
+import org.opendaylight.yangtools.yang.parser.spi.meta.MissingSubstatementException;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+
+public class YangDataExtensionTest {
+
+    private static final StatementStreamSource FOO_MODULE = sourceForResource(
+            "/yang-data-extension-test/foo.yang");
+    private static final StatementStreamSource FOO_INVALID_1_MODULE = sourceForResource(
+            "/yang-data-extension-test/foo-invalid-1.yang");
+    private static final StatementStreamSource FOO_INVALID_2_MODULE = sourceForResource(
+            "/yang-data-extension-test/foo-invalid-2.yang");
+    private static final StatementStreamSource FOO_INVALID_3_MODULE = sourceForResource(
+            "/yang-data-extension-test/foo-invalid-3.yang");
+    private static final StatementStreamSource BAR_MODULE = sourceForResource(
+            "/yang-data-extension-test/bar.yang");
+    private static final StatementStreamSource BAZ_MODULE = sourceForResource(
+            "/yang-data-extension-test/baz.yang");
+    private static final StatementStreamSource FOOBAR_MODULE = sourceForResource(
+            "/yang-data-extension-test/foobar.yang");
+    private static final StatementStreamSource IETF_RESTCONF_MODULE = sourceForResource(
+            "/yang-data-extension-test/ietf-restconf.yang");
+
+    private static Date revision;
+    private static QNameModule fooModule;
+    private static QName myYangDataA;
+    private static QName myYangDataB;
+
+    @BeforeClass
+    public static void setup() throws ParseException {
+        revision = SimpleDateFormatUtil.getRevisionFormat().parse("2017-06-01");
+        fooModule = QNameModule.create(URI.create("foo"), revision);
+        myYangDataA = QName.create(fooModule, "my-yang-data-a");
+        myYangDataB = QName.create(fooModule, "my-yang-data-b");
+    }
+
+    @Test
+    public void testYangData() throws Exception {
+        final SchemaContext schemaContext = StmtTestUtils.parseYangSources(FOO_MODULE, IETF_RESTCONF_MODULE);
+        assertNotNull(schemaContext);
+
+        final Set<ExtensionDefinition> extensions = schemaContext.getExtensions();
+        assertEquals(1, extensions.size());
+
+        final Module foo = schemaContext.findModuleByName("foo", revision);
+        assertNotNull(foo);
+
+        final List<UnknownSchemaNode> unknownSchemaNodes = foo.getUnknownSchemaNodes();
+        assertEquals(2, unknownSchemaNodes.size());
+
+        YangDataSchemaNode myYangDataANode = null;
+        YangDataSchemaNode myYangDataBNode = null;
+        for (final UnknownSchemaNode unknownSchemaNode : unknownSchemaNodes) {
+            assertTrue(unknownSchemaNode instanceof YangDataSchemaNode);
+            final YangDataSchemaNode yangDataSchemaNode = (YangDataSchemaNode) unknownSchemaNode;
+            if (myYangDataA.equals(yangDataSchemaNode.getQName())) {
+                myYangDataANode = yangDataSchemaNode;
+            } else if (myYangDataB.equals(yangDataSchemaNode.getQName())) {
+                myYangDataBNode = yangDataSchemaNode;
+            }
+        }
+
+        assertNotNull(myYangDataANode);
+        assertNotNull(myYangDataBNode);
+
+        assertNotNull(myYangDataANode.getContainer());
+        assertNotNull(myYangDataBNode.getContainer());
+    }
+
+    @Test
+    public void testConfigStatementBeingIgnoredInYangDataBody() throws Exception {
+        final SchemaContext schemaContext = StmtTestUtils.parseYangSources(BAZ_MODULE, IETF_RESTCONF_MODULE);
+        assertNotNull(schemaContext);
+
+        final Module baz = schemaContext.findModuleByName("baz", revision);
+        assertNotNull(baz);
+
+        final List<UnknownSchemaNode> unknownSchemaNodes = baz.getUnknownSchemaNodes();
+        assertEquals(1, unknownSchemaNodes.size());
+
+        final UnknownSchemaNode unknownSchemaNode = unknownSchemaNodes.iterator().next();
+        assertTrue(unknownSchemaNode instanceof YangDataSchemaNode);
+        final YangDataSchemaNode myYangDataNode = (YangDataSchemaNode) unknownSchemaNode;
+        assertNotNull(myYangDataNode);
+
+        final ContainerSchemaNode contInYangData = myYangDataNode.getContainer();
+        assertNotNull(contInYangData);
+        assertTrue(contInYangData.isConfiguration());
+        final ContainerSchemaNode innerCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(baz.getQNameModule(), "inner-cont"));
+        assertNotNull(innerCont);
+        assertTrue(innerCont.isConfiguration());
+        final ContainerSchemaNode grpCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(baz.getQNameModule(), "grp-cont"));
+        assertNotNull(grpCont);
+        assertTrue(grpCont.isConfiguration());
+    }
+
+    @Test
+    public void testIfFeatureStatementBeingIgnoredInYangDataBody() throws Exception {
+        final CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.setSupportedFeatures(ImmutableSet.of());
+        reactor.addSources(FOOBAR_MODULE, IETF_RESTCONF_MODULE);
+
+        final SchemaContext schemaContext = reactor.buildEffective();
+        assertNotNull(schemaContext);
+
+        final Module foobar = schemaContext.findModuleByName("foobar", revision);
+        assertNotNull(foobar);
+
+        final List<UnknownSchemaNode> unknownSchemaNodes = foobar.getUnknownSchemaNodes();
+        assertEquals(1, unknownSchemaNodes.size());
+
+        final UnknownSchemaNode unknownSchemaNode = unknownSchemaNodes.iterator().next();
+        assertTrue(unknownSchemaNode instanceof YangDataSchemaNode);
+        final YangDataSchemaNode myYangDataNode = (YangDataSchemaNode) unknownSchemaNode;
+        assertNotNull(myYangDataNode);
+
+        final ContainerSchemaNode contInYangData = myYangDataNode.getContainer();
+        assertNotNull(contInYangData);
+        final ContainerSchemaNode innerCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(foobar.getQNameModule(), "inner-cont"));
+        assertNotNull(innerCont);
+        final ContainerSchemaNode grpCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(foobar.getQNameModule(), "grp-cont"));
+        assertNotNull(grpCont);
+    }
+
+    @Test
+    public void testYangDataBeingIgnored() throws Exception {
+        // yang-data statement is ignored if it does not appear as a top-level statement
+        // i.e., it will not appear in the final SchemaContext
+        final SchemaContext schemaContext = StmtTestUtils.parseYangSources(BAR_MODULE, IETF_RESTCONF_MODULE);
+        assertNotNull(schemaContext);
+
+        final Module bar = schemaContext.findModuleByName("bar", revision);
+        assertNotNull(bar);
+        final ContainerSchemaNode cont = (ContainerSchemaNode) bar.getDataChildByName(
+                QName.create(bar.getQNameModule(), "cont"));
+        assertNotNull(cont);
+
+        final Set<ExtensionDefinition> extensions = schemaContext.getExtensions();
+        assertEquals(1, extensions.size());
+
+        final List<UnknownSchemaNode> unknownSchemaNodes = cont.getUnknownSchemaNodes();
+        assertEquals(0, unknownSchemaNodes.size());
+    }
+
+    @Test
+    public void testYangDataWithMissingTopLevelContainer() {
+        try {
+            StmtTestUtils.parseYangSources(FOO_INVALID_1_MODULE, IETF_RESTCONF_MODULE);
+            fail("Exception should have been thrown because of missing top-level container in yang-data statement.");
+        } catch (final ReactorException ex) {
+            final Throwable cause = ex.getCause();
+            assertTrue(cause instanceof MissingSubstatementException);
+            assertTrue(cause.getMessage().startsWith("YANG_DATA is missing CONTAINER. Minimal count is 1."));
+        }
+    }
+
+    @Test
+    public void testYangDataWithTwoTopLevelContainers() {
+        try {
+            StmtTestUtils.parseYangSources(FOO_INVALID_2_MODULE, IETF_RESTCONF_MODULE);
+            fail("Exception should have been thrown because of two top-level containers in yang-data statement.");
+        } catch (final ReactorException ex) {
+            final Throwable cause = ex.getCause();
+            assertTrue(cause instanceof InvalidSubstatementException);
+            assertTrue(cause.getMessage().startsWith("Maximal count of CONTAINER for YANG_DATA is 1, detected 2."));
+        }
+    }
+
+    @Test
+    public void testYangDataWithInvalidToplevelNode() {
+        try {
+            StmtTestUtils.parseYangSources(FOO_INVALID_3_MODULE, IETF_RESTCONF_MODULE);
+            fail("Exception should have been thrown because of invalid top-level node in yang-data statement.");
+        } catch (final ReactorException ex) {
+            final Throwable cause = ex.getCause();
+            assertTrue(cause instanceof InvalidSubstatementException);
+            assertTrue(cause.getMessage().startsWith("LEAF is not valid for YANG_DATA."));
+        }
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/bar.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/bar.yang
new file mode 100644 (file)
index 0000000..2fe4298
--- /dev/null
@@ -0,0 +1,20 @@
+module bar {
+    namespace bar;
+    prefix bar;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    container cont {
+        rc:yang-data "my-yang-data" {
+            // only one top-level container is allowed, but in this case it does not matter
+            // as the whole yang-data node is ignored (because it does not appear as a top-level statement)
+            container top-level-cont {}
+            container another-top-level-cont {}
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/baz.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/baz.yang
new file mode 100644 (file)
index 0000000..f483584
--- /dev/null
@@ -0,0 +1,29 @@
+module baz {
+    namespace baz;
+    prefix baz;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    rc:yang-data "my-yang-data" {
+        container cont {
+            config false;
+
+            container inner-cont {
+                config false;
+            }
+
+            uses grp;
+        }
+    }
+
+    grouping grp {
+        container grp-cont {
+            config false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-1.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-1.yang
new file mode 100644 (file)
index 0000000..b56ac4d
--- /dev/null
@@ -0,0 +1,15 @@
+module foo {
+    namespace foo;
+    prefix foo;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    rc:yang-data "my-yang-data" {
+        // missing top level container = should throw an exception
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-2.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-2.yang
new file mode 100644 (file)
index 0000000..33eb4e1
--- /dev/null
@@ -0,0 +1,25 @@
+module foo {
+    namespace foo;
+    prefix foo;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    rc:yang-data "my-yang-data" {
+        // two top level containers, but only one is allowed = should throw an exception
+        container cont-1 {}
+        uses grp-1;
+    }
+
+    grouping grp-1 {
+        uses grp-2;
+    }
+
+    grouping grp-2 {
+        container cont-2 {}
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-3.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-3.yang
new file mode 100644 (file)
index 0000000..98ae30f
--- /dev/null
@@ -0,0 +1,18 @@
+module foo {
+    namespace foo;
+    prefix foo;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    rc:yang-data "my-yang-data" {
+        // invalid top level node, it must be a container = should throw an exception
+        leaf lf {
+            type string;
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo.yang
new file mode 100644 (file)
index 0000000..250cb3a
--- /dev/null
@@ -0,0 +1,23 @@
+module foo {
+    namespace foo;
+    prefix foo;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    rc:yang-data "my-yang-data-a" {
+        container cont {}
+    }
+
+    rc:yang-data "my-yang-data-b" {
+        uses grp;
+    }
+
+    grouping grp {
+        container grp-cont {}
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foobar.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foobar.yang
new file mode 100644 (file)
index 0000000..1b68a84
--- /dev/null
@@ -0,0 +1,29 @@
+module foobar {
+    namespace foobar;
+    prefix foobar;
+
+    import ietf-restconf {
+        prefix rc;
+        revision-date 2017-01-26;
+    }
+
+    revision 2017-06-01;
+
+    rc:yang-data "my-yang-data" {
+        container cont {
+            if-feature feat;
+
+            container inner-cont {
+                if-feature feat;
+            }
+
+            uses grp {
+                if-feature feat;
+            }
+        }
+    }
+
+    grouping grp {
+        container grp-cont {}
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/ietf-restconf.yang b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/ietf-restconf.yang
new file mode 100644 (file)
index 0000000..3008664
--- /dev/null
@@ -0,0 +1,253 @@
+module ietf-restconf {
+  yang-version 1.1;
+  namespace "urn:ietf:params:xml:ns:yang:ietf-restconf";
+  prefix "rc";
+
+  organization
+    "IETF NETCONF (Network Configuration) Working Group";
+
+  contact
+    "WG Web:   <https://datatracker.ietf.org/wg/netconf/>
+     WG List:  <mailto:netconf@ietf.org>
+     Author:   Andy Bierman
+               <mailto:andy@yumaworks.com>
+     Author:   Martin Bjorklund
+               <mailto:mbj@tail-f.com>
+     Author:   Kent Watsen
+               <mailto:kwatsen@juniper.net>";
+
+  description
+    "This module contains conceptual YANG specifications
+     for basic RESTCONF media type definitions used in
+     RESTCONF protocol messages.
+     Note that the YANG definitions within this module do not
+     represent configuration data of any kind.
+     The 'restconf-media-type' YANG extension statement
+     provides a normative syntax for XML and JSON
+     message-encoding purposes.
+     Copyright (c) 2017 IETF Trust and the persons identified as
+     authors of the code.  All rights reserved.
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD License
+     set forth in Section 4.c of the IETF Trust's Legal Provisions
+     Relating to IETF Documents
+     (http://trustee.ietf.org/license-info).
+     This version of this YANG module is part of RFC 8040; see
+     the RFC itself for full legal notices.";
+
+  revision 2017-01-26 {
+    description
+      "Initial revision.";
+    reference
+      "RFC 8040: RESTCONF Protocol.";
+  }
+
+  extension yang-data {
+    argument name {
+      yin-element true;
+    }
+    description
+      "This extension is used to specify a YANG data template that
+       represents conceptual data defined in YANG.  It is
+       intended to describe hierarchical data independent of
+       protocol context or specific message-encoding format.
+       Data definition statements within a yang-data extension
+       specify the generic syntax for the specific YANG data
+       template, whose name is the argument of the 'yang-data'
+       extension statement.
+       Note that this extension does not define a media type.
+       A specification using this extension MUST specify the
+       message-encoding rules, including the content media type.
+       The mandatory 'name' parameter value identifies the YANG
+       data template that is being defined.  It contains the
+       template name.
+       This extension is ignored unless it appears as a top-level
+       statement.  It MUST contain data definition statements
+       that result in exactly one container data node definition.
+       An instance of a YANG data template can thus be translated
+       into an XML instance document, whose top-level element
+       corresponds to the top-level container.
+       The module name and namespace values for the YANG module using
+       the extension statement are assigned to instance document data
+       conforming to the data definition statements within
+       this extension.
+       The substatements of this extension MUST follow the
+       'data-def-stmt' rule in the YANG ABNF.
+       The XPath document root is the extension statement itself,
+       such that the child nodes of the document root are
+       represented by the data-def-stmt substatements within
+       this extension.  This conceptual document is the context
+       for the following YANG statements:
+         - must-stmt
+         - when-stmt
+         - path-stmt
+         - min-elements-stmt
+         - max-elements-stmt
+         - mandatory-stmt
+         - unique-stmt
+         - ordered-by
+         - instance-identifier data type
+       The following data-def-stmt substatements are constrained
+       when used within a 'yang-data' extension statement.
+         - The list-stmt is not required to have a key-stmt defined.
+         - The if-feature-stmt is ignored if present.
+         - The config-stmt is ignored if present.
+         - The available identity values for any 'identityref'
+           leaf or leaf-list nodes are limited to the module
+           containing this extension statement and the modules
+           imported into that module.
+      ";
+  }
+
+  rc:yang-data yang-errors {
+    uses errors;
+  }
+
+  rc:yang-data yang-api {
+    uses restconf;
+  }
+
+  grouping errors {
+    description
+      "A grouping that contains a YANG container
+       representing the syntax and semantics of a
+       YANG Patch error report within a response message.";
+
+    container errors {
+      description
+        "Represents an error report returned by the server if
+         a request results in an error.";
+
+      list error {
+        description
+          "An entry containing information about one
+           specific error that occurred while processing
+           a RESTCONF request.";
+        reference
+          "RFC 6241, Section 4.3.";
+
+        leaf error-type {
+          type enumeration {
+            enum transport {
+              description
+                "The transport layer.";
+            }
+            enum rpc {
+              description
+                "The rpc or notification layer.";
+            }
+            enum protocol {
+              description
+                "The protocol operation layer.";
+            }
+            enum application {
+              description
+                "The server application layer.";
+            }
+          }
+          mandatory true;
+          description
+            "The protocol layer where the error occurred.";
+        }
+
+        leaf error-tag {
+          type string;
+          mandatory true;
+          description
+            "The enumerated error-tag.";
+        }
+
+        leaf error-app-tag {
+          type string;
+          description
+            "The application-specific error-tag.";
+        }
+
+        leaf error-path {
+          type instance-identifier;
+          description
+            "The YANG instance identifier associated
+             with the error node.";
+        }
+
+        leaf error-message {
+          type string;
+          description
+            "A message describing the error.";
+        }
+
+        anydata error-info {
+           description
+             "This anydata value MUST represent a container with
+              zero or more data nodes representing additional
+              error information.";
+        }
+      }
+    }
+  }
+
+  grouping restconf {
+    description
+      "Conceptual grouping representing the RESTCONF
+       root resource.";
+
+    container restconf {
+      description
+        "Conceptual container representing the RESTCONF
+         root resource.";
+
+      container data {
+        description
+          "Container representing the datastore resource.
+           Represents the conceptual root of all state data
+           and configuration data supported by the server.
+           The child nodes of this container can be any data
+           resources that are defined as top-level data nodes
+           from the YANG modules advertised by the server in
+           the 'ietf-yang-library' module.";
+      }
+
+      container operations {
+        description
+          "Container for all operation resources.
+           Each resource is represented as an empty leaf with the
+           name of the RPC operation from the YANG 'rpc' statement.
+           For example, the 'system-restart' RPC operation defined
+           in the 'ietf-system' module would be represented as
+           an empty leaf in the 'ietf-system' namespace.  This is
+           a conceptual leaf and will not actually be found in
+           the module:
+              module ietf-system {
+                leaf system-reset {
+                  type empty;
+                }
+              }
+           To invoke the 'system-restart' RPC operation:
+              POST /restconf/operations/ietf-system:system-restart
+           To discover the RPC operations supported by the server:
+              GET /restconf/operations
+           In XML, the YANG module namespace identifies the module:
+             <system-restart
+                xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
+           In JSON, the YANG module name identifies the module:
+             { 'ietf-system:system-restart' : [null] }
+          ";
+      }
+
+      leaf yang-library-version {
+        type string {
+          pattern '\d{4}-\d{2}-\d{2}';
+        }
+        config false;
+        mandatory true;
+        description
+          "Identifies the revision date of the 'ietf-yang-library'
+           module that is implemented by this RESTCONF server.
+           Indicates the year, month, and day in YYYY-MM-DD
+           numeric format.";
+      }
+    }
+  }
+
+}
\ No newline at end of file