This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Reference

Complete technical documentation in detail.

1 - YANG primer

Quickstart on the most used parts of the management modeling language.

YANG is a language utilized for modeling the management functions of applications. The primary function of YANG files are to enable remote tools to comprehend a server’s management functions, enable servers to accurately provide defined management functions, and generate documentation for individuals seeking to understand a server’s management functions. As a software engineer who implements management functions, it is crucial to understand how to create YANG files. Although there are numerous books on the subject, this document will provide a high-level understanding. It is recommended to consult the YANG RFC for a comprehensive specification of the language.

If you are familiar with other interface definition languages such as gRPC or OpenAPI, you will find several similarities with YANG. However, YANG extends beyond these languages to account for management-specific aspects.

It is essential to note that YANG is not limited to RESTCONF and can be employed with other communication protocols, such as NETCONF or custom protocols.

If you familiar with YANF and RESTCONF already, some these responses might look different. See Compliance on how to control this.

Data Definitions

module

Every YANG file starts module {} statement. All further definitions are contained inside the {} brackets.

module car {
  prefix "c";
  namespace "yourcompany.com";
  revision 2023-03-11;

  // all further definitions here
}

There is always just a single module in a YANG file.

RFC reference

leaf

module car {
  leaf driver {
    type string;
  }
}

If a server used this YANG file, then possible response for requesting data via curl might be:

curl https://server/restconf/data/car:

{
 "driver": "joe"
}

While the base URL of /restconf/data/ is not strictly neccessary, it is pretty standard for all RESTCONF servers.

RFC reference

all possible leaf types

NameDescription
binaryAny binary data
bitsA set of bits or flags
boolean“true” or “false”
decimal6464-bit signed decimal number
emptyA leaf that does not have any value
enumerationOne of an enumerated set of strings
identityrefA reference to an abstract identity
instance-identifierA reference to a data tree node
int88-bit signed integer
int1616-bit signed integer
int3232-bit signed integer
int6464-bit signed integer
leafrefA reference to a leaf instance
stringA character string
uint88-bit unsigned integer
uint1616-bit unsigned integer
uint3232-bit unsigned integer
uint6464-bit unsigned integer
unionChoice of member types

RFC reference

container

module car {
  container engine {}
}

Possible request/responses:

curl https://server/restconf/data/car:

{
 "engine": {}
}

RFC reference

list

module car {
  list cylinders {
    leaf firingOrder {
      type int32;
    }
  }
}

Possible request/responses:

curl https://server/restconf/data/car:

{
  "cylinders":[
    {
      "firingOrder": 1
    }
    {
      "firingOrder": 4
    }
    {
      "firingOrder": 3
    }
    {
      "firingOrder": 2
    }
  ]
}

Most list will have a key when referencing a particular item in the list.

module car {
  list cylinders {
    key num;
    leaf num {
      type int32;
    }
    leaf firingOrder {
      type int32;
    }
  }
}
curl https://server/restconf/data/car:

{
  "cylinders":[
    {
      "num": 1,
      "firingOrder": 1
    }
    {
      "num": 2,
      "firingOrder": 4
    }
    {
      "num": 3,
      "firingOrder": 3
    }
    {
      "num": 4,
      "firingOrder": 2
    }
  ]
}

curl https://server/restconf/data/car:cylinders=1

{
  "num": 1,
  "firingOrder": 1
}

RFC reference

container/leaf

module car {
  leaf driver {
    type string;
  }
  container engine {
    leaf cylinders {
      type int32;
    }
  }
}

Possible request/responses:

curl https://server/restconf/data/car:

{
 "driver": "joe"
 "engine": {
  "cylinders": 6
 }
}

RFC reference

leaf-list

module car {
  leaf-list owners {
    type string;
  }
}

Possible request/responses:

curl https://server/restconf/data/car:

{
 "owners": ["joe","mary"]
}

leaf-lists can have all the same types as leaf, only it would container multiples of said type.

RFC reference

rpc

module car {
  rpc start {}
}

Possible request/responses:

curl -X POST https://server/restconf/data/car:start

# no response but car should start otherwise you'd get an error

RFC reference

rpc/input/output

module car {
  rpc drive {
    input {
      leaf throttle {
        type int32;
      }
    }
    output {
      leaf acceleration {
        type int32;
      }
    }
  }
}

