Add support for yang-data inference 56/97356/6
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 30 Aug 2021 09:46:53 +0000 (11:46 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 30 Aug 2021 11:43:36 +0000 (13:43 +0200)
We will be needing inference abilities across yang-data. Add support for
entering in yang-data and also navigating within it.
YAngDataEffectiveStatement is updated to fully support schema tree and
data tree lookups.

JIRA: YANGTOOLS-1297
Change-Id: I1a10165017070a9430d5c9e43b3a705bc75c4d02
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataEffectiveStatement.java
model/yang-model-util/pom.xml
model/yang-model-util/src/main/java/module-info.java
model/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java
model/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1297Test.java [new file with mode: 0644]
model/yang-model-util/src/test/resources/ietf-restconf.yang [new file with mode: 0644]
parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataArgumentNamespace.java
parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataEffectiveStatementImpl.java

index d53b957324857a7c9c7aa50adb7a22d7bca49579..b10ebb7c354785cbdf5648ada67b691f79693d21 100644 (file)
@@ -8,8 +8,10 @@
 package org.opendaylight.yangtools.rfc8040.model.api;
 
 import com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UnknownEffectiveStatement;
 
 /**
@@ -17,7 +19,8 @@ import org.opendaylight.yangtools.yang.model.api.stmt.UnknownEffectiveStatement;
  * <a href="https://tools.ietf.org/html/rfc8040#section-8">RFC 8040</a>.
  */
 @Beta
-public interface YangDataEffectiveStatement extends UnknownEffectiveStatement<String, YangDataStatement> {
+public interface YangDataEffectiveStatement extends UnknownEffectiveStatement<String, YangDataStatement>,
+        DataTreeAwareEffectiveStatement<String, YangDataStatement> {
     @Override
     default StatementDefinition statementDefinition() {
         return YangDataStatements.YANG_DATA;
@@ -28,5 +31,5 @@ public interface YangDataEffectiveStatement extends UnknownEffectiveStatement<St
      *
      * @return container statement
      */
-    ContainerEffectiveStatement getContainer();
+    @NonNull ContainerEffectiveStatement getContainer();
 }
index 68c9a3e0905eef90402343b2209f627e87abfcc4..f70f4b9f95c1214f76b90cb7e6db25b83fd0b31b 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-xpath-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc8040-model-api</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
index b7c125050e94ae734cb0905ba2bb4ae0a8ed0f57..7e611322f049a2849bb0510c6d60028d03eb36a3 100644 (file)
@@ -9,12 +9,13 @@ module org.opendaylight.yangtools.yang.model.util {
     exports org.opendaylight.yangtools.yang.model.util;
 
     requires transitive com.google.common;
+    requires transitive org.opendaylight.yangtools.concepts;
+    requires transitive org.opendaylight.yangtools.yang.common;
     requires transitive org.opendaylight.yangtools.yang.model.api;
     requires transitive org.opendaylight.yangtools.yang.model.spi;
     requires transitive org.opendaylight.yangtools.yang.repo.api;
     requires transitive org.opendaylight.yangtools.yang.xpath.api;
-    requires transitive org.opendaylight.yangtools.yang.common;
-    requires transitive org.opendaylight.yangtools.concepts;
+    requires transitive org.opendaylight.yangtools.rfc8040.model.api;
     requires org.slf4j;
 
     // Annotations
index 2281db9bb48ceeca1aedd2825b596940fca502e7..06c8c0b9e551ab00ed07924bfc02fbb252d7abea 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Mutable;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
 import org.opendaylight.yangtools.yang.common.AbstractQName;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
@@ -484,6 +485,34 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
         return pushTypedef(requireNonNull(nodeIdentifier));
     }
 
+    /**
+     * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name.
+     *
+     * @param namespace Module namespace in which to lookup the template
+     * @param name Template name
+     * @return Resolved yang-data
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if the corresponding yang-data cannot be found
+     * @throws IllegalStateException if this stack is not empty
+     */
+    public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) {
+        final EffectiveStatement<?, ?> parent = deque.peekFirst();
+        checkState(parent == null, "Cannot lookup yang-data in a non-empty stack");
+
+        final String templateName = requireNonNull(name);
+        final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(requireNonNull(namespace));
+        checkArgument(module != null, "Module for %s not found", namespace);
+
+        final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class)
+            .filter(stmt -> templateName.equals(stmt.argument()))
+            .findFirst()
+            .orElseThrow(
+                () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace));
+        deque.push(ret);
+        currentModule = module;
+        return ret;
+    }
+
     /**
      * Pop the current statement from the stack.
      *
diff --git a/model/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1297Test.java b/model/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1297Test.java
new file mode 100644 (file)
index 0000000..685ae83
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+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.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class YT1297Test {
+    private static final QNameModule RESTCONF =
+        QNameModule.create(XMLNamespace.of("urn:ietf:params:xml:ns:yang:ietf-restconf"), Revision.of("2017-01-26"));
+    private static final QNameModule BAD_MODULE =
+        QNameModule.create(XMLNamespace.of("urn:ietf:params:xml:ns:yang:ietf-restconf"), Revision.of("2018-01-26"));
+
+    private static EffectiveModelContext context;
+
+    private final SchemaInferenceStack stack = SchemaInferenceStack.of(context);
+
+    @BeforeClass
+    public static void beforeClass() {
+        context = YangParserTestUtils.parseYangResource("/ietf-restconf.yang");
+    }
+
+    @Test
+    public void testEnterYangData() {
+        assertNotNull(stack.enterYangData(RESTCONF, "yang-api"));
+        assertNotNull(stack.enterDataTree(QName.create(RESTCONF, "restconf")));
+    }
+
+    @Test
+    public void testEnterYangDataNegative() {
+        Exception ex = assertThrows(IllegalArgumentException.class, () -> stack.enterYangData(RESTCONF, "bad-name"));
+        assertEquals("yang-data bad-name not present in " + RESTCONF, ex.getMessage());
+        ex = assertThrows(IllegalArgumentException.class, () -> stack.enterYangData(BAD_MODULE, "whatever"));
+        assertEquals("Module for " + BAD_MODULE + " not found", ex.getMessage());
+
+        assertNotNull(stack.enterGrouping(QName.create(RESTCONF, "errors")));
+        ex = assertThrows(IllegalStateException.class, () -> stack.enterYangData(RESTCONF, "yang-api"));
+        assertEquals("Cannot lookup yang-data in a non-empty stack", ex.getMessage());
+    }
+}
diff --git a/model/yang-model-util/src/test/resources/ietf-restconf.yang b/model/yang-model-util/src/test/resources/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
index 68f242edd03d2758ccb73b8dbdb7bfe4709f1878..c05187235eecf0bcc29ffd24423d4437cfe60fee 100644 (file)
@@ -15,11 +15,11 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
 
 /**
- * Namespace for remembering the {@code yang-data} argument's QName. This namespace is necessary because we are forced
- * to have a String argument due to us being an extension.
+ * Namespace for remembering the {@code yang-data} argument's QName.
  */
 @Beta
