From d2d8522c64bede5324547689345e4c0188b5bb87 Mon Sep 17 00:00:00 2001 From: Igor Foltin Date: Fri, 2 Jun 2017 14:41:49 +0200 Subject: [PATCH] Bug 8523: Add support for parsing restconf:yang-data extension 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 --- .../yang/model/api/YangDataSchemaNode.java | 27 ++ .../yang/parser/spi/meta/StmtContext.java | 9 + .../parser/spi/meta/StmtContextUtils.java | 15 ++ .../stmt/reactor/RootStatementContext.java | 5 + .../stmt/reactor/SubstatementContext.java | 26 ++ .../rfc6020/SupportedExtensionsMapping.java | 5 +- .../stmt/rfc6020/YangDataStatementImpl.java | 84 ++++++ .../stmt/rfc6020/YangInferencePipeline.java | 1 + .../YangDataEffectiveStatementImpl.java | 91 +++++++ .../yang/stmt/YangDataExtensionTest.java | 220 +++++++++++++++ .../yang-data-extension-test/bar.yang | 20 ++ .../yang-data-extension-test/baz.yang | 29 ++ .../foo-invalid-1.yang | 15 ++ .../foo-invalid-2.yang | 25 ++ .../foo-invalid-3.yang | 18 ++ .../yang-data-extension-test/foo.yang | 23 ++ .../yang-data-extension-test/foobar.yang | 29 ++ .../ietf-restconf.yang | 253 ++++++++++++++++++ 18 files changed, 894 insertions(+), 1 deletion(-) create mode 100644 yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/YangDataSchemaNode.java create mode 100644 yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangDataStatementImpl.java create mode 100644 yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/YangDataEffectiveStatementImpl.java create mode 100644 yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/YangDataExtensionTest.java create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/bar.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/baz.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-1.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-2.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-3.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foobar.yang create mode 100644 yang/yang-parser-impl/src/test/resources/yang-data-extension-test/ietf-restconf.yang 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 index 0000000000..92c1ab261f --- /dev/null +++ b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/YangDataSchemaNode.java @@ -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(); +} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContext.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContext.java index bd0e1d8322..7396b0813d 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContext.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContext.java @@ -61,6 +61,15 @@ public interface StmtContext, 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 diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java index 7725c74366..da704e5df4 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java @@ -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>) stmt.getStatementArgument()).test(supportedFeatures)) { isSupported = true; diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java index f81050ff8e..0ecbd93df1 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java @@ -189,6 +189,11 @@ public class RootStatementContext, E extends E return true; } + @Override + public boolean isInYangDataExtensionBody() { + return false; + } + @Override public boolean isEnabledSemanticVersioning() { return sourceContext.isEnabledSemanticVersioning(); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SubstatementContext.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SubstatementContext.java index d2e5c733d3..b1adcde96e 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SubstatementContext.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SubstatementContext.java @@ -67,6 +67,8 @@ final class SubstatementContext, 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, 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, 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(); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/SupportedExtensionsMapping.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/SupportedExtensionsMapping.java index 8029cc75e1..9d3eaf53de 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/SupportedExtensionsMapping.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/SupportedExtensionsMapping.java @@ -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> type; private final Class> 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 index 0000000000..fbe699e834 --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangDataStatementImpl.java @@ -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 implements UnknownStatement { + private static final SubstatementValidator SUBSTATEMENT_VALIDATOR = SubstatementValidator.builder( + SupportedExtensionsMapping.YANG_DATA) + .addMandatory(YangStmtMapping.CONTAINER) + .addOptional(YangStmtMapping.USES) + .build(); + + YangDataStatementImpl(final StmtContext, ?> ctx) { + super(ctx); + } + + public static class YangDataSupport extends AbstractStatementSupport, + EffectiveStatement>> { + + 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 createDeclared(final StmtContext, ?> ctx) { + return new YangDataStatementImpl(ctx); + } + + @Override + public EffectiveStatement> createEffective(final StmtContext, EffectiveStatement>> 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, + EffectiveStatement>> 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(); + } +} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangInferencePipeline.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangInferencePipeline.java index 99a6e9a3c0..11f603c463 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangInferencePipeline.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangInferencePipeline.java @@ -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 index 0000000000..1e3c359ef9 --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/YangDataEffectiveStatementImpl.java @@ -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 + implements YangDataSchemaNode { + + private final SchemaPath path; + private final QName maybeQNameArgument; + private final ContainerSchemaNode containerSchemaNode; + + public YangDataEffectiveStatementImpl(final StmtContext, ?> 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 index 0000000000..1acd69f75e --- /dev/null +++ b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/YangDataExtensionTest.java @@ -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 extensions = schemaContext.getExtensions(); + assertEquals(1, extensions.size()); + + final Module foo = schemaContext.findModuleByName("foo", revision); + assertNotNull(foo); + + final List 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 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 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 extensions = schemaContext.getExtensions(); + assertEquals(1, extensions.size()); + + final List 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 index 0000000000..2fe4298728 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/bar.yang @@ -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 index 0000000000..f4835844d8 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/baz.yang @@ -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 index 0000000000..b56ac4d21d --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-1.yang @@ -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 index 0000000000..33eb4e1590 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-2.yang @@ -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 index 0000000000..98ae30fb36 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo-invalid-3.yang @@ -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 index 0000000000..250cb3a834 --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foo.yang @@ -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 index 0000000000..1b68a848de --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/foobar.yang @@ -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 index 0000000000..3008664b4b --- /dev/null +++ b/yang/yang-parser-impl/src/test/resources/yang-data-extension-test/ietf-restconf.yang @@ -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: + WG List: + Author: Andy Bierman + + Author: Martin Bjorklund + + Author: Kent Watsen + "; + + 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: + + 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 -- 2.36.6