Possible request/responses:

curl -X POST -d '{"throttle":32}' https://server/restconf/data/car:drive

{
  "acceleration": 30
}

RFC reference

container/action/input/output

For historical reasons, action is exactly like rpc except rpcs are only allowed inside module and action is used everywhere else.

module car {
  
  rpc drive {} // correct

  container engine {
    action start {} // correct
  }
}
module car {
  
  action drive {} // INCORRECT, only rpc here
  
  container engine {
    rpc start {} // INCORRECT, only action here
  }
}

RFC reference

notification

module car {
  notification flatTire {}   
}

Possible request/responses:

curl https://server/restconf/data/car:flatTire

data: {"notificaton":{"eventTime":"2013-12-21T00:01:00Z"}}

RFC reference

notification/leaf

module car {
  notification flatTire {
    leaf tireCount {
      type int32;
    }
  }   
}

Possible request/responses:

curl https://server/restconf/data/car:flatTire

data: {"notificaton":{"eventTime":"2013-12-21T00:01:00Z","event":{"tireCount":1}}}

RFC reference

anydata

module car {
  anydata glovebox;
}

Possible request/responses:

curl https://server/restconf/data/car:

{
  "glovebox" {
    "papers" : ["registration", "manual"],
    "napkinCount" : 30
  }
}

RFC reference

Organizational

group/uses

Grouping is simply a way to reuse a set of definitions

This YANG

module car {
  
  leaf driver {
    type string;
  }

  uses engineDef;

  grouping engineDef {
    container engine {
      leaf cylinders {
        type int32;
      }
    } 
  }
}

is equivalent to this YANG:

module car {
  
  leaf driver {
    type string;
  }

  container engine {
    leaf cylinders {
      type int32;
    }
  } 
}

RFC reference

choice/case

When you want to ensure there is just one of a number of definitions. If you are familiar with gRPC, this is like oneof. Some languages call this union:

module {
  choice nameDoesntMatter {
    leaf a {
      type string;
    }
    leaf b {
      type string;
    }
    leaf c {
      type string;
    }
  }
}

This means if a exists, then b and c cannot.

This you have multiple items in the option, you can wrap them with a case statement.

module {
  choice nameDoesntMatter {
    case nameAlsoDoesntMatter1 {
      leaf a {
        type string;
      }
      leaf aa {
        type string;
      }
    }
    leaf b {
      type string;
    }
    leaf c {
      type string;
    }
  }
}

This means if a and/or aa exist, then b and c cannot.

RFC reference

typedef

typedef are simply a way to reuse a leaf type

This YANG

module {
  leaf driver {
    type string;
  }
}

is equivalent to this YANG

module {
  
  typedef person {
    type string;
  }

  leaf driver {
    type person;
  }
}

This would be more useful then type has more definitions associated with it and more opportunities to reuse the typedef.

RFC reference

Metrics

Metrics are just definitions that are marked config false.

module {

  leaf speed {
    config false;  // <-- Metric HERE
    type int32;
  }

  leaf color {
    type string;
  }
}
module {

  container stats {
    config false;  // <-- children of a containers are metrics too
    leaf count {
      type int32;
    }
    leaf rate {
      type int32;
    }
  }
}

RFC reference

Constraints

There are a lot more types of contraints, but here are a few;

number types

module car {
  leaf cylinders {
    type int32 {
      range "1..12";
    }
  }
}

You can have any number of range items that you need.

RFC reference

string types

module car {
  leaf color {
    type string {
      pattern "[a-z]*"; // lowercase
    }
  }
}

You can have any number of pattern items that you need.

RFC reference

Extensions

You can customize YANG files with data that is specific to your application. Extensions will be ignored by any systems that do not support your customizations.

module car {
  prefix my;

  extension secret;

  leaf owner {
    type string;
    my:secret;
  }
}

extensions have have any number of arguments:

module car {
  prefix my;

  extension secret {
    argument "vault";
  }

  leaf owner {
    type string;
    my:secret "safe";
  }

  leaf history {
    type string;
    my:secret "jerry";
  }
}

RFC reference

More