-// FIXME: YANGTOOLS-1196: remove this namespace once we can bind freely
+// FIXME: We should not be needing this namespace, as yang-data's argument is not documented anywhere to be compatible
+//        with 'identifier', hence we cannot safely form a QName.
 public interface YangDataArgumentNamespace extends ParserNamespace<Empty, QName> {
     NamespaceBehaviour<Empty, QName, @NonNull YangDataArgumentNamespace> BEHAVIOUR =
         NamespaceBehaviour.statementLocal(YangDataArgumentNamespace.class);
index 1898e738d1f72fe5bda58572a2aef95b023f84e3..ff7b8d86b095b0e9dbe39760c0cd31488188f3b2 100644 (file)
@@ -7,12 +7,14 @@
  */
 package org.opendaylight.yangtools.rfc8040.parser;
 
+import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableList;
+import java.util.Map;
+import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
@@ -23,7 +25,10 @@ import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaNodeDefaults;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
 import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.UnknownEffectiveStatementBase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 import org.opendaylight.yangtools.yang.parser.spi.meta.SchemaPathSupport;
@@ -31,7 +36,6 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.SchemaPathSupport;
 @Beta
 final class YangDataEffectiveStatementImpl extends UnknownEffectiveStatementBase<String, YangDataStatement>
         implements YangDataEffectiveStatement, YangDataSchemaNode {
-
     private final @Nullable SchemaPath path;
     private final @NonNull QName argumentQName;
     private final @NonNull ContainerEffectiveStatement container;
@@ -46,7 +50,7 @@ final class YangDataEffectiveStatementImpl extends UnknownEffectiveStatementBase
 
         // TODO: this is strong binding of two API contracts. Unfortunately ContainerEffectiveStatement design is
         //       incomplete.
-        Verify.verify(container instanceof ContainerSchemaNode);
+        verify(container instanceof ContainerSchemaNode, "Incompatible container %s", container);
     }
 
     @Override
@@ -76,6 +80,18 @@ final class YangDataEffectiveStatementImpl extends UnknownEffectiveStatementBase
         return this;
     }
 
+    @Override
+    protected <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends Map<K, V>> getNamespaceContents(
+            final Class<N> namespace) {
+        if (SchemaTreeAwareEffectiveStatement.Namespace.class.equals(namespace)
+            || DataTreeAwareEffectiveStatement.Namespace.class.equals(namespace)) {
+            @SuppressWarnings("unchecked")
+            final Map<K, V> ns = (Map<K, V>)Map.of(container.argument(), container);
+            return Optional.of(ns);
+        }
+        return super.getNamespaceContents(namespace);
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this).omitNullValues()