[TOC]
- 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
- was developed and introduced in Opendaylight Hydrogen release
- small backwards compatible enhancements in Opendaylight Helium, mostly done for increasing performance of runtime
- SHOULD model YANG semantics correctly and provide / enforce compile-time safety
- This prevents bugs at compile time, more allows for assumptions at runtime and performance optimizations
- Code generation should be isolated. Presence of additional model should not change generation of original model.
- SHOULD allow for reusing instances of objects at various subtrees
- All YANG structural statements are represented in Java as interfaces describing DTOs
- Derived value types (typedef) are represented by:
- Immutable Value object for most simple YANG types
- Java enum for type enumeration
- Two major implementations of these DTO interfaces exists:
- Immutable DTO which is created by user using compile-time generated DTO Builders
- LazyDataObject Proxy - Immutable Proxy implementation which lazily translates from NormalizedNode representation of data. Only data explicitly accessed by user are translated.
augment and choice models mixin concept, where third-party models could introduce additional properties to existing structures. But their semantics are really different:
- choice - node could have only one case is allowed (0-1)
- augment - node could have multiple different augmentations present (0-n)
For modeling choice to case relationship we choosed inheritance because signature of setter / getter clearly says there could be zero or one instance.
interface ExampleChoice {}
interface ExampleCase1 extends ExampleChoice { ... }
interface ExampleCase2 extends ExampleChoice { ... }
ExampleChoice getExampleChoice();
...
void setExampleChoice(ExampleChoice choice);
As you could see signature clearly says there could be only one instance of ExampleChoice set for parent object. This captures YANG semantics clearly.
When designing mapping of augmentations you need to start thinking of conflicts and compilation isolation.
The requirements for augmenation mapping to properly capture YANG semantics should be always done at least for three modules (to simulate third-party extensions which are developed separatelly).
Also note that YANG allows for augmentations from different models use same identifiers for leaves and containers.
Based on this properties we could have following three models:
// Standards model
module switch {
namespace "urn:example";
container switch {
}
}
// Openflow protocol model
module openflow-example {
namespace "urn:example:openflow";
import "switch" {prefix s;}
augment "/s:switch" {
ext:augment-identifier openflow-switch;
leaf id { type int64;}
}
}
// Foo protocol model
module foo-example {
namespace "urn:example:foo";
import "switch" {prefix s;}
augment "/s:switch" {
ext:augment-identifier foo-switch;
leaf id { type int32;}
}
}
So we should have three interfaces describing DTO from each model. We still did not decide composition over inheritance (augmentations implements target interface).
interface Switch extends DataObject {}
interface OpenflowSwitch extends DataObject {
Long getId();
}
interface FooSwitch extends DataObject {
Integer getId();
}
- Lets assume, there is device which support both data models, so it should have two augmentations present.
- Object representing Switch should implement interfaces Switch, OpenflowSwitch and FooSwitch
- Problem is there is conflicting signature for getId method, which prevents any Object implements OpenflowSwitch and FooSwitch interfaces at same time.
- In this simple case (if we do not assume conflict) this would result in four different implementations of interface needed, since we need to account for all possible combinations.
-
Lets assume, there is device which support both data models, so it should have two augmentations present.
-
For composition we opted to use ExtensibleObject pattern, which usually have signature
<T> T getExtension(Class<T> ext);
-
Switch interface defines method
<T> T getAugmentation(Class<T> ext);
, which returns augmentation if present.-
This allows for only having implementation of Switch interface, which could be used for all possible combinations of augmentations, even if they are using same leaf names.
class SwitchImpl implements Switch { Map<Class<?>> augmentations;
<T> T getAugmentation(Class<T> aug) { return (T) augmentations.get(aug); } }
-
This simple analysis lead us to rule out inheritance between augmentation and augmented objects, since augmentations could be declared issolated and could introduce conflicting return type signatures which is disallowed in Java.
These problems could not be addressed without change of API generation and requires new revision of Java Binding Specification.
- Java / YANG Namespace collision bugs
- Bug 138: Grouping and RPC namespaces collide
- Bug 157: Naming conflict appears when key of list is constructed from leaf with name key
- Bug 476: Classes should be generated for typedef and grouping of same name
- Missing mappings of YANG concepts
- Bug 706: Missing support for anyxml statement in java generator and mapping service
- Bug 1313: No API exists to get created data from onDataChanged() after sequential PUTs
- Bug 1434: Make arrays non-mutable
- Instance Identifier problems
- Bug 1466: Instance Identifier is unable to represent Choice / Case, only children
- Bug 1644: InstanceIdentifier bug
- Better Mapping to Java Types
This bugs shared common root in Binding Specification v1 and that was caused by not accounting for identifeir namespace differences between Java and YANG.
- Java has identifier namespace which. Unique identifier in this namespace is Package Name with Class name
- YANG has 5 namespaces
- Module and submodule namespace (each module and submodule name must be unique)
- Extension namespace (extension name must be unique in module context)
- Type namespace (two types must not share same name in module scope)
- Grouping namepspace
- Data tree, rpc and notification namespace
This effectively means that following YANG model is valid:
module example {
namespace "urn:example";
extension example {}
typedef example {type string;}
grouping example {}
container example {
container example {
leaf example {type example;};
}
}
}
-
Use different packages names for types, groupings and data tree items.
-
If module package name is urn.example:
- urn.example
- urn.example.type
- urn.example.grouping
- urn.example.data
- urn.example.rpc
- urn.example.notify
-
Extension namespace collision is currently non-issue since extensions are used in schema and not in data
Current Binding Specification v1 does not provide following mappings:
- anyxml statement
- Instance Identifier which reference leaf node, leaf-list node or list node, which does not have key defined.
Anyxml definition in RFC6020:
The "anyxml" statement is used to represent an unknown chunk of XML. No restrictions are placed on the XML. This can be useful, for example, in RPC replies.
This effectivelly means that anyxml node could host valid XML document or just string.
Interpretation of anyxml nodes is actually left out to the text of specification and we identified several patterns:
- Free form text or XML document in RFC6022: YANG Module for NETCONF Monitoring
- XML conforming to data tree schema in RFC6241: Network Configuration Protocol (NETCONF)
Natural candidates for anyxml representations are:
- org.w3c.dom.Element
- javax.xml.transform.Source
- Own version of Source, which will also provide YANG specific constructs: eg. NormalizedNodeSource, BindingSource)
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
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
- Enhancements:
- Bug 1478: Autoboxing support
- Bug 1485: BigInteger being used in range checks when a simple type would be sufficient
- Bug 1117: Improve RPC API error handling