Even this is not an exhaustive, but still more useful contructs:

  • import - pull in select YANG from another file. RFC reference
  • include - pull in all YANG from another file. RFC reference
  • default - value to use for leafs when no value is supplied. RFC reference
  • augment - used with grouping/uses to add definitions to a grouping. RFC reference
  • refine - also used with uses to alter specific definition including adding constraints. RFC reference
  • leafref - a reference to another leaf’s data that must exist. Kinda like a foreign key RFC reference
  • when - data definitions that only exist when certain data is true. RFC reference
  • must - a constraint that is tied to other leaf’s data. RFC reference
  • identity - system wide enum that can have heirarchies. RFC reference
  • feature - denote parts of the YANG that are only valid if a feeature it on. RFC reference
  • revision - track versions of your YANG. RFC reference
  • error-message - control the error messaging. RFC reference
  • ordered-by - control the order of lists. RFC reference

Example YANG files

  • toaster.yang - Used as the “hello world” or “TODOs” application for YANG. It demonstrates common YANG items.
  • fc-restconf.yang - If you use FreeCONF, this is the configuration of the RESTCONF service you’d be using to handle your RESTCONF interface.
  • ietf-inet-types.yang - IETF types are useful to import as common set of field types/

2 - Basic components

Six basic components when developing with FreeCONF.

When using the FreeCONF developer API you’ll likely be interfacing with these six FreeCONF components:

  1. Module - the parsed YANG model (i.e. AST). From this single meta object, you can reach the meta definitions for every single item in the YANG file including custom extensions.
  2. Node - custom code you write that connects the definitions in Module to your application. As your management API is called, Nodes are responsible for returning more Nodes to navigate your application. Nodes are also responsible for handling each configuration field, each metric, each notification and each function. You can use a variety of methods to implement your Nodes from:
    • writing each by hand
    • reflection
    • generated from code
    • library of base implementations you have developed
    • nodes that read or write from/to JSON, YAML, XML
    • nodes that read and write from/to a database
    • nodes that proxy to other nodes including remote ones to create a client
    • nodes that extend other nodes
    • yet to be developed
    • any combination of the above
  3. Selection - a pairing of a node and a meta definition that lets you navigate and operate on your application’s live objects.
  4. Browser - Where to get the first Selection object known as the root Selection.
  5. Device - Holds a set of Browsers that you elect to make available in your management API.
  6. Server - Handles the communication in and out of your Device. It understands the protocol and responsible for interoperability with clients communicating with your Device.
classDiagram
  Selection o--> Node
  Selection o--> Meta
  Device *-->  "1..*" Browser
  Server *--> Device
  Browser *--> Module
  Browser --> Selection : << creates >>
  Selection --> Selection : << creates >>
  Browser *--> Node
  Module *--> "1..*" Meta
  Node --> Node : << creates >>
  class Device{    
  }
  class Selection{
  }
  class Module{
  }
  class Meta{    
  }
  class Node{
  }
  class Server{    
  }

3 - Using RESTCONF

Interfacing with a RESTCONF API as a tool, CLI or script

This is about consuming a RESTCONF API, not building one. In short, if you know REST then you know RESTCONF. There are some really cool features however that will find useful and a few conventions that would be good to learn.

Adjustable data granularity

Traditional REST APIs struggle with what level of information to return for each GET request and this is called granularity. Return too little data and scripts will need to make constant trips for more. Return too much data and majority of it will likely be unused. Both situations cause delays and additional resource consumption.

RESTCONF APIs do not have this issue. Clients can precisely control the data they want from depth, to list pagination. From selecting fields to excluding fields.

With APIs developed with FreeCONF, each implemention has the control to only read the data that was requested. That is, granularity is not implemented as a reponse filters like GraphQL after the data has already been extracted.

Methods

No surprises here:

  • GET - Getting configuration, metrics and notifications. Notificatons are in SSE format detailed later in this document.
  • PATCH - Updating configuration. If objects are not found, they will be created so this is really an upsert.
  • PUT - Replacing configuration. If objects are found, they will removed first then recreated with the given configuration.
  • POST - Creating read-write data (e.g. config). If objects are not found, you will get an error.
  • DELETE - Deleting read-write data. If objects are not found, you will not get an error.
  • OPTIONS - Useful to test if user has access to certain data path

URL

