Introduction
This document is intended for developers / designers participating in design of model-derived Java Binding for MD-SAL, or for developers interested in understanding issues with current design and initial input / reasoning for follow-up design of next version.
|
Important
|
This is living document, which may be updated when new issues are raised, solutions proposed. |
Assumed Knowledge
-
Good understanding of YANG language
-
Good understanding of Java language, language syntax, semantics and rules.
-
Good understanding of common design patterns and their application in Java.
Knowledge of other JVM-based languages such as Groovy or Scala, and understanding how these languages rendered their specific concepts is also beneficial.
What is Java Binding Specification
-
Binding specification defines mapping of YANG modeled data to respective Java Objects, structures and DTOs
-
has compile time and runtime aspects, compile time aspects are mostly visible to users of MD-SAL
-
Currently used specification is Binding Specification v1
Requirements for Binding Specification v2
Interfaces & classes provided and generated by Binding Specification v2:
-
SHOULD model YANG semantics correctly
-
MUST NOT prevent support of YANG 1.1. See Appendix 1: YANG 1.1 List of changes
-
Provide / enforce maximal possible constrains during compile-time
-
This prevents bugs at compile time, more allows for assumptions at runtime and performance optimizations
-
-
Isolation - Code generation MUST BE isolated. Presence of additional model should not change generation of original model.
-
SHOULD allow for reusing instances of objects at various subtrees
Known issues of Binding Specification 1
Issues preventing correct model generation
Grouping, data, typedef and identity namespaces collide
This bugs shared common root in Binding Specification v1 and that was caused by not accounting for identifier namespace differences between Java and YANG.
-
Java has 1 namespace, where unique identifier is is Package name with Class name
-
YANG has 6 separate namespaces and allows for same name to be used in this separate namespaces.
- Module and submodule namespace
-
Each module and submodule name must be unique.
- Extension namespace
-
All extensions names defined in module and its submodules must be unique.
- Typedef namespace
-
All derived type names defined within a parent node or at the top level of the module or its submodules must be unique.
- Grouping namespace
-
All grouping names defined within a parent node or at the top level of the module or its submodules must be unique.
- Data namespace
-
All leafs, leaf-lists, lists, containers, choices, rpcs, notifications, and anyxmls defined (directly or through a uses statement) within a parent node or at the top level of the module must be unique.
- Identity namespace
-
All identity names defined in a module and its submodules share the same identity identifier namespace.
module example {
namespace "urn:example";
identity example {}
typedef example {type string;}
grouping example {}
container example {
container example {
leaf example {type example;}
}
}
}
Proposed solution
Use different packages names for identities, types, groupings and data tree items.
The format of package name is {gen-prefix}.{module-id}.{namespace-id}.{tree-id} where:
- gen-prefix
-
Constant prefix for all generated code in order to not conflict with hand-written code. Value is
org.opendaylight.mdsal.gen.v2 - module-id
-
Module name translated to package identifier. It is shorter than namespace, requires less substitutions and still is unique identifier of module, which can not change over time.
- namespace-id
-
One of YANG defined identifier namespaces:
-
ident- identity namespace, package for identities -
type- type namespace, package for types -
grp- grouping namespace, package for groupings -
data- package for all instantiated data tree nodes
-
- tree-id
-
Package identifier derived from
schema-node-identifierin order to separate namespace on each level of data tree.
-
If module name is
example-network-topologyunique identifier isexample.network.topology-
org.opendaylight.mdsal.gen.v2.urn.example.network.topology- module specific items -
org.opendaylight.mdsal.gen.v2.urn.example.network.topology.type- interfaces / classes representing derived types -
org.opendaylight.mdsal.gen.v2.urn.example.network.topology.grp- interfaces / classes representing grouping and their children -
org.opendaylight.mdsal.gen.v2.urn.example.network.topology.data- interfaces / classes representing notifications, rpcs, data tree
-
Methods introduced by Binding Specification conflicts with modeled items
Binding Specification v1 uses getter pattern for representing nested children
derived from YANG model. Name of child is converted to valid JAVA name and
prepended with is or get prefix.
Unfortunately Java & Binding Specification v1 also uses get prefix for some
methods.
container example {
list property {
key `key`;
leaf key { (1)
type string;
}
}
leaf implemented-interface { (2)
type string;
}
leaf class { (3)
type string;
}
}
-
Conflicts with
getKeyintroduced byIdentifiablewhich is used for lists with key -
Conflicts with
getImplementedInterfacedefined inDataContainerwhich is base interface of all generated lists, containers, cases, choices -
Conflicts with
getClassdefined inObjectwhich is root of all Java classes
Enumeration mapping is based on incorrect assumptions
Enumeration mapping was based on notion / idea that names of possibles values
are identifier as defined in RFC6020, but actual name is string.
It takes as an argument a string which is the assigned name. The string MUST NOT be zero-length and MUST NOT have any leading or trailing whitespace characters (any Unicode character with the "White_Space" property). The use of Unicode control codes SHOULD be avoided.
This mapping makes impossible to represent following model:
typedef math-operand {
type enumeration {
enum "+";
enum "/";
enum "*";
enum "-"";
}
}
4625: groupings should not share classes with their instantiations
Correct use of Java language
Inner class with same name as outer class is not allowed
Inner classes are used for generation of anonymous union, bit and enumeration types
defined in model.
In Java inner class MUST NOT have same name as outer class, which causes compilation error for following model:
grouping flags {
leaf flags {
type bits {
bit one;
bit two;
}
}
}
grouping status {
leaf status {
type enumeration {
enum open;
enum closed;
}
}
}
Incompatibilities with allowed model upgrade paths
Multiple augmentations of same target should result in one interface
Mappings incompatible with YANG 1.1
Identity mapping does not allow for identities with multiple bases
YANG 1.1
module example-crypto-base {
yang-version 1.1;
namespace "urn:example:crypto-base";
prefix `crypto`;
identity crypto-alg {
description
"Base identity from which all crypto algorithms
are derived.";
}
identity symmetric-key {
description
"Base identity used to identify symmetric-key crypto
algorithms.";
}
identity public-key {
description
"Base identity used to identify public-key crypto
algorithms.";
}
}
module example-des {
yang-version 1.1;
namespace "urn:example:des";
prefix `des`;
import `example-crypto-base` {
prefix `crypto`;
}
identity des {
base "crypto:crypto-alg";
base "crypto:symmetric-key";
description "DES crypto algorithm";
}
identity des3 {
base "crypto:crypto-alg";
base "crypto:symmetric-key";
description "Triple DES crypto algorithm";
}
}
Derived enumeration could limit valid values
Derived bits could limit valid values
Missing Mappings of YANG concepts
-
706: Missing support for
anyxml// Supplier<Source>
Usability issues
-
2872: Generated Java Enumerations should contain mapping to the string counter part
-
1870: Binding Specification: Type empty needs better representation than Boolean or Null vs NonNul
-
5673: Add "add"/"del" utility methods to builders.
-
5667: Incorrect use of format strings in generated code when backing type is an array (binding spec v2)
2641: Enumeration value defined in yang model is translated without underscore
Generate Equivalency for comparison of items by key and unique
ChildOf<> does not properly work with Choice / Case
Mapping of list and leaf-list does not properly captures modeled semantic
After analysis of specification, implementation of applications and
MD-SAL, we found out that list and leaf-list keyword actually has three different
behaviors based on combination of key and ordered-by statements.
In order to correctly expose this to Binding Applications, representation in parent node should be extended to facilitate this mapping should be changed.
| Key statement | Ordered-by | Behaviour | v2 Type |
|---|---|---|---|
key is defined |
system (default) |
Unordered map |
Map |
key is defined |
user |
Ordered map |
Map |
key is not defined |
— |
Ordered |
List |
| Ordered-by | Behaviour | v2 Type |
|---|---|---|
system (default) |
Unordered Distinct |
Set |
user |
Ordered Distinct |
Set |
1097:Return an empty list and never null from list-valued parameters
Leaf, leaf-list Instance Identifiers
Instance Identifier currently are constructed using classes as path arguments
-
is fine and allows for Instance Identifier to capture target type, but works only for container and list
Instance Identifier needs to be extended to allow targeting:
-
leaves
-
choice and case statements
Proposed solution
Introduce LeafPathArgument. LeafPathArguments for leafes will be stored in interface describing parent container as constants. This will allow for use such as:
InstanceIdentifier<Boolean> activePath = InstanceIdentifier.create(Foo.class).leaf(Foo.ACTIVE);
ListenableFuture<Optional<Boolean>> active = tx.read(CONFIGURATION,activePath);
This will require changing signature of MD-SAL to allow Object in its interfaces if we want to read boolean directly. Other approach is to have special DTO which implements DataObject and encapsulates LeafValue, this will allow MD-SAL to still limit input to DataObject.
InstanceIdentifier<LeafValue<Boolean>> activePath = InstanceIdentifier.create(Foo.class).leaf(Foo.ACTIVE);
ListenableFuture<Optional<LeafValue<Boolean>>> active = tx.read(CONFIGURATION,activePath);
Note: Use of Optional is property of MD-SAL and not of Binding Specification
Collections should be really immutable in immutable transfer objects
Performance issues
Other
-
1478: Autoboxing support
-
1095: Simplify InstanceIdentifer creation
-
1117: Improve RPC API error handling
-
1459: Reorganize yang-binding
-
2289: Binding codegen: RFC6020 defines the order of evaluation for union members
-
5668: Binding codegen: RFC6020 defines the order of evaluation for union members (binding spec v2)
Appendix 1: YANG 1.1 List of changes
|
Note
|
This is verbatim copy of Section 1.1 of YANG 1.1 Draft |
-
Changed the YANG version from "1" to "1.1".
-
Made the
yang-versionstatement mandatory. -
Made noncharacters illegal in the built-in type
string. -
Defined the legal characters in YANG modules.
-
Changed the rules for the interpretation of escaped characters in double quoted strings. This is an backwards incompatible change from YANG version 1. A module that uses a character sequence that is now illegal must change the string to match the new rules.
-
An unquoted string cannot contain any single or double quote characters. This is an backwards incompatible change from YANG version 1.
-
Extended the
if-featuresyntax to be a boolean expression over feature names. -
Allow
if-featureinbit,enum, andidentity. -
Allow
if-featureinrefine. -
Made
whenandif-featureillegal on list keys. -
Allow
choiceas a shorthand case statement. -
Added a new substatement
modifierto pattern. -
Allow
mustininput,output, andnotification. -
Allow
require-instanceinleafref. -
Allow
augmentto add conditionally mandatory nodes. -
Added a set of new XPath functions.
-
Clarified the XPath context’s tree.
-
Defined the string value of an identityref in XPath expressions.
-
Clarified what unprefixed names mean in leafrefs in typedefs.
-
Allow identities to be derived from multiple base identities.
-
Allow enumerations and bits to be subtyped.
-
Allow leaf-lists to have default values.
-
Allow non-unique values in non-configuration leaf-lists.
-
Use [RFC7405] syntax for case-sensitive strings in the grammar.
-
Changed the module advertisement mechanism.
-
Changed the scoping rules for definitions in submodules. A submodule can now reference all definitions in all submodules that belong to the same module, without using the
includestatement. -
Added a new statement
actionthat is used to define operations tied to data nodes. -
Allow notifications to be tied to data nodes.
-
Added a new data definition statement
anydata. -
Allow types
emptyandleafrefin unions. -
Allow type
emptyin akey.
Appendix 2: YANG 1.1 Updating a Module
|
Note
|
Italics text means section was added in YANG 1.1. This is verbatim copy of Section 11 of YANG 1.1 Draft |
As experience is gained with a module, it may be desirable to revise that module. However, changes to published modules are not allowed if they have any potential to cause interoperability problems between a client using an original specification and a server using an updated specification.
For any published change, a new revision statement (Section 7.1.9)
MUST be included in front of the existing revision statements. If
there are no existing revision statements, then one MUST be added
to identify the new revision. Furthermore, any necessary changes
MUST be applied to any meta-data statements, including the
organization and contact statements (Section 7.1.7,
Section 7.1.8).
Note that definitions contained in a module are available to be
imported by any other module, and are referenced in import
statements via the module name. Thus, a module name MUST NOT be
changed. Furthermore, the namespace statement MUST NOT be changed,
since all XML elements are qualified by the namespace.
Obsolete definitions MUST NOT be removed from published modules since their identifiers may still be referenced by other modules.
A definition in a published module may be revised in any of the following ways:
-
An
enumerationtype may have new enums added, provided the old enums’s values do not change. Note that inserting a new enum before an existing enum or reordering existing enums will result in new values for the existing enums, unless they have explicit values assigned to them. -
A
bitstype may have new bits added, provided the old bit positions do not change. Note that inserting a new bit before an existing bit or reordering existing bit will result in new positions for the existing bits, unless they have explicit positions assigned to them. -
A
range,length, orpatternstatement may expand the allowed value space. -
A
defaultstatement may be added to a leaf that does not have a default value (either directly or indirectly through its type). -
A
unitsstatement may be added. -
A
referencestatement may be added or updated. -
A
muststatement may be removed or its constraint relaxed. -
A
whenstatement may be removed or its constraint relaxed. -
A
mandatorystatement may be removed or changed fromtruetofalse. -
A
min-elementsstatement may be removed, or changed to require fewer elements. -
A
max-elementsstatement may be removed, or changed to allow more elements. -
A
descriptionstatement may be added or clarified without changing the semantics of the definition. -
A
basestatement may be added to anidentitystatement. -
A
basestatement may be removed from anidentityreftype, provided there is at least onebasestatement left. -
New typedefs, groupings, rpcs, notifications, extensions, features, and identities may be added.
-
New data definition statements may be added if they do not add mandatory nodes (Section 3) to existing nodes or at the top level in a module or submodule, or if they are conditionally dependent on a new feature (i.e., have an
if-featurestatement that refers to a new feature). -
A new
casestatement may be added. -
A node that represented state data may be changed to represent configuration, provided it is not mandatory (Section 3).
-
An
if-featurestatement may be removed, provided its node is not mandatory (Section 3). -
A
statusstatement may be added, or changed fromcurrenttodeprecatedorobsolete, or fromdeprecatedtoobsolete. -
A
typestatement may be replaced with anothertypestatement that does not change the syntax or semantics of the type. For example, an inline type definition may be replaced with a typedef, but an int8 type cannot be replaced by an int16, since the syntax would change. -
Any set of data definition nodes may be replaced with another set of syntactically and semantically equivalent nodes. For example, a set of leafs may be replaced by a uses of a grouping with the same leafs.
-
A module may be split into a set of submodules, or a submodule may be removed, provided the definitions in the module do not change in any other way than allowed here.
-
The
prefixstatement may be changed, provided all local uses of the prefix also are changed.
Otherwise, if the semantics of any previous definition are changed (i.e., if a non-editorial change is made to any definition other than those specifically allowed above), then this MUST be achieved by a new definition with a new identifier.
In statements that have any data definition statements as substatements, those data definition substatements MUST NOT be reordered. If new data definition statements are added, they can be added anywhere in the sequence of existing substatement.
Appendix 3: Refactoring YANG model example
Design of binding specification version 2 in case of refactoring initial YANG model:
Example 1a, 1b:
module foo1a {
namespace "urn:test:foo1a";
prefix f1a;
revision 2016-01-01 {
description "Initial YANG model";
}
container a {
container b {
container c {
}
}
}
}
module foo1b {
namespace "urn:test:foo1b";
prefix f1b;
revision 2016-01-01 {
description "First refactor only augment";
}
container a {
}
augment "/a" {
container b {
}
}
augment "/a/b" {
container c {
}
}
}
Both previous modules foo1a & foo1b generate following instantiated Java structure:
getB getC data.A -> data.a.B -> data.a.b.C
as augments become "invisible" in this one module context.
Example 2a:
module foo2a {
namespace "urn:test:foo2a";
prefix f2b;
revision 2016-01-01 {
description "Second refactor one grouping";
}
grouping a {
container b {
container c {
}
}
}
container a {
uses a;
}
}
In module foo2a, one grouping is added:
grp.A -> grp.a.B -> grp.a.bC | getB | getC | data.A -> data.a.B -> data.a.b.C
Example 2b:
module foo2b {
namespace "urn:test:foo2b";
prefix f2;
revision 2016-01-01 {
description "Third refactor grouping augment";
}
grouping a {
container b {
}
}
container a {
uses a {
augment b {
container c {
}
}
}
}
}
In module foo2b, one grouping and one augment is added:
grp.A -> grp.a.B | getB | getC data.A -> data.a.B -> data.a.b.C
module foo3 {
namespace "urn:test:foo3";
prefix f3;
revision 2016-01-01 {
description "Fourth refactor groupings only";
}
grouping a {
container b {
uses b;
}
}
grouping b {
container c {
}
}
container a {
uses a;
}
}
grp B -> grp b.C
getB | getC |
grp.A -> grp.a.B -> grp a.b.C
| getB | getC |
data.A -> data.a.B -> data.a.b.C
-
pros vs. binding spec v1:
-
well covered relations between elements
-
classes with same name in different packages (partially solves binding spec. v1 issue)
-
-
cons vs. binding spec v1:
-
higher amount of classes
-
higher memory consumption
-
amount of classes with same name (will be tackled by aliases)
-
Appendix 4: Augmenting YANG model example
Design of binding specification version 2 in case of augment:
-
one YANG model
module foo1a {
namespace "urn:test:foo1a";
prefix f1a;
revision 2016-01-01 {
description "Default code";
}
container a {
container b {
container c {
}
}
container bar {
}
}
}
or
module foo1b {
namespace "urn:test:foo1b";
prefix f1b;
revision 2016-01-01 {
description "First refactor, this code should look the same as the default code due
to the fact that these augments are in the same module";
}
container a {
}
augment a {
container b {
}
}
augment a {
container bar {
}
}
augment "/a/b" {
container c {
}
}
}
Both previous modules foo1a & foo1b generate following instantiated Java structure:
A -> a.B -> a.b.C -> a.Bar
-
multiple YANG models
module foo2 {
namespace "urn:test:foo2";
prefix f2;
revision 2016-01-01 {
description "Augments of the same element should be put together";
}
import foo1a {
prefix f1;
revision-date 2016-01-01;
}
augment "/f1:a" {
container from-b {
}
}
augment "/f1:a/f1:b" {
container from-b-1 {
}
}
augment "/f1:a/f1:b" {
container from-b-2 {
}
}
}
Previous module foo2 (alias "b") and foo1a (alias a) generates following instantiated java structure:
A -> a.B -> a.b.C
-> b.BB -> FromB1
-> FromB2
-> a.Bar
-> b.BA -> b.ba.FromB
Appendix 5: DTOs & builders
DTO and builders needs to be in different packages
container foo { class fooBuilder
}
container foo-builder { interface fooBuilder
}
list foo { data.Foo
key identifier; key.foo.FooIdentifier
leaf identifier {
type union { type.foo.identifier.IdentifierUnion
type string;
}
}
}
container foo-identifier { data.FooIdentifier
}
typedef foo-identifier { type.FooIdentifier
}
grouping nodes {
list node { for grouping key.grp.nodes.node.nodeidentifier
key id;
leaf id {
type leafref;
}
}
}
container nodes {
uses nodes; for instantiated key.data.nodes.node.nodeidentifier
}
Appendix 6: Various YANG model snippets & mappings
grouping A { -> grp.AGrouping
container B -> grp.a.BData
container B-DATA -> grp.a.BDataData
}
container A { -> data.A extends AGrouping
uses A; -> data.a.B extends BData
data.a.BData extends BDataData
}
grouping a { -> interface grp.AGrouping {
list b; List<? extends grp.a.BData>();
} }
container a { -> interface data.A extends AGrouping {
uses a; List<data.a.B> getB();
} }