Skip to content

Latest commit

 

History

History
392 lines (326 loc) · 9.62 KB

TUTORIAL.md

File metadata and controls

392 lines (326 loc) · 9.62 KB

Getting Started with yang-js

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... :-)

Table of Contents

Terminology

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

Working with Yang Schema

Composite Type

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
}

Schema Extension

// 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.

Schema Conversion

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] } } } } } }

Schema Composition

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.

Schema Binding

# 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.

Working with Multiple Schemas

Preload Dependency

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.

Automatic Resolution

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.

External Package Bundle

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.

Interal Package Bundle

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.

Working with Models

Model Events

# 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