Basic for is this : /restconf/data/{module}:{path}[?params]

  • module - name of the yang file So car.yang would be served at /restconf/data/car:
  • path - drill down into the objects. So to access the car tires, would be /restconf/data/car:tires. Only tricky part is drilling into lists. If you wanted to drill into front-left tire, you might use /restconf/data/car:tires=front-left. Drilling deeper still might be /restconf/data/car:tires=front-left/vendor
  • ?params - For GET methods only, many params will help you limit the amount of data you return to save bandwidth and compute resources. More detailed information can be found in the specification but here is a quick summary:
    • depth=N - limits the level of data returns to N levels in the hierarchy
    • content=config - return only configuration data
    • content=nonconfig - return only metric data
    • with-defaults=trim - Do not return leaf values if they match the default value. This is useful for determining what a configuration user may have actually changed versus what configuration a device is actually using.
    • fields=a;b/c - returns only select data paths. Note: you’ll need to encode parameters depending on your http client libraries. For example this would be fields=a%3db/c

Custom URL params

FreeCONF adds a few extra, useful parameters when retrieving data

  • fc.xfields=a;b/c - inverse of fields in that it returns all fields except specified. Again watch the encoding of the ; as detailed above.
  • fc.range=b/c!N-M - returns rows N thru M inclusive in list b/c
  • fc.max-node-count=N - increases or decreases the maximum allowed data to be returned. There are limits by default to ensure unbounded requests do not bog down system.

Examples

See specification for more details on how RESTCONF maps to REST.

TaskMethodPathDescription
ReadGET/restconf/data/car:Get’s all data (configuration and metrics) for car module
ReadGET/restconf/data/car:tireGet’s all data for all tires
ReadGET/restconf/data/car:tire=1Get’s all data for first car tire. Yes, seeing an equals in a URL can be disconcerting, but it is legal.
UpdatePATCH/restconf/data/car:cruise
body:{"desiredSpeed":65}
Set cruise control desired speed
ReadGET/restconf/data/car:tire?c2-range=!1-2Get’s all data for car tires 1 and 2
ReadGET/restconf/data/car:tire?fields=wear%3didGet’s only tire id and wear level for all tires. %3d is encoded =.
ReadGET/restconf/data/car:tire?content=config&with-defaults=trimGet’s only configuration that is changed from the default for all tires
CreatePOST/restconf/data/car:navigate
body:{"destination":{"address":"10 Main st."}}
Add a new destination address to navigation. This would only work if no naviation address was already set.
DeleteDELETE/restconf/data/car:navigate/destinationRemove destination from navigation system.
RPCPOST/restconf/data/car:rotateTiresRun a RPC to rotate the tires
RPCPOST/restconf/data/car:rotateTires
body:{"order":"clockwise"}
Run a RPC to rotate the tires in specific order
RPCPOST/restconf/data/car:rotateTires
body:{"order":"clockwise"}
response:{"charge":30.00}
Run a RPC to rotate the tires in specific order and return the cost.
Event StreamGET/restconf/data/car:
response:
{"status":"running"}

{"status":"stopped","reason":"flat"}
Stream of events regarding the car status
Event StreamGET/restconf/data/car:?filter=status%3dstopped
response:
{"status":"stopped","reason":"flat"}
Stream only events that cause car to stop. %3d is encoded =.

Events

RESTCONF delivers events using SSE(Server State Events) over HTTP. This is simply a stream per event stream. HTTP/2 allows for an unlimited number of streams over a single connection. Each event is serialized JSON followed by 2 end-of-line characters so you know the event message boundaries.

There is no special library required to read these messages and you can subscribe to as many event streams as you want w/o opening a new connection.

Subscribing to events in web browser:

// this looks like a new connection, but HTTP/2 sends it over existing connection
// to unsubscribe, call  events.close();
const events = new EventSource("/restconf/data/car:updates");
events.addEventListener("message", (e) => {
   console.log(e.data);
 });

Subscribing to events in CLI

$ curl https://server/restconf/data/car:updates
data: {"tire":{"wear":80}}

data: {"tire":{"wear":70}}

Examples subscription paths:

PathDescription
updatesAny changes to car
updates?filter=tire/wear<20Any changes to car when the tire wear is less than 20

More

When using REST API to build a web interface, checkout model assisted web UI.

4 - Node Development

Using the FreeCONF API to build your management interface

Nodes are used to bridge your application to the YANG modeled management interface. As a software engineer you decide how those nodes are designed. Each management request constructs the node heirarchy to navigate to the part of the application to perform the management operation. When the management operation is complete, the nodes are garbage collected. Parallel requests construct different set of nodes so you can keep state in your request logic.

