This is the multi-page printable view of this section. Click here to print.
Reference
- 1: YANG primer
- 2: Basic components
- 3: Using RESTCONF
- 4: Node Development
- 4.1: Interface
- 4.2: Edit lifecycle
- 5: Resources
- 6: Compliance
- 6.1: Extensions
- 6.2: RFCs
- 7: Doc generation
- 8: Errors
1 - YANG primer
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.
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.
all possible leaf types
Name | Description |
---|---|
binary | Any binary data |
bits | A set of bits or flags |
boolean | “true” or “false” |
decimal64 | 64-bit signed decimal number |
empty | A leaf that does not have any value |
enumeration | One of an enumerated set of strings |
identityref | A reference to an abstract identity |
instance-identifier | A reference to a data tree node |
int8 | 8-bit signed integer |
int16 | 16-bit signed integer |
int32 | 32-bit signed integer |
int64 | 64-bit signed integer |
leafref | A reference to a leaf instance |
string | A character string |
uint8 | 8-bit unsigned integer |
uint16 | 16-bit unsigned integer |
uint32 | 32-bit unsigned integer |
uint64 | 64-bit unsigned integer |
union | Choice of member types |
container
module car {
container engine {}
}
Possible request/responses:
curl https://server/restconf/data/car:
{
"engine": {}
}
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
}
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
}
}
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.
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
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
}
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
}
}
notification
module car {
notification flatTire {}
}
Possible request/responses:
curl https://server/restconf/data/car:flatTire
data: {"notificaton":{"eventTime":"2013-12-21T00:01:00Z"}}
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}}}
anydata
module car {
anydata glovebox;
}
Possible request/responses:
curl https://server/restconf/data/car:
{
"glovebox" {
"papers" : ["registration", "manual"],
"napkinCount" : 30
}
}
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;
}
}
}
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.
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.
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;
}
}
}
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.
string types
module car {
leaf color {
type string {
pattern "[a-z]*"; // lowercase
}
}
}
You can have any number of pattern
items that you need.
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";
}
}
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
When using the FreeCONF developer API you’ll likely be interfacing with these six FreeCONF components:
- 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.
- 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
- Selection - a pairing of a node and a meta definition that lets you navigate and operate on your application’s live objects.
- Browser - Where to get the first Selection object known as the root Selection.
- Device - Holds a set of Browsers that you elect to make available in your management API.
- 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
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 Socar.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
- ForGET
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 hierarchycontent=config
- return only configuration datacontent=nonconfig
- return only metric datawith-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 befields=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/cfc.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.
Task | Method | Path | Description |
---|---|---|---|
Read | GET | /restconf/data/car: | Get’s all data (configuration and metrics) for car module |
Read | GET | /restconf/data/car:tire | Get’s all data for all tires |
Read | GET | /restconf/data/car:tire=1 | Get’s all data for first car tire. Yes, seeing an equals in a URL can be disconcerting, but it is legal. |
Update | PATCH | /restconf/data/car:cruise body: {"desiredSpeed":65} | Set cruise control desired speed |
Read | GET | /restconf/data/car:tire?c2-range=!1-2 | Get’s all data for car tires 1 and 2 |
Read | GET | /restconf/data/car:tire?fields=wear%3did | Get’s only tire id and wear level for all tires. %3d is encoded = . |
Read | GET | /restconf/data/car:tire?content=config&with-defaults=trim | Get’s only configuration that is changed from the default for all tires |
Create | POST | /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. |
Delete | DELETE | /restconf/data/car:navigate/destination | Remove destination from navigation system. |
RPC | POST | /restconf/data/car:rotateTires | Run a RPC to rotate the tires |
RPC | POST | /restconf/data/car:rotateTires body: {"order":"clockwise"} | Run a RPC to rotate the tires in specific order |
RPC | POST | /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 Stream | GET | /restconf/data/car: response: {"status":"running"} {"status":"stopped","reason":"flat"} | Stream of events regarding the car status |
Event Stream | GET | /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:
Path | Description |
---|---|
updates | Any changes to car |
updates?filter=tire/wear<20 | Any 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
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
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
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]
Node | BeginEdit | End Edit |
---|---|---|
Root | 3. EditRoot=false,New=false,Delete=false | 6. EditRoot=false,New=false,Delete=false |
Parent | 2. EditRoot=false,New=false,Delete=false | 5. EditRoot=false,New=false,Delete=false |
Target | 1. EditRoot=true,New=false,Delete=true | 4. EditRoot=true,New=false,Delete=true |
NOTES:
- The
[Child]
and[Child-Child]
nodes never get called when their parent is deleted - 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]
Node | BeginEdit | End Edit |
---|---|---|
Root | 3. EditRoot=false,New=false,Delete=false | 10. EditRoot=false,New=false,Delete=false |
Parent | 2. EditRoot=false,New=false,Delete=false | 9. EditRoot=false,New=false,Delete=false |
Target | 1. EditRoot=true,New=false,Delete=false | 8. EditRoot=true,New=false,Delete=false |
Child | 4. EditRoot=true,New=false,Delete=false | 7. EditRoot=true,New=false,Delete=false |
Child-Child | 5. EditRoot=true,New=true,Delete=false | 6. EditRoot=true,New=true,Delete=false |
Edit: Edit leaf on a node
[Root]
[Parent]
[Target] <-- Edit leaf property on Target
[Child]
[Child-Child]
Node | BeginEdit | End Edit |
---|---|---|
Root | 3. EditRoot=false,New=false,Delete=false | 6. EditRoot=false,New=false,Delete=false |
Parent | 2. EditRoot=false,New=false,Delete=false | 5. EditRoot=false,New=false,Delete=false |
Target | 1. EditRoot=true,New=false,Delete=false | 4. EditRoot=true,New=false,Delete=false |
5 - Resources
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
- YANG/RESTCONF on wikipedia
- RFCs
- Network Programmability with YANG - Book by BenoƮt Claise on RESTCONF/YANG and related technologies.
- gNMI is gRPC over HTTP/2 based protocol as an alternative to RESTCONF.
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
- Yuma123 - Open source implementation of NETCONF
- SaltStack
- Chef Support
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
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
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
andoutput
object wrappers add no value
Uploading files
Rational:
- Being able to upload files to a REST API is fundamental to any REST API.
URL Parameters
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
There might be more, but these are the high level, implemented RFCs
- RFC 6020 - YANG 1.0
- RFC 7950 - YANG 1.1
- RFC 7951 - JSON encoding
- RFC 8040 - RESTCONF - sans XML and etag support
- RFC 8525 - YANG Library
- RFC 8071 - RESTCONF Call Home
7 - Doc generation
FreeCONF comes with a utility to help generate documentation. Your options include
- HTML format
- Markdown - Useful for storing in github.com, gitlab.com
- Graphviz dot that can be then turned into SVG diagram that then can be added to top of HTML page
- Raw JSON which can then be used as input to your own template script
- 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
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
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)