This guide/tutorial will provide various examples around usage of
yang-js
library for working with YANG schemas and generated
Models. It will get better soon... :-)
Here's a collection of commonly used terms and their definitions within this module and other related modules:
- Schema: A descriptive resource that expresses the data hierarchy, constraints, and behavior of a data model
- Model: An instance of Schema evaluated with data
- Component: An implementation resource that associates control logic bindings to a Model
A handy convention is to define/save the generated Yang.eval function as a Composite Type definition and re-use for creating multiple adaptive schema objects:
# coffeescript
FooType = (Yang schema)
foo1 = (FooType) {
foo:
a: 'apple'
b: 10
}
foo2 = (FooType) {
foo:
a: 'banana'
b: 20
}
// javascript
var schema = Yang.parse('container foo { leaf a; }');
var model = schema.eval({ foo: { a: 'bar' } });
// try assigning a new arbitrary property
model.foo.b = 'hello';
console.log(model.foo.b);
// returns: 'hello' (without validation since not part of schema)
Here comes the magic:
// extend the previous container foo expression with an additional leaf
schema.extends('leaf b { type uint8; }')
model.foo.b = 'hello'; // throws error since not uint8 type
The extends mechanism provides
interesting programmatic approach to dynamically modify a given
Yang
expression over time on a running system.
module foo {
description "A Foo Example";
container bar {
leaf a {
type string;
}
leaf b {
type uint8;
}
}
}
Result of parse looks like:
{ kind: 'module',
tag: 'foo',
description:
{ kind: 'description',
tag: 'A Foo Example' },
container:
[ { kind: 'container',
tag: 'bar',
leaf: [Object] } ] }
When the above Yang
expression is converted
toJSON:
{ module:
{ foo:
{ description: 'A Foo Example',
container:
{ bar: { leaf: { a: [Object], b: [Object] } } } } } }
Below example demonstrates typical use:
# coffeescript
Yang = require 'yang-js'
schema = Yang.compose {
bar:
a: 'hello'
b: 123
}, tag: 'foo'
console.log schema.toString()
// javascript
const Yang = require('yang-js');
const schema = Yang.compose({
bar: {
a: 'hello',
b: 123,
}
}, { tag: 'foo' })
The output of [schema.toString()](./src/yang.litcoffee#tostring) looks
as follows:
container foo { container bar { leaf a { type string; } leaf b { type number; } } }
Please note that [compose](../src/yang.litcoffee#compose-data)
detected the top-level YANG construct to be a simple `container`
instead of a `module`. It will only auto-detect as a `module` if any
of the properties of the top-level object contains a `function` or the
passed-in object itself is a `named function` with additional
properties.
Below example will auto-detect as `module` since a simple `container`
cannot contain a *function* as one of its properties.
```coffeescript
# coffeescript
Yang = require 'yang-js'
obj =
bar:
a: 'hello'
b: 123
test: ->
schema = Yang.compose obj, { tag: 'foo' }
console.log schema.toString()
This is a very handy facility to dynamically discover YANG schema mapping for any arbitrary asset being used (even NPM modules) so that you can qualify/validate the target resource for schema compliance.
You can also override the detected YANG construct as follows:
Yang = require 'yang-js'
obj =
bar:
a: 'hello'
b: 123
schema = Yang.compose obj, { tag: 'foo', kind: 'module' }
console.log schema.toString()
When you manually alter the Yang
expression instance, it will
internally trigger a check for scope validation and reject if the the
change will render the current schema invalid. Basically, you can't
simply change a container
that contains other elements into a leaf
or any other arbitrary kind.
# coffeescript
Yang = require 'yang-js'
schema = """
module foo {
feature hello;
container bar {
leaf readonly {
config false;
type boolean;
}
}
rpc test;
}
"""
schema = Yang.parse(schema).bind {
'feature(hello)': -> true # provide some capability
'/foo:bar/readonly': -> true
'/test': -> 'success'
}
// javascript
const Yang = require('yang-js');
let schema = `
module foo {
feature hello;
container bar {
leaf readonly {
config false;
type boolean;
}
}
rpc test;
}
`
schema = Yang.parse(schema).bind({
'feature(hello)': () => true,
'/foo:bar/readonly': () => true,
'/test': () => 'success'
});
In the above example, a key/value
object was passed-in to the
bind method where the key
is a
string that will be mapped to a Yang Expression contained within the
expression being bound. It accepts XPATH-like expression which will be
used to locate the target expression within the schema. The value
of
the binding must be a JS function, otherwise it will be silently
ignored.
You can also bind a function directly to a given Yang Expression instance as follows:
Yang = require 'yang-js'
schema = Yang.parse('rpc test;').bind -> @output = "ok"
Please note that calling bind more than once on a given Yang expression will replace any prior binding.
You can utilize Yang.import to load the dependency module into the Yang compiler:
const Yang = require('yang-js');
Yang.import('/some/path/to/dependency.yang');
You can also use built-in require()
directly:
require('yang-js');
require('/some/path/to/dependency.yang');
The pre-load approach is iterative in that you would need to ensure dependency YANG modules are loaded in the proper order of the nested dependency chain.
When utilizing Yang.import
or register/require
, the
Yang compiler internally utilizes
Yang.resolve to attempt
to locate dependency modules automatically.
It first checks local package.json
to resolve the dependency via
External or
Internal definitions. If not found, it
will try to locate the some-dependency.yang
in the same directory
that the dependent schema is being required.
To utilize YANG modules bundled in an external package inside your own
app, you can add a section inside your local package.json
as
follows:
{
"models": {
"ietf-yang-types": "yang-js",
"ietf-inet-types": "yang-js",
"yang-store": "yang-js"
}
}
This will enable Yang.resolve
and Yang.import
to locate these
YANG modules from the yang-js
package.
To allow external packages to perform
Automatic Resolution of modules being
provided by your app, as well as for your own app to resolve local
dependencies, you can add a section inside your package.json
as
follows:
{
"models": {
"my-module": "./some/path/to/my-module.yang",
"my-bound-module": "./lib/my-bound-module.js"
}
}
Please note that you can reference a YANG schema file directly as well as a JavaScript file which exports a Yang schema instance. The second approach is useful for exporting bound schemas (see Schema Binding) which contains function bindings on the YANG schema.
# coffeescript
Yang = require 'yang-js'
schema = """
module foo {
list bar {
container obj {
leaf a { type string; }
leaf b { type uint8; }
}
}
}
"""
model = (Yang schema) {
'foo:bar': [
{ obj: { a: 'apple', b: 10 } }
{ obj: { a: 'orange, b: 20 } }
]
}
model.on 'update', (prop, prev) ->
# do something with 'prop'
The example above will register an event listener using
Model.on to trigger whenever the
data state of the model
is updated.
You can also utilize XPATH expressions to only listen for specific events occurring inside the data tree:
model.on 'update', '/foo:bar/obj/a', (prop, prev) ->
console.log "the property 'a' changed on one of the elements in the 'list bar'"
model.in('/foo:bar[0]/obj/a').set 'pineapple' # trigger event
model.in('/foo:bar[1]/obj/a').set 'grape' # trigger event