Unlike gRPC or OpenAPI, FreeCONF was designed to allow you to work with your existing application data structures and functions, not separate, generated ones. One reason for the difference is RESTCONF let’s the client pick and choose the data they want returned. Generating full data structures only to be partially populated is inefficient at best. Luckily FreeCONF gives you many options for how to implement your nodes, from a blank slate, to reflection, to code generation to anywhere in between.

Each node starts from a root node registered with the Browser object. Each node controls how its child nodes are constructed and therefore you can mix node implementations to suit each part of your existing application’s design.

If you’re not sure where to start, typically a YANG model uses the similar naming for fields and data structures of the application so I would start with Reflect and Extend. If your code becomes too repetative, then you can start to look at strategies for generating code to replace parts of your current code. This phased approach is useful because you’ll know what code to generate and replace.

4.1 - Interface

Understanding the role of a node.

You are unlikely to be implementing this interface as a developer but this gives a great understanding of all the responsiblities of a node. Each of the node base struct will give you options to control certain aspects of these functions.

type Node interface {

	// Child is called when navigating, creating or deleting a "container" or "list".
	// The request fields contain the nature of the request.
	//
	// Params:
	//  ChildRequest - contains the nature (e.g. get, delete, new) and details of the
	//                 request (e.g. identity name of the container or list )
	//
	// Return:
	//   Node - Return nil entity does not exist otherwise node implementation of the underlying
	//          node requested
	Child(r ChildRequest) (child Node, err error)

	// Next is called for items in a list when navigating, creating or deleting items in the
	// "list".
	//
	// Params:
	//  ListRequest - contains the nature (e.g. get, delete, new) and details of the
	//                 request (e.g. key, row number)
	//
	// Return:
	//   Node - Return nil entity does not exist otherwise node implementation of the underlying
	//          node requested
	//   []val.Value - If a key was defined in YANG, AND the request is for the next item
	//           in the list, then you must also return the key expressed in val.Value array
	Next(r ListRequest) (next Node, key []val.Value, err error)

	// Field is called to read or write "leaf" or "leaf-list" items on container/list items or
	// even root module.
	//
	// Params:
	//   FieldRequest - identity of the field and the contains the nature (e.g. read or write)
	//             tip: if the YANG defines the field as config:false, you can assume the field is
	//             only called for reading and you can ignore the flag for read v.s. write
	//
	//   *ValueHandle - contains a single the "Val" that with either:
	//                   a.) contain the value to be set or
	//                   b.) be expected to be set to the requested value to be read
	Field(r FieldRequest, hnd *ValueHandle) error

	// Choose is only called when there is a need to know which single case between several "choice/case"
	// sets are currently true.  This is only called when reading.
	//
	// Params:
	//   Selection - current selection that is handling the choice investigation
	//   *Choice - name for the choice (there could be several in a given container)
	//
	// Return:
	//   *ChoiceCase - which case is currently valid (there can be only one)
	//
	Choose(sel Selection, choice *meta.Choice) (m *meta.ChoiceCase, err error)

	// BeginEdit is called simply to inform a node when it is about to be edited including deleting or creating.
	// While there is nothing required of nodes to do anything on this call, implementations might have
	// very important, internal operations they need to perform like obtaining write locks for example. It is
	// also an opportunity to reject the edit for whatever reason by returning an error.
	//
	// It important to know this is called on **every** parent node for any edit to any of their children.
	// This is known as "event bubbling" and can be helpful when parents are in a better position to handle
	// operations for children.  You can easily distinguish this situation from the NodeRequest parameter.
	// See https://github.com/freeconf/restconf/wiki/Edit-Node-Traversal for details on when this is called
	// in particular in the order
	//
	// Params:
	//  NodeRequest - contains the nature (e.g. get, delete, new)
	BeginEdit(r NodeRequest) error

	// EndEdit is called simply to inform a node after it has been edited including deleting or creating.
	// While there is nothing required of nodes to do anything on this call, implementations might have
	// very important, internal operations they need to perform like release write locks for example. It is
	// also an opportunity to apply changes to live application or persist data to permanent storage.
	//
	// It important to know this is called on **every** parent node for any edit to any of their children.
	// This is known as "event bubbling" and can be helpful when parents are in a better position to handle
	// operations for children.  You can easily distinguish this situation from the NodeRequest parameter.
	// See https://github.com/freeconf/restconf/wiki/Edit-Node-Traversal for details on when this is called
	// in particular in the order
	//
	// Params:
	//  NodeRequest - contains the nature (e.g. get, delete, new)
	EndEdit(r NodeRequest) error

	// Action(rpc/action) is called when caller wished to run a 'action' or 'rpc' definition.  Input can
	// be found in request if an input is defined.  Output only has to be returned for
	// definitions that declare an output.
	//
	// Params:
	//  ActionRequest - In addition to the identity name of the action or rpc, it may contains the "input"
	//        to the action if one was defined in the YANG.
	//
	// Return:
	//  Node - If there is an expected response from action ("output" defined in YANG) then this would be
	//        the Node implementation of that data.
	Action(r ActionRequest) (output Node, err error)

	// Notify(notification) is called when caller wish to subscribe to events from a node.

	// Notificationsare unique with respect to resources are allocated that survive the original
	// request so implementation should try not keep references to any more resources than neccessary
	// to ensure they do not reference objects that become stale or that impose large chunks of memory
	// unknowningly and unneccesarily.
	//
	// Params:
	//   NotifyRequest - In addition to the identity name of the notification, this contains the stream
	//        to write events to.
	//
	// Return:
	//   NotifyCloser - Function to called to stop streaming events. This will be called for example when
	//      client closes the event stream socket either naturally or because of a network disconnect.
	//
	Notify(r NotifyRequest) (NotifyCloser, error)

	// Peek give an opportunity for API callers to gain access to the objects behind a node.
	//
	// Params:
	//   Selection - current selection that is handling the peeking
	//   interface{} - Who (or under what context) is attempting to peek. This can be used by
	//         node implementation to decide what is returned or if anything at all is returned.
	//         It may also be ingored.  This is up to
	//
	// Return:
	//   interface{} - Can be anything.
	//
	// This can be considered a violation of abstraction so implementation is not gauranteed and
	// can vary w/o any compiler warnings.  This can be useful in unit testing but uses outside this
	// should be used judicously.
	Peek(sel Selection, consumer interface{}) interface{}

	// Context provides an opportunity to add/change values in the request context that is passed to
	// operations on this node and operations to children of this node.  FreeCONF does not put or
	// require anything in the context, it is meant as a way to make application or request specific
	// data available to nodes.
	//
	// A popular use of context is to store the user and or user roles making the request so
	// operations can authorize or log user operations.  See RESTCONF request filters for one
	// way to implement that.
	Context(sel Selection) context.Context
}

