This page documents the existing Yang Parser used in ONAP and OpenDayLight and will investigate if it fulfill the needs of the C&PS.
The Yang parser used in ONAP (CCSDK / SDNC) was developed (and still is) a OpenDayLight Library.
There are 2 different usage patterns within CCSDK, though.
Most of CCSDK/SDNC is using Yang primarily to define their northbound interfaces. In that context, there’s a maven plugin (org.opendaylight.yangtools:yang-maven-plugin) that is used to generate source code from the Yang model. Our application code in that case doesn’t really do anything directly with the Yang, since all of the that is handled for us by the generated code.
In ccsdk/sli/plugins, there is a plugin called restconf-client which was contributed by Huawei. That code uses the yangtool parser more directly so that it can interpret the results being returned when it calls a restconf interface.
Additional Resources (still to be examined)
*Although this documentation link is to the latest ODL doc revision, it is very outdated and the code examples need significant updates, see findings in Mini-PoC below (bug reported: https://jira.opendaylight.org/browse/DOCS-126)
To help this evaluation I will create a small sample project with the goal to pare a yang file using the ODL Yang Tools. I will report my findings here
The documentation mentioned above lists many modules but the code examples do not clarify which exactly are needed to parse a Yang model and Yang data files in java code.
<properties> <maven.compiler.version>3.8.1</maven.compiler.version> <maven.compiler.release>11</maven.compiler.release> <odl.yangtools.version>5.0.5</odl.yangtools.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven.compiler.version}</version> <configuration> <release>${maven.compiler.release}</release> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.opendaylight.yangtools</groupId> <artifactId>yang-parser-api</artifactId> <version>${odl.yangtools.version}</version> </dependency> <dependency> <groupId>org.opendaylight.yangtools</groupId> <artifactId>yang-parser-impl</artifactId> <version>${odl.yangtools.version}</version> </dependency> <dependency> <groupId>org.opendaylight.yangtools</groupId> <artifactId>yang-model-util</artifactId> <version>${odl.yangtools.version}</version> </dependency> <dependency> <groupId>org.opendaylight.yangtools</groupId> <artifactId>yang-data-codec-xml</artifactId> <version>${odl.yangtools.version}</version> </dependency> <dependency> <groupId>org.opendaylight.yangtools</groupId> <artifactId>yang-data-codec-gson</artifactId> <version>${odl.yangtools.version}</version> </dependency> <!-- SLF4J API --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> </dependency> <!-- LOG4J --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> </dependencies> |
Note. SLF4J has also been added as the implementation requires an enabled logger
The sample code provided in the documentation is faulty (using == for assigning?!) and is using some long deprecated and even removed classes and methods.
Bug reported: https://jira.opendaylight.org/browse/DOCS-126
static YangParserFactory PARSER_FACTORY; static { final Iterator<YangParserFactory> it = ServiceLoader.load(YangParserFactory.class).iterator(); if (!it.hasNext()) { throw new IllegalStateException("No YangParserFactory found"); } PARSER_FACTORY = it.next(); } ... File file = new File(ClassLoader.getSystemClassLoader().getResource("example.yang").getFile()); YangTextSchemaSource source = YangTextSchemaSource.forFile(file); final YangParser yangParser = PARSER_FACTORY.createParser(StatementParserMode.DEFAULT_MODE); yangParser.addSource(source); SchemaContext schemaContext = yangParser.buildEffectiveModel(); schemaContext.getModules() |
This is the kind of object (module) that gets created:
The SchemaContext
object generated by the Yang Parser in java has the following possible structures (java collections)
SchemaTree
is implemented as a ImmutableMap<QName, SchemaTreeEffectiveStatement<?>>
The SchemaTree
can represent a tree of any Yang Leaf, List or Container. All leafs implement the LeafStatement
interface which provides methods for many Yang Language features including
getType()
getUnits()
getMandatory()
getWhenStatement()
getMustStatments()
The package org.opendaylight.yangtools.yang.model.util.type
contains classes for all the possible data types including:
There are also some special data types such as:
And also 'restricted' versions of the base types such as:
Description | Yang | Java Object View | Notes | XML Validation | JSON |
---|---|---|---|---|---|
Datatypes and basic constraints | |||||
Basic String |
| TypeStatement TypeAwareDeclaredStatement.getType() | Yes | Yes | |
Mandatory Basic String |
|
| No | No | |
Limited String | leaf pnf-name { type string { length "0..256"; } |
| Yes | Yes | |
typedef (String) with pattern |
| List<PatternConstraint> RestrictedStringType.getPatternConstraints() | Yes | Yes | |
Limited uint64 | leaf cid { type uint64 { range "0..503"; } } | org.opendaylight.yangtools.yang.model.util.type.RestrictedUint64Type | Yes | Yes | |
boolean with default value |
| org.opendaylight.yangtools.yang.model.util.type.DerivedBooleanType | N/A | N/A | |
Unique | |||||
Unique | list server { key "name"; unique "ip port"; leaf name { type string; } leaf ip { type dotted-quad; } leaf port { type uint32; } } | org.opendaylight.yangtools.yang.model.api.stmt.UniqueStatement | No | No | |
Choice | |||||
Choice | choice transfer-method { leaf transfer-interval { type uint64 { range "15..2880"; } units minutes; } leaf transfer-on-commit { type empty; } } | org.opendaylight.yangtools.yang.model.api.stmt.ChoiceStatement | N/A | N/A | |
Must | |||||
Must | leaf ifType { type enumeration { enum ethernet; enum atm; } } leaf ifMTU { type uint32; } must "ifType != 'ethernet' or " + "(ifType = 'ethernet' and ifMTU = 1500)" error-message 466px"An ethernet MTU must be 1500"; } | org.opendaylight.yangtools.yang.model.api.stmt.ErrorMessageStatement | No | No | |
When | |||||
When | leaf a { type boolean; } leaf b { type string; when "../a = 'true'"; } | org.opendaylight.yangtools.yang.model.api.stmt.WhenStatement | No | No | |
Extension | |||||
Extension declaration |
| N/A | |||
Extension usage | leaf attribute-with-temporal-storage { type string; cm-notify-api:store-state-ext "3d"; } |
which extends public interface UnknownStatement<A> extends DeclaredStatement<A> { | N/A | ||
Augmentation | |||||
augment "server" { when "port = '8787'"; leaf enable-debug { type boolean; } } | The additional leaf just appears in the SchemaTree (without any reference that it is an augmentation) | N/A | |||
RPC | |||||
rpc | rpc nbrlist-change-notification { | N/A | |||
rpc input | input { in a neighbor list for this fap service"; } | N/A | |||
rpc output | output { | N/A |
SchemaContext schemaContext = ... (see previous snippets) final Module module = schemaContext.findModules("ultraconfig-interfaces").iterator().next(); QName qName = QName.create(module.getQNameModule(),"interfaces"); final Optional<DataSchemaNode> node = module.findDataChildByName(qName); if (node.isPresent()) { final InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("example2data.xml"); final XMLStreamReader reader = UntrustedXML.createXMLStreamReader(resourceAsStream); final NormalizedNodeResult result = new NormalizedNodeResult(); final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result); final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext, node.get() ); xmlParser.parse(reader); final NormalizedNode<?, ?> transformedInput = result.getResult(); } |
*Note: the DataSchemaNode being used when creating the XmlParserStream HAS to be the root node of the xml data!
The table in the sections above has a column with the XML validation findings.
SchemaContext schemaContext = ... (see previous snippets) JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(schemaContext); final NormalizedNodeResult result = new NormalizedNodeResult(); final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result); final JsonParserStream jsonParser = JsonParserStream.create(streamWriter, jsonCodecFactory); final InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("example2data.json"); final JsonReader jsonReader = new JsonReader(new InputStreamReader(resourceAsStream)); jsonParser.parse(jsonReader); final NormalizedNode<?, ?> transformedInput = result.getResult(); |
Pros
Cons