4.2 - Edit lifecycle

In-depth details on developing your applications using FreeCONF and understanding the sequence of callbacks when editing node structures.

This is an in-depth, advanced description about how Node implementations handle edits.

When initiating an edit on your application you might want to call custom functions to create data structures at the beginning of the edit, at the end or at some point along the way. You might also want to implement custom locking. This document will go thru your options using FreeCONF library.

type Node interface {

    // Hook before an edit is made. opportunity to reject potential edit or create locks
    BeginEdit(r NodeRequest) error

    // Called to navigate and process edits
    Child(r ChildRequest) (child Node, err error)
    Next(r ListRequest) (next Node, key []val.Value, err error)
    Field(r FieldRequest, hnd *ValueHandle) error


    // Hook after an edit is made. opportunity to trigger persistance, finalize edit
    // or free locks
    EndEdit(r NodeRequest) error
}

Edit: Delete a node

[Root]
    [Parent]
        [Target]    <-- Delete Target
            [Child]
                [Child-Child]
NodeBeginEditEnd Edit
Root3. EditRoot=false,New=false,Delete=false6. EditRoot=false,New=false,Delete=false
Parent2. EditRoot=false,New=false,Delete=false5. EditRoot=false,New=false,Delete=false
Target1. EditRoot=true,New=false,Delete=true4. EditRoot=true,New=false,Delete=true

NOTES:

  1. The [Child] and [Child-Child] nodes never get called when their parent is deleted
  2. Every single ancester node of [Target] would be called on every edit no matter how deep the tree was. This would mean you could implement a single global write lock on a [Root] for any write edit if that is what you needed. Or a single write to database of entire tree on any edit.

Edit:Create a new node thru a target node

[Root]
    [Parent]
        [Target]    <-- New Child-Child request passed to Target
            [Child]
                [Child-Child]
NodeBeginEditEnd Edit
Root3. EditRoot=false,New=false,Delete=false10. EditRoot=false,New=false,Delete=false
Parent2. EditRoot=false,New=false,Delete=false9. EditRoot=false,New=false,Delete=false
Target1. EditRoot=true,New=false,Delete=false8. EditRoot=true,New=false,Delete=false
Child4. EditRoot=true,New=false,Delete=false7. EditRoot=true,New=false,Delete=false
Child-Child5. EditRoot=true,New=true,Delete=false6. EditRoot=true,New=true,Delete=false

Edit: Edit leaf on a node

[Root]
    [Parent]
        [Target]    <-- Edit leaf property on Target
            [Child]
                [Child-Child]
NodeBeginEditEnd Edit
Root3. EditRoot=false,New=false,Delete=false6. EditRoot=false,New=false,Delete=false
Parent2. EditRoot=false,New=false,Delete=false5. EditRoot=false,New=false,Delete=false
Target1. EditRoot=true,New=false,Delete=false4. EditRoot=true,New=false,Delete=false

5 - Resources

Links of useful resources on the Internet

FreeCONF plays an important part of a larger community of people bringing together specifications, information, products and projects. If you would like to submit a link, please submit a merge request.

FreeCONF

Information/Specifications

Examples

  • Industry YANG files - From yangcatalog.org project. Useful to see if others have modeled similar applications and/or examples models.
  • More Industry YANG files - Same as aboive for the OpenConfig quasi-standard
  • Bartender - Open source, robotic bartender built with FreeCONF.

Tools and implementations

  • gNMIc - a gNMI CLI client that provides full support for Capabilities, Get, Set and Subscribe RPCs with collector capabilities. Of particular interest is the terminal that supports tab complete on YANG.
  • Ultra Config - Manage configurations in the cloud.
  • MG Soft - Thick client to walk RESTCONF compatible endpoints
  • YumaWorks - C++ Drivers for RESTCONF among other things
  • Ansible - RESTCONF - Support for RESTCONF and related protocols with documentation here
  • Ansible - URI - RESTCONF client is so simple and Ansible support for REST APIs might be easier to use.
  • ygot - Alternative to FreeCONF for generating Go code from YANG
  • Watsen Networks SZTP Support - For bootstrapping startup configs even before call home requests.
  • Terraform via gNMI - Early experiment but could be used to start something more.
  • gNMI Gateway - Includes Prometheus exporter

Honorable Mention

FreeCONF doesn’t currently support these tools but they are in reach. If these protocol were added to FreeCONF or these projects added support for RESTCONF or gNMI.

NETCONF

NETCONF is a TCP/IP socket based protocol

SNMP

SNMP is still used for metrics today and might provide some value and would be a simple enough protocol to integrate with.

6 - Compliance

Items of note regarding IETF RFC compliance

FreeCONF’s intentions is for strict compliance with IETF RFCs and OpenConfig to allow interoperability with other tools. In order to make the standards appeal to a more general audiance however, FreeCONF has a few extensions that detailed below. If you find a violation or concern, please bringing this to the attention of the project via an issue or discussion. Any ommisions in compliance are likely due to limited resources to implement and contributions would be welcomed.

6.1 - Extensions

Listing of optional extensions

By default FreeCONF’s intentions is for strict compliance with RFC. There are some minor additions that can often be disabled should you require only strict compliance.

Useful options

JSON w/o namespaces

If you submit application/yang-data+json in either Accept or Content-Type in HTTP request headers, you get JSON with namespaces per RFC

Strict Example Response:

{
    "car:engine":{"speed":10,"x:status":running}
}

With no MIME types or ?simplified in URL, you get

Optional Example Response:

{
    "engine":{"speed":10,"status":running}
}

PUT, PATCH or POST requests can have namespaces or not, doesn’t matter. But if they are supplied, they must be correct.

Rational(s):

  • namespaces expose how YANG files are organized and often that shouldn’t matter to API consumer. It might matter to another machine (m2m) in which case proper MIME types would be used and this simplified version wouldn’t matter.
  • added noise in data and primary data stutter
  • only useful for rare name collisions which should be avoided anyway to make APIs more clear anyway

Simplified base URL

If you submit application/yang-data+json in either Accept or Content-Type in HTTP request headers, these are the base URLS as per the RFC

Strict Base URLs

/restconf/data/{module:}... - CRUD and `actions`
/restconf/operations/{module:}...  - `rpc`
/restconf/streams/{module:}... - `notifications`

With no MIME types or ?simplified in URL, you get

Optional Base URL:

/restconf/data/{module:}... - CRUD, `rpcs`, `actions` and `notifications`

Rational(s)

  • Separation was likely for historical reasons and causes unnecessary complicates API usages

RPC Input/Output Wrapping

If you submit application/yang-data+json in either Accept or Content-Type in HTTP request headers, you get JSON with namespaces per RFC

Strict Example Input:

{
    "car:input": {
      "gas":30
    }
}

Strict Example Output:

{
    "car:output": {
      "cost":43.56
    }
}

With no MIME types or ?simplified in URL, you get

Optional Example Input:

{
    "gas":30
}

Optional Example Output:

{
    "cost":43.56
}

Rational:

  • input and output object wrappers add no value

Uploading files

Details

Rational:

  • Being able to upload files to a REST API is fundamental to any REST API.

URL Parameters

FreeCONF specific URL params

Rational:

  • Very useful when creating web UIs

Recursive YANG definitions

Referencing a grouping from inside the grouping is not allowed in YANG but is allowed in FreeCONF YANG parser

module fileStructure {
  
  grouping directory {
    leaf name {
      type string;
    }
    container parent {
      uses directory;
    }
  }
}

Rational:

  • Recursive relationships should be avoided when possible but sometimes unavoidable so being able to model it is important. Implementors should use caution to ensure the data model is not recursive as well. Also when developing tools that navigate the YANG AST, be sure to use flag that denotes a recursive defintion to avoid indefinite recusion.

6.2 - RFCs

Listing of IETF RFCs implemented

There might be more, but these are the high level, implemented RFCs

7 - Doc generation

Generating documentation from your yang files.

FreeCONF comes with a utility to help generate documentation. Your options include

  1. HTML format
  2. Markdown - Useful for storing in github.com, gitlab.com
  3. Graphviz dot that can be then turned into SVG diagram that then can be added to top of HTML page
  4. Raw JSON which can then be used as input to your own template script
  5. Export template you can edit and pass back in

Usage

$ go run github.com/freeconf/yang/cmd/fc-yang doc -help

Usage of fc-yang:
  -f string
    	output format. available formats include html, md, json or dot. (default "none")
  -img-link string
    	Link to image for HTML templates. Default is (module-name).svg.
  -module string
    	Module to be documented.
  -off value
    	disable this feature.  You can specify -off multiple times to disable multiple features. You cannot specify both on and off however.
  -on value
    	enable this feature.  You can specify -on multiple times to enable multiple features. You cannot specify both on and off however.
  -t string
    	Use the template instead of the builtin template.
  -title string
    	Title. (default "RESTful API")
  -x	export the builting template to stdout. You can then edit template and pass it back in using -t option.  Be sure to pick correct format.
  -ypath string
    	Path to YANG files

Optional Graphviz

For the SVG, you will need to install Graphviz.

Example:

sudo apt install graphviz

Example commands

Generate HTML with SVG image at top

fc-yang doc -f dot -module fc-restconf -ypath yang > fc-restconf.dot
dot -Tsvg fc-restconf.dot -o fc-restconf.svg
fc-yang doc -f html -module fc-restconf -title "FreeCONF RESTCONF" -ypath yang > fc-restconf.html

Example output - HTML with SVG

fc-restconf API

Customize by tweaking the template

fc-yang doc -f html -module my-mod -x > doc.template
# Edit doc.template
fc-yang doc -f html -t doc.template -title "My API" -module my-mod -ypath yang > my-api.html

8 - Errors

Returning 401, 409 and 400 class errors

When coding your node.Node implementation, if you return an error from any function, your http clients will receive a 500 error, but if you want to return an error with a different HTTP status code, you can use or wrap one of the predefined errors

import (
  "github.com/freeconf/yang/fc"
)

  ...
  // Option #1 - straight 401 error
  return fc.UnauthorizedError 

  // Option #2 - enhanced but still an 401 error
  return fmt.Errorf("Bad ACL %w", fc.UnauthorizedError)