mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-02 20:27:14 +00:00
Merge pull request #14448 from qlyoung/doc-add-northbound-api-docs
doc: add northbound api arch docs
This commit is contained in:
commit
fe1da43cab
@ -22,3 +22,4 @@ FRRouting Developer's Guide
|
||||
path
|
||||
pceplib
|
||||
link-state
|
||||
northbound/northbound
|
||||
|
15
doc/developer/northbound/_sidebar.rst
Normal file
15
doc/developer/northbound/_sidebar.rst
Normal file
@ -0,0 +1,15 @@
|
||||
Northbound:
|
||||
~~~~~~~~~~~
|
||||
|
||||
- [[Architecture]]
|
||||
- [[Transactional CLI]]
|
||||
- [[Retrofitting Configuration Commands]]
|
||||
- [[Operational data, RPCs and Notifications]]
|
||||
- [[Plugins - ConfD]]
|
||||
- [[Plugins: Sysrepo]]
|
||||
- [[Plugins - Writing Your Own]]
|
||||
- [[YANG module translator]]
|
||||
- [[Advanced topics]]
|
||||
- [[YANG tools]]
|
||||
- [[Demos]]
|
||||
- [[Links]]
|
294
doc/developer/northbound/advanced-topics.rst
Normal file
294
doc/developer/northbound/advanced-topics.rst
Normal file
@ -0,0 +1,294 @@
|
||||
Auto-generated CLI commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to have less code to maintain, it should be possible to write a
|
||||
tool that auto-generates CLI commands based on the FRR YANG models. As a
|
||||
matter of fact, there are already a number of NETCONF-based CLIs that do
|
||||
exactly that (e.g. `Clixon <https://github.com/clicon/clixon>`__,
|
||||
ConfD’s CLI).
|
||||
|
||||
The problem however is that there isn’t an exact one-to-one mapping
|
||||
between the existing CLI commands and the corresponding YANG nodes from
|
||||
the native models. As an example, ripd’s
|
||||
``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` command
|
||||
changes three YANG leaves at the same time. In order to auto-generate
|
||||
CLI commands and retain their original form, it’s necessary to add
|
||||
annotations in the YANG modules to specify how the commands should look
|
||||
like. Without YANG annotations, the CLI auto-generator will generate a
|
||||
command for each YANG leaf, (leaf-)list and presence-container. The
|
||||
ripd’s ``timers basic`` command, for instance, would become three
|
||||
different commands, which would be undesirable.
|
||||
|
||||
This Tail-f’s®
|
||||
`document <http://info.tail-f.com/hubfs/Whitepapers/Tail-f_ConfD-CLI__Cfg_Mode_App_Note_Rev%20C.pdf>`__
|
||||
shows how to customize ConfD auto-generated CLI commands using YANG
|
||||
annotations.
|
||||
|
||||
The good news is that *libyang* allows users to create plugins to
|
||||
implement their own YANG extensions, which can be used to implement CLI
|
||||
annotations. If done properly, a CLI generator can save FRR developers
|
||||
from writing and maintaining hundreds if not thousands of DEFPYs!
|
||||
|
||||
CLI on a separate program
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The flexible design of the northbound architecture opens the door to
|
||||
move the CLI to a separate program in the long-term future. Some
|
||||
advantages of doing so would be: \* Treat the CLI as just another
|
||||
northbound client, instead of having CLI commands embedded in the
|
||||
binaries of all FRR daemons. \* Improved robustness: bugs in CLI
|
||||
commands (e.g. null-pointer dereferences) or in the CLI code itself
|
||||
wouldn’t affect the FRR daemons. \* Foster innovation by allowing other
|
||||
CLI programs to be implemented, possibly using higher level programming
|
||||
languages.
|
||||
|
||||
The problem, however, is that the northbound retrofitting process will
|
||||
convert only the CLI configuration commands and EXEC commands in a first
|
||||
moment. Retrofitting the “show” commands is a completely different story
|
||||
and shouldn’t happen anytime soon. This should hinder progress towards
|
||||
moving the CLI to a separate program.
|
||||
|
||||
Proposed feature: confirmed commits
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Confirmed commits allow the user to request an automatic rollback to the
|
||||
previous configuration if the commit operation is not confirmed within a
|
||||
number of minutes. This is particularly useful when the user is
|
||||
accessing the CLI through the network (e.g. using SSH) and any
|
||||
configuration change might cause an unexpected loss of connectivity
|
||||
between the user and the router (e.g. misconfiguration of a routing
|
||||
protocol). By using a confirmed commit, the user can rest assured the
|
||||
connectivity will be restored after the given timeout expires, avoiding
|
||||
the need to access the router physically to fix the problem.
|
||||
|
||||
Example of how this feature could be provided in the CLI:
|
||||
``commit confirmed [minutes <1-60>]``. The ability to do confirmed
|
||||
commits should also be exposed in the northbound API so that the
|
||||
northbound plugins can also take advantage of it (in the case of the
|
||||
Sysrepo and ConfD plugins, confirmed commits are implemented externally
|
||||
in the *netopeer2-server* and *confd* daemons, respectively).
|
||||
|
||||
Proposed feature: enable/disable configuration commands/sections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since the ``lyd_node`` data structure from *libyang* can hold private
|
||||
data, it should be possible to mark configuration commands or sections
|
||||
as active or inactive. This would allow CLI users to leverage this
|
||||
feature to disable parts of the running configuration without actually
|
||||
removing the associated commands, and then re-enable the disabled
|
||||
configuration commands or sections later when necessary. Example:
|
||||
|
||||
::
|
||||
|
||||
ripd(config)# show configuration running
|
||||
Configuration:
|
||||
[snip]
|
||||
!
|
||||
router rip
|
||||
default-metric 2
|
||||
distance 80
|
||||
network eth0
|
||||
network eth1
|
||||
!
|
||||
end
|
||||
ripd(config)# disable router rip
|
||||
ripd(config)# commit
|
||||
% Configuration committed successfully (Transaction ID #7).
|
||||
|
||||
ripd(config)# show configuration running
|
||||
Configuration:
|
||||
[snip]
|
||||
!
|
||||
!router rip
|
||||
!default-metric 2
|
||||
!distance 80
|
||||
!network eth0
|
||||
!network eth1
|
||||
!
|
||||
end
|
||||
ripd(config)# enable router rip
|
||||
ripd(config)# commit
|
||||
% Configuration committed successfully (Transaction ID #8).
|
||||
|
||||
ripd(config)# show configuration running
|
||||
[snip]
|
||||
frr defaults traditional
|
||||
!
|
||||
router rip
|
||||
default-metric 2
|
||||
distance 80
|
||||
network eth0
|
||||
network eth1
|
||||
!
|
||||
end
|
||||
|
||||
This capability could be useful in a number of occasions, like disabling
|
||||
configuration commands that are no longer necessary (e.g. ACLs) but that
|
||||
might be necessary at a later point in the future. Other example is
|
||||
allowing users to disable a configuration section for testing purposes,
|
||||
and then re-enable it easily without needing to copy and paste any
|
||||
command.
|
||||
|
||||
Configuration reloads
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Given the limitations of the previous northbound architecture, the FRR
|
||||
daemons didn’t have the ability to reload their configuration files by
|
||||
themselves. The SIGHUP handler of most daemons would only re-read the
|
||||
configuration file and merge it into the running configuration. In most
|
||||
cases, however, what is desired is to replace the running configuration
|
||||
by the updated configuration file. The *frr-reload.py* script was
|
||||
written to work around this problem and it does it well to a certain
|
||||
extent. The problem with the *frr-reload.py* script is that it’s full of
|
||||
special cases here and there, which makes it fragile and unreliable.
|
||||
Maintaining the script is also an additional burden for FRR developers,
|
||||
few of whom are familiar with its code or know when it needs to be
|
||||
updated to account for a new feature.
|
||||
|
||||
In the new northbound architecture, reloading the configuration file can
|
||||
be easily implemented using a configuration transaction. Once the FRR
|
||||
northbound retrofitting process is complete, all daemons should have the
|
||||
ability to reload their configuration files upon receiving the SIGHUP
|
||||
signal, or when the ``configuration load [...] replace`` command is
|
||||
used. Once that point is reached, the *frr-reload.py* script will no
|
||||
longer be necessary and should be removed from the FRR repository.
|
||||
|
||||
Configuration changes coming from the kernel
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This
|
||||
`post <http://discuss.tail-f.com/t/who-should-not-set-configuration-once-a-system-is-up-and-running/111>`__
|
||||
from the Tail-f’s® forum describes the problem of letting systems
|
||||
configure themselves behind the users back. Here are some selected
|
||||
snippets from it: > Traditionally, northbound interface users are the
|
||||
ones in charge of providing configuration data for systems. > > In some
|
||||
systems, we see a deviation from this traditional practice; allowing
|
||||
systems to configure “themselves” behind the scenes (or behind the users
|
||||
back). > > While there might be a business case for such a practice,
|
||||
this kind of configuration remains “dangerous” from northbound users
|
||||
perspective and makes systems hard to predict and even harder to debug.
|
||||
(…) > > With the advent of transactional Network configuration, this
|
||||
practice can not work anymore. The fact that systems are given the right
|
||||
to change configuration is a key here in breaking transactional
|
||||
configuration in a Network.
|
||||
|
||||
FRR is immune to some of the problems described in the aforementioned
|
||||
post. Management clients can configure interfaces that don’t yet exist,
|
||||
and once an interface is deleted from the kernel, its configuration is
|
||||
retained in FRR.
|
||||
|
||||
There are however some cases where information learned from the kernel
|
||||
(e.g. using netlink) can affect the running configuration of all FRR
|
||||
daemons. Examples: interface rename events, VRF rename events, interface
|
||||
being moved to a different VRF, etc. In these cases, since these events
|
||||
can’t be ignored, the best we can do is to send YANG notifications to
|
||||
the management clients to inform about the configuration changes. The
|
||||
management clients should then be prepared to handle such notifications
|
||||
and react accordingly.
|
||||
|
||||
Interfaces and VRFs
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of now zebra doesn’t have the ability to create VRFs or virtual
|
||||
interfaces in the kernel. The ``vrf`` and ``interface`` commands only
|
||||
create pre-provisioned VRFs and interfaces that are only activated when
|
||||
the corresponding information is learned from the kernel. When
|
||||
configuring FRR using an external management client, like a NETCONF
|
||||
client, it might be desirable to actually create functional VRFs and
|
||||
virtual interfaces (e.g. VLAN subinterfaces, bridges, etc) that are
|
||||
installed in the kernel using OS-specific APIs (e.g. netlink, routing
|
||||
socket, etc). Work needs to be done in this area to make this possible.
|
||||
|
||||
Shared configuration objects
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
One of the existing problems in FRR is that it’s hard to ensure that all
|
||||
daemons are in sync with respect to the shared configuration objects
|
||||
(e.g. interfaces, VRFs, route-maps, ACLs, etc). When a route-map is
|
||||
configured using *vtysh*, the same command is sent to all relevant
|
||||
daemons (the daemons that implement route-maps), which ensures
|
||||
synchronization among them. The problem is when a daemon starts after
|
||||
the route-maps are created. In this case this daemon wouldn’t be aware
|
||||
of the previously configured route-maps (unlike the other daemons),
|
||||
which can lead to a lot of confusion and unexpected problems.
|
||||
|
||||
With the new northbound architecture, configuration objects can be
|
||||
manipulated using higher level abstractions, which opens more
|
||||
possibilities to solve this decades-long problem. As an example, one
|
||||
solution would be to make the FRR daemons fetch the shared configuration
|
||||
objects from zebra using the ZAPI interface during initialization. The
|
||||
shared configuration objects could be requested using a list of XPaths
|
||||
expressions in the ``ZEBRA_HELLO`` message, which zebra would respond by
|
||||
sending the shared configuration objects encoded in the JSON format.
|
||||
This solution however doesn’t address the case where zebra starts or
|
||||
restarts after the other FRR daemons. Other solution would be to store
|
||||
the shared configuration objects in the northbound SQL database and make
|
||||
all daemons fetch these objects from there. So far no work has been made
|
||||
on this area as more investigation needs to be done.
|
||||
|
||||
vtysh support
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
As explained in the [[Transactional CLI]] page, all commands introduced
|
||||
by the transactional CLI are not yet available in *vtysh*. This needs to
|
||||
be addressed in the short term future. Some challenges for doing that
|
||||
work include: \* How to display configurations (running, candidates and
|
||||
rollbacks) in a more clever way? The implementation of the
|
||||
``show running-config`` command in *vtysh* is not something that should
|
||||
be followed as an example. A better idea would be to fetch the desired
|
||||
configuration from all daemons (encoded in JSON for example), merge them
|
||||
all into a single ``lyd_node`` variable and then display the combined
|
||||
configurations from this variable (the configuration merges would
|
||||
transparently take care of combining the shared configuration objects).
|
||||
In order to be able to manipulate the JSON configurations, *vtysh* will
|
||||
need to load the YANG modules from all daemons at startup (this might
|
||||
have a minimal impact on startup time). The only issue with this
|
||||
approach is that the ``cli_show()`` callbacks from all daemons are
|
||||
embedded in their binaries and thus not accessible externally. It might
|
||||
be necessary to compile these callbacks on a separate shared library so
|
||||
that they are accessible to *vtysh* too. Other than that, displaying the
|
||||
combined configurations in the JSON/XML formats should be
|
||||
straightforward. \* With the current design, transaction IDs are
|
||||
per-daemon and not global across all FRR daemons. This means that the
|
||||
same transaction ID can represent different transactions on different
|
||||
daemons. Given this observation, how to implement the
|
||||
``rollback configuration`` command in *vtysh*? The easy solution would
|
||||
be to add a ``daemon WORD`` argument to specify the context of the
|
||||
rollback, but per-daemon rollbacks would certainly be confusing and
|
||||
convoluted to end users. A better idea would be to attack the root of
|
||||
the problem: change configuration transactions to be global instead of
|
||||
being per-daemon. This involves a bigger change in the northbound
|
||||
architecture, and would have implications on how transactions are stored
|
||||
in the SQL database (daemon-specific and shared configuration objects
|
||||
would need to have their own tables or columns). \* Loading
|
||||
configuration files in the JSON or XML formats will be tricky, as
|
||||
*vtysh* will need to know which sections of the configuration should be
|
||||
sent to which daemons. *vtysh* will either need to fetch the YANG
|
||||
modules implemented by all daemons at runtime or obtain this information
|
||||
at compile-time somehow.
|
||||
|
||||
Detecting type mismatches at compile-time
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As described in the [[Retrofitting Configuration Commands]] page, the
|
||||
northbound configuration callbacks detect type mismatches at runtime
|
||||
when fetching data from the the ``dnode`` parameter (which represents
|
||||
the configuration node being created, modified, deleted or moved). When
|
||||
a type mismatch is detected, the program aborts and displays a backtrace
|
||||
showing where the problem happened. It would be desirable to detect such
|
||||
type mismatches at compile-time, the earlier the problems are detected
|
||||
the sooner they are fixed.
|
||||
|
||||
One possible solution to this problem would be to auto-generate C
|
||||
structures from the YANG models and provide a function that converts a
|
||||
libyang’s ``lyd_node`` variable to a C structure containing the same
|
||||
information. The northbound callbacks could then fetch configuration
|
||||
data from this C structure, which would naturally lead to type
|
||||
mismatches being detected at compile time. One of the challenges of
|
||||
doing this would be the handling of YANG lists and leaf-lists. It would
|
||||
be necessary to use dynamic data structures like hashes or rb-trees to
|
||||
hold all elements of the lists and leaf-lists, and the process of
|
||||
converting a ``lyd_node`` to an auto-generated C-structure could be
|
||||
expensive. At this point it’s unclear if it’s worth adding more
|
||||
complexity in the northbound architecture to solve this specific
|
||||
problem.
|
283
doc/developer/northbound/architecture.rst
Normal file
283
doc/developer/northbound/architecture.rst
Normal file
@ -0,0 +1,283 @@
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The goal of the new northbound API is to provide a better interface to
|
||||
configure and monitor FRR programatically. The current design based on
|
||||
CLI commands is no longer adequate in a world where computer networks
|
||||
are becoming increasingly bigger, more diverse and more complex. Network
|
||||
scripting using *expect* and screen scraping techniques is too primitive
|
||||
and unreliable to be used in large-scale networks. What is proposed is
|
||||
to modernize FRR to turn it into an API-first routing stack, and
|
||||
reposition the CLI on top of this API. The most important change,
|
||||
however, is not the API that will be provided to external users. In
|
||||
fact, multiple APIs will be supported and users will have the ability to
|
||||
write custom management APIs if necessary. The biggest change is the
|
||||
introduction of a model-driven management architecture based on the
|
||||
`YANG <https://tools.ietf.org/html/rfc7950>`__ modeling language.
|
||||
Instead of writing code tied to any particular user interface
|
||||
(e.g. DEFUNs), YANG allows us to write API-agnostic code (in the form of
|
||||
callbacks) that can be used by any management interface. As an example,
|
||||
it shouldn’t matter if a set of configuration changes is coming from a
|
||||
`NETCONF <https://tools.ietf.org/html/rfc6241>`__ session or from a CLI
|
||||
terminal, the same callbacks should be called to process the
|
||||
configuration changes regardless of where they came from. This
|
||||
model-driven design ensures feature parity across all management
|
||||
interfaces supported by FRR.
|
||||
|
||||
Quoting RFC 7950: > YANG is a language originally designed to model data
|
||||
for the NETCONF protocol. A YANG module defines hierarchies of data that
|
||||
can be used for NETCONF-based operations, including configuration, state
|
||||
data, RPCs, and notifications. This allows a complete description of all
|
||||
data sent between a NETCONF client and server. Although out of scope for
|
||||
this specification, YANG can also be used with protocols other than
|
||||
NETCONF.
|
||||
|
||||
While the YANG and NETCONF specifications are tightly coupled with one
|
||||
another, both are independent to a certain extent and are evolving
|
||||
separately. Examples of other management protocols that use YANG include
|
||||
`RESTCONF <https://tools.ietf.org/html/rfc8040>`__,
|
||||
`gNMI <https://github.com/openconfig/reference/tree/master/rpc/gnmi>`__
|
||||
and
|
||||
`CoAP <https://www.ietf.org/archive/id/draft-vanderstok-core-comi-11.txt>`__.
|
||||
|
||||
In addition to being management-protocol independent, some other
|
||||
advantages of using YANG in FRR are listed below: \* Have a formal
|
||||
contract between FRR and application developers (management clients). A
|
||||
management client that has access to the FRR YANG models knows about all
|
||||
existing configuration options available for use. This information can
|
||||
be used to auto-generate user-friendly interfaces like Web-UIs, custom
|
||||
CLIs and even code bindings for several different programming languages.
|
||||
Using `PyangBind <https://github.com/robshakir/pyangbind>`__, for
|
||||
example, it’s possible to generate Python class hierarchies from YANG
|
||||
models and use these classes to instantiate objects that mirror the
|
||||
structure of the YANG modules and can be serialized/deserialized using
|
||||
different encoding formats. \* Support different encoding formats for
|
||||
instance data. Currently only JSON and XML are supported, but
|
||||
`GPB <https://developers.google.com/protocol-buffers/>`__ and
|
||||
`CBOR <http://cbor.io/>`__ are other viable options in the long term.
|
||||
Additional encoding formats can be implemented in the *libyang* library
|
||||
for optimal performance, or externally by translating data to/from one
|
||||
of the supported formats (with a performance penalty). \* Have a formal
|
||||
mechanism to introduce backward-incompatible changes based on `semantic
|
||||
versioning <http://www.openconfig.net/docs/semver/>`__ (not part of the
|
||||
YANG standard, which allows backward-compatible module updates only). \*
|
||||
Provide seamless support to the industry-standard NETCONF/RESTCONF
|
||||
protocols as alternative management APIs. If FRR configuration/state
|
||||
data is modeled using YANG, supporting YANG-based protocols like NETCONF
|
||||
and RESTCONF is much easier.
|
||||
|
||||
As important as shifting to a model-driven management paradigm, the new
|
||||
northbound architecture also introduces the concept of configuration
|
||||
transactions. Configuration transactions allow management clients to
|
||||
commit multiple configuration changes at the same time and rest assured
|
||||
that either all changes will be applied or none will (all-or-nothing).
|
||||
Configuration transactions are implemented as pseudo-atomic operations
|
||||
and facilitate automation by removing the burden of error recovery from
|
||||
the management side. Another property of configuration transactions is
|
||||
that the configuration changes are always processed in a pre-defined
|
||||
order to ensure consistency. Configuration transactions that encompass
|
||||
multiple network devices are called network-wide transactions and are
|
||||
also supported by the new northbound architecture. When FRR is built
|
||||
using the ``--enable-config-rollbacks`` option, all committed
|
||||
transactions are recorded in the FRR rollback log, which can reside
|
||||
either in memory (volatile) or on persistent storage.
|
||||
|
||||
Network-wide Transactions is the most important leap in network
|
||||
management technology since SNMP. The error recovery and sequencing
|
||||
tasks are removed from the manager side. This is usually more than
|
||||
half the cost in a mature system; more than the entire cost of the
|
||||
managed devices.
|
||||
`[source] <https://www.nanog.org/sites/default/files/tuesday_tutorial_moberg_netconf_35.pdf>`__.
|
||||
|
||||
Figures 1 and 2 below illustrate the old and new northbound architecture
|
||||
of FRR, respectively. As it can be seen, in the old architecture the CLI
|
||||
was the only interface used to configure and monitor FRR (the SNMP
|
||||
plugin was’t taken into account given the small number of implemented
|
||||
MIBs). This means that the only way to automate FRR was by writing
|
||||
scripts that send CLI commands and parse the text output (which usually
|
||||
doesn’t have any structure) using screen scraping and regular
|
||||
expressions.
|
||||
|
||||
+-----------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+=========================================+
|
||||
| *Figure 1: old northbound architecture* |
|
||||
+-----------------------------------------+
|
||||
|
||||
The new northbound architectures, on the other hand, features a
|
||||
multitude of different management APIs, all of them connected to the
|
||||
northbound layer of the FRR daemons. By default, only the CLI interface
|
||||
is compiled built-in in the FRR daemons. The other management interfaces
|
||||
are provided as optional plugins and need to be loaded during the daemon
|
||||
initialization (e.g. *zebra -M confd*). This design makes it possible to
|
||||
integrate FRR with different NETCONF solutions without introducing
|
||||
vendor lock-in. The [[Plugins - Writing Your Own]] page explains how to
|
||||
write custom northbound plugins that can be tailored to all needs
|
||||
(e.g. support custom transport protocols, different data encoding
|
||||
formats, fine-grained access control, etc).
|
||||
|
||||
+-----------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+=========================================+
|
||||
| *Figure 2: new northbound architecture* |
|
||||
+-----------------------------------------+
|
||||
|
||||
Figure 3 shows the internal view of the FRR northbound architecture. In
|
||||
this image we can see that northbound layer is an abstract entity
|
||||
positioned between the northbound callbacks and the northbound clients.
|
||||
The northbound layer is responsible to process the requests coming from
|
||||
the northbound clients and call the appropriate callbacks to satisfy
|
||||
these requests. The northbound plugins communicate with the northbound
|
||||
layer through a public API, which allow users to write third-party
|
||||
plugins that can be maintained separately. The northbound plugins, in
|
||||
turn, have their own APIs to communicate with external management
|
||||
clients.
|
||||
|
||||
+---------------------------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+=========================================================+
|
||||
| *Figure 3: new northbound architecture - internal view* |
|
||||
+---------------------------------------------------------+
|
||||
|
||||
Initially the CLI (and all of its commands) will be maintained inside
|
||||
the FRR daemons. In the long term, however, the goal is to move the CLI
|
||||
to a separate program just like any other management client. The
|
||||
[[Advanced Topics]] page describes the motivations and challenges of
|
||||
doing that. Last but not least, the *libyang* block inside the
|
||||
northbound layer is the engine that makes everything possible. The
|
||||
*libyang* library will be described in more detail in the following
|
||||
sections.
|
||||
|
||||
YANG models
|
||||
-----------
|
||||
|
||||
The main decision to be made when using YANG is which models to
|
||||
implement. There’s a general consensus that using standard models is
|
||||
preferable over using custom (native) models. The reasoning is that
|
||||
applications based on standard models can be reused for all network
|
||||
appliances that support those models, whereas the same doesn’t apply for
|
||||
applications written based on custom models.
|
||||
|
||||
That said, there are multiple standards bodies publishing YANG models
|
||||
and unfortunately not all of them are converging (or at least not yet).
|
||||
In the context of FRR, which is a routing stack, the two sets of YANG
|
||||
models that would make sense to implement are the ones from IETF and
|
||||
from the OpenConfig working group. The question that arises is: which
|
||||
one of them should we commit to? Or should we try to support both
|
||||
somehow, at the cost of extra development efforts?
|
||||
|
||||
Another problem, from an implementation point of view, is that it’s
|
||||
challenging to adapt the existing code base to match standard models. A
|
||||
more reasonable solution, at least in a first moment, would be to use
|
||||
YANG deviations and augmentations to do the opposite: adapt the standard
|
||||
models to the existing code. In practice however this is not as simple
|
||||
as it seems. There are cases where the differences are too substantial
|
||||
to be worked around without restructuring the code by changing its data
|
||||
structures and their relationships. As an example, the *ietf-rip* model
|
||||
places per-interface RIP configuration parameters inside the
|
||||
*control-plane-protocol* list (which is augmented by *ietf-rip*). This
|
||||
means that it’s impossible to configure RIP interface parameters without
|
||||
first configuring a RIP routing instance. The *ripd* daemon on the other
|
||||
hand allows the operator to configure RIP interface parameters even if
|
||||
``router rip`` is not configured. If we were to implement the *ietf-rip*
|
||||
module natively, we’d need to change ripd’s CLI commands (and the
|
||||
associated code) to reflect the new configuration hierarchy.
|
||||
|
||||
Taking into account that FRR has a huge code base and that the
|
||||
northbound retrofitting process per-se will cause a lot of impact, it
|
||||
was decided to take a conservative approach and write custom YANG models
|
||||
for FRR modeled after the existing CLI commands. Having YANG models that
|
||||
closely mirror the CLI commands will allow the FRR developers to
|
||||
retrofit the code base much more easily, without introducing
|
||||
backward-incompatible changes in the CLI and reducing the likelihood of
|
||||
introducing bugs. The [[Retrofitting Configuration Commands]] page
|
||||
explains in detail how to convert configuration commands to the new
|
||||
northbound model.
|
||||
|
||||
Even though having native YANG models is not the ideal solution, it will
|
||||
be already a big step forward for FRR to migrate to a model-driven
|
||||
management architecture, with support for configuration transactions and
|
||||
multiple management interfaces, including NETCONF and RESTCONF (through
|
||||
the northbound plugins).
|
||||
|
||||
The new northbound also features an experimental YANG module translator
|
||||
that will allow users to translate to and from standard YANG models by
|
||||
using translation tables. The [[YANG module translator]] page describes
|
||||
this mechanism in more detail. At this point it’s unclear what can be
|
||||
achieved through module translation and if that can be considered as a
|
||||
definitive solution to support standard models or not.
|
||||
|
||||
Northbound Architecture
|
||||
-----------------------
|
||||
|
||||
+-----------------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+===============================================+
|
||||
| *Figure 4: libyang’s lys_node data structure* |
|
||||
+-----------------------------------------------+
|
||||
|
||||
+-----------------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+===============================================+
|
||||
| *Figure 5: libyang’s lyd_node data structure* |
|
||||
+-----------------------------------------------+
|
||||
|
||||
+---------------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+=============================================+
|
||||
| *Figure 6: libyang’s ly_ctx data structure* |
|
||||
+---------------------------------------------+
|
||||
|
||||
+----------------------------------------+
|
||||
| |space-1.jpg| |
|
||||
+========================================+
|
||||
| *Figure 7: configuration transactions* |
|
||||
+----------------------------------------+
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
The new northbound adds the libyang library as a new mandatory
|
||||
dependency for FRR. To obtain and install this library, follow the steps
|
||||
below:
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/CESNET/libyang
|
||||
$ cd libyang
|
||||
$ git checkout devel
|
||||
$ mkdir build ; cd build
|
||||
$ cmake -DENABLE_LYD_PRIV=ON ..
|
||||
$ make
|
||||
$ sudo make install
|
||||
|
||||
..
|
||||
|
||||
NOTE: first make sure to install the libyang
|
||||
`requirements <https://github.com/CESNET/libyang#build-requirements>`__.
|
||||
|
||||
FRR needs libyang from version 0.16.7 or newer, which is maintained in
|
||||
the ``devel`` branch. libyang 0.15.x is maintained in the ``master``
|
||||
branch and doesn’t contain one small feature used by FRR (the
|
||||
``LY_CTX_DISABLE_SEARCHDIR_CWD`` flag). FRR also makes use of the
|
||||
libyang’s ``ENABLE_LYD_PRIV`` feature, which is disabled by default and
|
||||
needs to be enabled at compile time.
|
||||
|
||||
It’s advisable (but not required) to install sqlite3 and build FRR with
|
||||
``--enable-config-rollbacks`` in order to have access to the
|
||||
configuration rollback feature.
|
||||
|
||||
To test the northbound, the suggested method is to use the
|
||||
[[Transactional CLI]] with the *ripd* daemon and play with the new
|
||||
commands. The ``debug northbound`` command can be used to see which
|
||||
northbound callbacks are called in response to the ``commit`` command.
|
||||
For reference, the [[Demos]] page shows a small demonstration of the
|
||||
transactional CLI in action and what it’s capable of.
|
||||
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/se52j8awh/arch-before.png
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/fziaiwboh/arch-after.png
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/qmc3ocmep/nb-layer.png
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/z4ljsodht/lys_node.png
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/6eynw1h7l/lyd_node.png
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/5cohdhiyp/ly_ctx.png
|
||||
.. |space-1.jpg| image:: https://s22.postimg.cc/8waf3bgjl/transactions.png
|
25
doc/developer/northbound/demos.rst
Normal file
25
doc/developer/northbound/demos.rst
Normal file
@ -0,0 +1,25 @@
|
||||
Transactional CLI
|
||||
-----------------
|
||||
|
||||
This short demo shows some of the capabilities of the new transactional
|
||||
CLI: |asciicast|
|
||||
|
||||
ConfD + NETCONF + Cisco YDK
|
||||
---------------------------
|
||||
|
||||
This is a very simple demo of *ripd* being configured by a python
|
||||
script. The script uses NETCONF to communicate with *ripd*, which has
|
||||
the ConfD plugin loaded. The most interesting part, however, is the fact
|
||||
that the python script is not using handcrafted XML payloads to
|
||||
configure *ripd*. Instead, the script is using python bindings generated
|
||||
using Cisco’s YANG Development Kit (YDK).
|
||||
|
||||
- Script used in the demo:
|
||||
https://gist.github.com/rwestphal/defa9bd1ccf216ab082d4711ae402f95
|
||||
|
||||
|asciicast|
|
||||
|
||||
.. |asciicast| image:: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1.png
|
||||
:target: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1
|
||||
.. |asciicast| image:: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv.png
|
||||
:target: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv
|
233
doc/developer/northbound/links.rst
Normal file
233
doc/developer/northbound/links.rst
Normal file
@ -0,0 +1,233 @@
|
||||
RFCs
|
||||
~~~~
|
||||
|
||||
- `RFC 7950 - The YANG 1.1 Data Modeling
|
||||
Language <https://tools.ietf.org/html/rfc7950>`__
|
||||
- `RFC 7951 - JSON Encoding of Data Modeled with
|
||||
YANG <https://tools.ietf.org/html/rfc7951>`__
|
||||
- `RFC 8342 - Network Management Datastore Architecture
|
||||
(NMDA) <https://tools.ietf.org/html/rfc8342>`__
|
||||
- `RFC 6087 - Guidelines for Authors and Reviewers of YANG Data Model
|
||||
Documents <https://tools.ietf.org/html/rfc6087>`__
|
||||
- `RFC 8340 - YANG Tree
|
||||
Diagrams <https://tools.ietf.org/html/rfc8340>`__
|
||||
- `RFC 6991 - Common YANG Data
|
||||
Types <https://tools.ietf.org/html/rfc6991>`__
|
||||
- `RFC 6241 - Network Configuration Protocol
|
||||
(NETCONF) <https://tools.ietf.org/html/rfc6241>`__
|
||||
- `RFC 8040 - RESTCONF
|
||||
Protocol <https://tools.ietf.org/html/rfc8040>`__
|
||||
|
||||
YANG models
|
||||
~~~~~~~~~~~
|
||||
|
||||
- Collection of several YANG models, including models from standards
|
||||
organizations such as the IETF and vendor specific models:
|
||||
https://github.com/YangModels/yang
|
||||
- OpenConfig: https://github.com/openconfig/public
|
||||
|
||||
Presentations
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- FRR Advanced Northbound API (May 2018)
|
||||
|
||||
- Slides:
|
||||
https://www.dropbox.com/s/zhybthruwocbqaw/netdef-frr-northbound.pdf?dl=1
|
||||
|
||||
- Ok, We Got Data Models, Now What?
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=2oqkiZ83vAA
|
||||
- Slides:
|
||||
https://www.nanog.org/sites/default/files/20161017_Alvarez_Ok_We_Got_v1.pdf
|
||||
|
||||
- Data Model-Driven Management: Latest Industry and Tool Developments
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=n_oKGJ_jgYQ
|
||||
- Slides:
|
||||
https://pc.nanog.org/static/published/meetings/NANOG72/1559/20180219_Claise_Data_Modeling-Driven_Management__v1.pdf
|
||||
|
||||
- Network Automation And Programmability: Reality Versus The Vendor
|
||||
Hype When Considering Legacy And NFV Networks
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=N5wbYncUS9o
|
||||
- Slides:
|
||||
https://www.nanog.org/sites/default/files/1_Moore_Network_Automation_And_Programmability.pdf
|
||||
|
||||
- Lightning Talk: The API is the new CLI?
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=ngi0erGNi58
|
||||
- Slides:
|
||||
https://pc.nanog.org/static/published/meetings/NANOG72/1638/20180221_Grundemann_Lightning_Talk_The_v1.pdf
|
||||
|
||||
- Lightning Talk: OpenConfig - progress toward vendor-neutral network
|
||||
management
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=10rSUbeMmT4
|
||||
- Slides:
|
||||
https://pc.nanog.org/static/published/meetings/NANOG71/1535/20171004_Shaikh_Lightning_Talk_Openconfig_v1.pdf
|
||||
|
||||
- Getting started with OpenConfig
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=L7trUNK8NJI
|
||||
- Slides:
|
||||
https://pc.nanog.org/static/published/meetings/NANOG71/1456/20171003_Alvarez_Getting_Started_With_v1.pdf
|
||||
|
||||
- Why NETCONF and YANG
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=mp4h8aSTba8
|
||||
|
||||
- NETCONF and YANG Concepts
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=UwYYvT7DBvg
|
||||
|
||||
- NETCONF Tutorial
|
||||
|
||||
- Video: https://www.youtube.com/watch?v=N4vov1mI14U
|
||||
|
||||
Whitepapers
|
||||
~~~~~~~~~~~
|
||||
|
||||
- Automating Network and Service Configuration Using NETCONF and YANG:
|
||||
http://www.tail-f.com/wordpress/wp-content/uploads/2013/02/Tail-f-Presentation-Netconf-Yang.pdf
|
||||
- Creating the Programmable Network: The Business Case for NETCONF/YANG
|
||||
in Network Devices:
|
||||
http://www.tail-f.com/wordpress/wp-content/uploads/2013/10/HR-Tail-f-NETCONF-WP-10-08-13.pdf
|
||||
- NETCONF/YANG: What’s Holding Back Adoption & How to Accelerate It:
|
||||
https://www.oneaccess-net.com/images/public/wp_heavy_reading.pdf
|
||||
- Achieving Automation with YANG Modeling Technologies:
|
||||
https://www.cisco.com/c/dam/en/us/products/collateral/cloud-systems-management/network-services-orchestrator/idc-achieving-automation-wp.pdf
|
||||
|
||||
Blog posts and podcasts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- OpenConfig and IETF YANG Models: Can they converge? -
|
||||
http://rob.sh/post/215/
|
||||
- OpenConfig: Standardized Models For Networking -
|
||||
https://packetpushers.net/openconfig-standardized-models-networking/
|
||||
- (Podcast) OpenConfig: From Basics to Implementations -
|
||||
https://blog.ipspace.net/2017/02/openconfig-from-basics-to.html
|
||||
- (Podcast) How Did NETCONF Start on Software Gone Wild -
|
||||
https://blog.ipspace.net/2017/12/how-did-netconf-start-on-software-gone.html
|
||||
- YANG Data Models in the Industry: Current State of Affairs (March
|
||||
2018) -
|
||||
https://www.claise.be/2018/03/yang-data-models-in-the-industry-current-state-of-affairs-march-2018/
|
||||
- Why Data Model-driven Telemetry is the only useful Telemetry? -
|
||||
https://www.claise.be/2018/02/why-data-model-driven-telemetry-is-the-only-useful-telemetry/
|
||||
- NETCONF versus RESTCONF: Capabilitity Comparisons for Data
|
||||
Model-driven Management -
|
||||
https://www.claise.be/2017/10/netconf-versus-restconf-capabilitity-comparisons-for-data-model-driven-management-2/
|
||||
- An Introduction to NETCONF/YANG -
|
||||
https://www.fir3net.com/Networking/Protocols/an-introduction-to-netconf-yang.html
|
||||
- Network Automation and the Rise of NETCONF -
|
||||
https://medium.com/@k.okasha/network-automation-and-the-rise-of-netconf-e96cc33fe28
|
||||
- YANG and the Road to a Model Driven Network -
|
||||
https://medium.com/@k.okasha/yang-and-road-to-a-model-driven-network-e9e52d47148d
|
||||
|
||||
Software
|
||||
~~~~~~~~
|
||||
|
||||
libyang
|
||||
^^^^^^^
|
||||
|
||||
libyang is a YANG data modelling language parser and toolkit written
|
||||
(and providing API) in C.
|
||||
|
||||
- GitHub page: https://github.com/CESNET/libyang
|
||||
- Documentaion: https://netopeer.liberouter.org/doc/libyang/master/
|
||||
|
||||
pyang
|
||||
^^^^^
|
||||
|
||||
pyang is a YANG validator, transformator and code generator, written
|
||||
in python. It can be used to validate YANG modules for correctness,
|
||||
to transform YANG modules into other formats, and to generate code
|
||||
from the modules.
|
||||
|
||||
- GitHub page: https://github.com/mbj4668/pyang
|
||||
- Documentaion: https://github.com/mbj4668/pyang/wiki/Documentation
|
||||
|
||||
ncclient
|
||||
^^^^^^^^
|
||||
|
||||
ncclient is a Python library that facilitates client-side scripting
|
||||
and application development around the NETCONF protocol.
|
||||
|
||||
- GitHub page: https://github.com/ncclient/ncclient
|
||||
- Documentaion: https://ncclient.readthedocs.io/en/latest/
|
||||
|
||||
YDK
|
||||
^^^
|
||||
|
||||
ydk-gen is a developer tool that can generate API’s that are modeled
|
||||
in YANG. Currently, it generates language binding for Python, Go and
|
||||
C++ with planned support for other language bindings in the future.
|
||||
|
||||
- GitHub pages:
|
||||
|
||||
- Generator: https://github.com/CiscoDevNet/ydk-gen
|
||||
- Python: https://github.com/CiscoDevNet/ydk-py
|
||||
|
||||
- Python samples: https://github.com/CiscoDevNet/ydk-py-samples
|
||||
|
||||
- Go: https://github.com/CiscoDevNet/ydk-go
|
||||
- C++: https://github.com/CiscoDevNet/ydk-cpp
|
||||
|
||||
- Documentation:
|
||||
|
||||
- Python: http://ydk.cisco.com/py/docs/
|
||||
- Go: http://ydk.cisco.com/go/docs/
|
||||
- C++: http://ydk.cisco.com/cpp/docs/
|
||||
|
||||
- (Blog post) Simplifying Network Programmability with Model-Driven
|
||||
APIs:
|
||||
https://blogs.cisco.com/sp/simplifying-network-programmability-with-model-driven-apis
|
||||
- (Video introduction) Infrastructure as a Code Using YANG, OpenConfig
|
||||
and YDK: https://www.youtube.com/watch?v=G1b6vJW1R5w
|
||||
|
||||
pyangbind
|
||||
^^^^^^^^^
|
||||
|
||||
A plugin for pyang that creates Python bindings for a YANG model.
|
||||
|
||||
- GitHub page: https://github.com/robshakir/pyangbind
|
||||
- Documentation: http://pynms.io/pyangbind/
|
||||
|
||||
ConfD
|
||||
^^^^^
|
||||
|
||||
- Official webpage (for ConfD Basic):
|
||||
http://www.tail-f.com/confd-basic/
|
||||
- Training Videos: http://www.tail-f.com/confd-training-videos/
|
||||
- Forum: http://discuss.tail-f.com/
|
||||
|
||||
Sysrepo
|
||||
^^^^^^^
|
||||
|
||||
Sysrepo is an YANG-based configuration and operational state data
|
||||
store for Unix/Linux applications.
|
||||
|
||||
- GitHub page: https://github.com/sysrepo/sysrepo
|
||||
- Official webpage: http://www.sysrepo.org/
|
||||
- Documentation: http://www.sysrepo.org/static/doc/html/
|
||||
|
||||
Netopeer2
|
||||
^^^^^^^^^
|
||||
|
||||
Netopeer2 is a set of tools implementing network configuration tools
|
||||
based on the NETCONF Protocol. This is the second generation of the
|
||||
toolset, originally available as the Netopeer project. Netopeer2 is
|
||||
based on the new generation of the NETCONF and YANG libraries -
|
||||
libyang and libnetconf2. The Netopeer server uses sysrepo as a
|
||||
NETCONF datastore implementation.
|
||||
|
||||
- GitHub page: https://github.com/CESNET/Netopeer2
|
||||
|
||||
Clixon
|
||||
^^^^^^
|
||||
|
||||
Clixon is an automatic configuration manager where you generate
|
||||
interactive CLI, NETCONF, RESTCONF and embedded databases with
|
||||
transaction support from a YANG specification.
|
||||
|
||||
- GitHub page: https://github.com/clicon/clixon
|
||||
- Project page: http://www.clicon.org/
|
21
doc/developer/northbound/northbound.rst
Normal file
21
doc/developer/northbound/northbound.rst
Normal file
@ -0,0 +1,21 @@
|
||||
.. _northbound:
|
||||
|
||||
**************
|
||||
Northbound API
|
||||
**************
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
advanced-topics
|
||||
architecture
|
||||
demos
|
||||
links
|
||||
operational-data-rpcs-and-notifications
|
||||
plugins-sysrepo
|
||||
ppr-basic-test-topology
|
||||
ppr-mpls-basic-test-topology
|
||||
retrofitting-configuration-commands
|
||||
transactional-cli
|
||||
yang-module-translator
|
||||
yang-tools
|
@ -0,0 +1,565 @@
|
||||
Operational data
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Writing API-agnostic code for YANG-modeled operational data is
|
||||
challenging. ConfD and Sysrepo, for instance, have completely different
|
||||
APIs to fetch operational data. So how can we write API-agnostic
|
||||
callbacks that can be used by both the ConfD and Sysrepo plugins, and
|
||||
any other northbound client that might be written in the future?
|
||||
|
||||
As an additional requirement, the callbacks must be designed in a way
|
||||
that makes in-place XPath filtering possible. As an example, a
|
||||
management client might want to retrieve only a subset of a large YANG
|
||||
list (e.g. a BGP table), and for optimal performance it should be
|
||||
possible to filter out the unwanted elements locally in the managed
|
||||
devices instead of returning all elements and performing the filtering
|
||||
on the management application.
|
||||
|
||||
To meet all these requirements, the four callbacks below were introduced
|
||||
in the northbound architecture:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* Operational data callback.
|
||||
*
|
||||
* The callback function should return the value of a specific leaf or
|
||||
* inform if a typeless value (presence containers or leafs of type
|
||||
* empty) exists or not.
|
||||
*
|
||||
* xpath
|
||||
* YANG data path of the data we want to get
|
||||
*
|
||||
* list_entry
|
||||
* pointer to list entry
|
||||
*
|
||||
* Returns:
|
||||
* pointer to newly created yang_data structure, or NULL to indicate
|
||||
* the absence of data
|
||||
*/
|
||||
struct yang_data *(*get_elem)(const char *xpath, void *list_entry);
|
||||
|
||||
/*
|
||||
* Operational data callback for YANG lists.
|
||||
*
|
||||
* The callback function should return the next entry in the list. The
|
||||
* 'list_entry' parameter will be NULL on the first invocation.
|
||||
*
|
||||
* list_entry
|
||||
* pointer to a list entry
|
||||
*
|
||||
* Returns:
|
||||
* pointer to the next entry in the list, or NULL to signal that the
|
||||
* end of the list was reached
|
||||
*/
|
||||
void *(*get_next)(void *list_entry);
|
||||
|
||||
/*
|
||||
* Operational data callback for YANG lists.
|
||||
*
|
||||
* The callback function should fill the 'keys' parameter based on the
|
||||
* given list_entry.
|
||||
*
|
||||
* list_entry
|
||||
* pointer to a list entry
|
||||
*
|
||||
* keys
|
||||
* structure to be filled based on the attributes of the provided
|
||||
* list entry
|
||||
*
|
||||
* Returns:
|
||||
* NB_OK on success, NB_ERR otherwise
|
||||
*/
|
||||
int (*get_keys)(void *list_entry, struct yang_list_keys *keys);
|
||||
|
||||
/*
|
||||
* Operational data callback for YANG lists.
|
||||
*
|
||||
* The callback function should return a list entry based on the list
|
||||
* keys given as a parameter.
|
||||
*
|
||||
* keys
|
||||
* structure containing the keys of the list entry
|
||||
*
|
||||
* Returns:
|
||||
* a pointer to the list entry if found, or NULL if not found
|
||||
*/
|
||||
void *(*lookup_entry)(struct yang_list_keys *keys);
|
||||
|
||||
These callbacks were designed to provide maximum flexibility, and borrow
|
||||
a lot of ideas from the ConfD API. Each callback does one and only one
|
||||
task, they are indivisible primitives that can be combined in several
|
||||
different ways to iterate over operational data. The extra flexibility
|
||||
certainly has a performance cost, but it’s the price to pay if we want
|
||||
to expose FRR operational data using several different management
|
||||
interfaces (e.g. NETCONF via either ConfD or Sysrepo+Netopeer2). In the
|
||||
future it might be possible to introduce optional callbacks that do
|
||||
things like returning multiple objects at once. They would provide
|
||||
enhanced performance when iterating over large lists, but their use
|
||||
would be limited by the northbound plugins that can be integrated with
|
||||
them.
|
||||
|
||||
NOTE: using the northbound callbacks as a base, the ConfD plugin can
|
||||
provide up to 100 objects between each round trip between FRR and the
|
||||
*confd* daemon. Preliminary tests showed FRR taking ~7 seconds
|
||||
(asynchronously, without blocking the main pthread) to return a RIP
|
||||
table containing 100k routes to a NETCONF client connected to *confd*
|
||||
(JSON was used as the encoding format). Work needs to be done to find
|
||||
the bottlenecks and optimize this operation.
|
||||
|
||||
The [[Plugins - Writing Your Own]] page explains how the northbound
|
||||
plugins can fetch operational data using the aforementioned northbound
|
||||
callbacks, and how in-place XPath filtering can be implemented.
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
Now let’s move to an example to show how these callbacks are implemented
|
||||
in practice. The following YANG container is part of the *ietf-rip*
|
||||
module and contains operational data about RIP neighbors:
|
||||
|
||||
.. code:: yang
|
||||
|
||||
container neighbors {
|
||||
description
|
||||
"Neighbor information.";
|
||||
list neighbor {
|
||||
key "address";
|
||||
description
|
||||
"A RIP neighbor.";
|
||||
leaf address {
|
||||
type inet:ipv4-address;
|
||||
description
|
||||
"IP address that a RIP neighbor is using as its
|
||||
source address.";
|
||||
}
|
||||
leaf last-update {
|
||||
type yang:date-and-time;
|
||||
description
|
||||
"The time when the most recent RIP update was
|
||||
received from this neighbor.";
|
||||
}
|
||||
leaf bad-packets-rcvd {
|
||||
type yang:counter32;
|
||||
description
|
||||
"The number of RIP invalid packets received from
|
||||
this neighbor which were subsequently discarded
|
||||
for any reason (e.g. a version 0 packet, or an
|
||||
unknown command type).";
|
||||
}
|
||||
leaf bad-routes-rcvd {
|
||||
type yang:counter32;
|
||||
description
|
||||
"The number of routes received from this neighbor,
|
||||
in valid RIP packets, which were ignored for any
|
||||
reason (e.g. unknown address family, or invalid
|
||||
metric).";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
We know that this is operational data because the ``neighbors``
|
||||
container is within the ``state`` container, which has the
|
||||
``config false;`` property (which is applied recursively).
|
||||
|
||||
As expected, the ``gen_northbound_callbacks`` tool also generates
|
||||
skeleton callbacks for nodes that represent operational data:
|
||||
|
||||
.. code:: c
|
||||
|
||||
{
|
||||
.xpath = "/frr-ripd:ripd/state/neighbors/neighbor",
|
||||
.cbs.get_next = ripd_state_neighbors_neighbor_get_next,
|
||||
.cbs.get_keys = ripd_state_neighbors_neighbor_get_keys,
|
||||
.cbs.lookup_entry = ripd_state_neighbors_neighbor_lookup_entry,
|
||||
},
|
||||
{
|
||||
.xpath = "/frr-ripd:ripd/state/neighbors/neighbor/address",
|
||||
.cbs.get_elem = ripd_state_neighbors_neighbor_address_get_elem,
|
||||
},
|
||||
{
|
||||
.xpath = "/frr-ripd:ripd/state/neighbors/neighbor/last-update",
|
||||
.cbs.get_elem = ripd_state_neighbors_neighbor_last_update_get_elem,
|
||||
},
|
||||
{
|
||||
.xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd",
|
||||
.cbs.get_elem = ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem,
|
||||
},
|
||||
{
|
||||
.xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd",
|
||||
.cbs.get_elem = ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem,
|
||||
},
|
||||
|
||||
The ``/frr-ripd:ripd/state/neighbors/neighbor`` list within the
|
||||
``neighbors`` container has three different callbacks that need to be
|
||||
implemented. Let’s start with the first one, the ``get_next`` callback:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *ripd_state_neighbors_neighbor_get_next(void *list_entry)
|
||||
{
|
||||
struct listnode *node;
|
||||
|
||||
if (list_entry == NULL)
|
||||
node = listhead(peer_list);
|
||||
else
|
||||
node = listnextnode((struct listnode *)list_entry);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
Given a list entry, the job of this callback is to find the next element
|
||||
from the list. When the ``list_entry`` parameter is NULL, then the first
|
||||
element of the list should be returned.
|
||||
|
||||
*ripd* uses the ``rip_peer`` structure to represent RIP neighbors, and
|
||||
the ``peer_list`` global variable (linked list) is used to store all RIP
|
||||
neighbors.
|
||||
|
||||
In order to be able to iterate over the list of RIP neighbors, the
|
||||
callback returns a ``listnode`` variable instead of a ``rip_peer``
|
||||
variable. The ``listnextnode`` macro can then be used to find the next
|
||||
element from the linked list.
|
||||
|
||||
Now the second callback, ``get_keys``:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static int ripd_state_neighbors_neighbor_get_keys(void *list_entry,
|
||||
struct yang_list_keys *keys)
|
||||
{
|
||||
struct listnode *node = list_entry;
|
||||
struct rip_peer *peer = listgetdata(node);
|
||||
|
||||
keys->num = 1;
|
||||
(void)inet_ntop(AF_INET, &peer->addr, keys->key[0].value,
|
||||
sizeof(keys->key[0].value));
|
||||
|
||||
return NB_OK;
|
||||
}
|
||||
|
||||
This one is easy. First, we obtain the RIP neighbor from the
|
||||
``listnode`` structure. Then, we fill the ``keys`` parameter according
|
||||
to the attributes of the RIP neighbor. In this case, the ``neighbor``
|
||||
YANG list has only one key: the neighbor IP address. We then use the
|
||||
``inet_ntop()`` function to transform this binary IP address into a
|
||||
string (the lingua franca of the FRR northbound).
|
||||
|
||||
The last callback for the ``neighbor`` YANG list is the ``lookup_entry``
|
||||
callback:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *
|
||||
ripd_state_neighbors_neighbor_lookup_entry(struct yang_list_keys *keys)
|
||||
{
|
||||
struct in_addr address;
|
||||
|
||||
yang_str2ipv4(keys->key[0].value, &address);
|
||||
|
||||
return rip_peer_lookup(&address);
|
||||
}
|
||||
|
||||
This callback is the counterpart of the ``get_keys`` callback: given an
|
||||
array of list keys, the associated list entry should be returned. The
|
||||
``yang_str2ipv4()`` function is used to convert the list key (an IP
|
||||
address) from a string to an ``in_addr`` structure. Then the
|
||||
``rip_peer_lookup()`` function is used to find the list entry.
|
||||
|
||||
Finally, each YANG leaf inside the ``neighbor`` list has its associated
|
||||
``get_elem`` callback:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:ripd/state/neighbors/neighbor/address
|
||||
*/
|
||||
static struct yang_data *
|
||||
ripd_state_neighbors_neighbor_address_get_elem(const char *xpath,
|
||||
void *list_entry)
|
||||
{
|
||||
struct rip_peer *peer = list_entry;
|
||||
|
||||
return yang_data_new_ipv4(xpath, &peer->addr);
|
||||
}
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:ripd/state/neighbors/neighbor/last-update
|
||||
*/
|
||||
static struct yang_data *
|
||||
ripd_state_neighbors_neighbor_last_update_get_elem(const char *xpath,
|
||||
void *list_entry)
|
||||
{
|
||||
/* TODO: yang:date-and-time is tricky */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd
|
||||
*/
|
||||
static struct yang_data *
|
||||
ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem(const char *xpath,
|
||||
void *list_entry)
|
||||
{
|
||||
struct rip_peer *peer = list_entry;
|
||||
|
||||
return yang_data_new_uint32(xpath, peer->recv_badpackets);
|
||||
}
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd
|
||||
*/
|
||||
static struct yang_data *
|
||||
ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath,
|
||||
void *list_entry)
|
||||
{
|
||||
struct rip_peer *peer = list_entry;
|
||||
|
||||
return yang_data_new_uint32(xpath, peer->recv_badroutes);
|
||||
}
|
||||
|
||||
These callbacks receive the list entry as parameter and return the
|
||||
corresponding data using the ``yang_data_new_*()`` wrapper functions.
|
||||
Not much to explain here.
|
||||
|
||||
Iterating over operational data without blocking the main pthread
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
One of the problems we have in FRR is that some “show” commands in the
|
||||
CLI can take too long, potentially long enough to the point of
|
||||
triggering some protocol timeouts and bringing sessions down.
|
||||
|
||||
To avoid this kind of problem, northbound clients are encouraged to do
|
||||
one of the following: \* Create a separate pthread for handling requests
|
||||
to fetch operational data. \* Iterate over YANG lists and leaf-lists
|
||||
asynchronously, returning a maximum number of elements per time instead
|
||||
of returning all elements in one shot.
|
||||
|
||||
In order to handle both cases correctly, the ``get_next`` callbacks need
|
||||
to use locks to prevent the YANG lists from being modified while they
|
||||
are being iterated over. If that is not done, the list entry returned by
|
||||
this callback can become a dangling pointer when used in another
|
||||
callback.
|
||||
|
||||
Currently the ConfD and Sysrepo plugins run only in the main pthread.
|
||||
The plan in the short-term is to introduce a separate pthread only for
|
||||
handling operational data, and use the main pthread only for handling
|
||||
configuration changes, RPCs and notifications.
|
||||
|
||||
RPCs and Actions
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The FRR northbound supports YANG RPCs and Actions through the ``rpc()``
|
||||
callback, which is documented as follows in the *lib/northbound.h* file:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* RPC and action callback.
|
||||
*
|
||||
* Both 'input' and 'output' are lists of 'yang_data' structures. The
|
||||
* callback should fetch all the input parameters from the 'input' list,
|
||||
* and add output parameters to the 'output' list if necessary.
|
||||
*
|
||||
* xpath
|
||||
* xpath of the YANG RPC or action
|
||||
*
|
||||
* input
|
||||
* read-only list of input parameters
|
||||
*
|
||||
* output
|
||||
* list of output parameters to be populated by the callback
|
||||
*
|
||||
* Returns:
|
||||
* NB_OK on success, NB_ERR otherwise
|
||||
*/
|
||||
int (*rpc)(const char *xpath, const struct list *input,
|
||||
struct list *output);
|
||||
|
||||
Note that the same callback is used for both RPCs and actions, which are
|
||||
essentially the same thing. In the case of YANG actions, the ``xpath``
|
||||
parameter can be consulted to find the data node associated to the
|
||||
operation.
|
||||
|
||||
As part of the northbound retrofitting process, it’s suggested to model
|
||||
some EXEC-level commands using YANG so that their functionality is
|
||||
exposed to other management interfaces other than the CLI. As an
|
||||
example, if the ``clear bgp`` command is modeled using a YANG RPC, and a
|
||||
corresponding ``rpc`` callback is written, then it should be possible to
|
||||
clear BGP neighbors using NETCONF and RESTCONF with that RPC (the ConfD
|
||||
and Sysrepo plugins have full support for YANG RPCs and actions).
|
||||
|
||||
Here’s an example of a very simple RPC modeled using YANG:
|
||||
|
||||
.. code:: yang
|
||||
|
||||
rpc clear-rip-route {
|
||||
description
|
||||
"Clears RIP routes from the IP routing table and routes
|
||||
redistributed into the RIP protocol.";
|
||||
}
|
||||
|
||||
This RPC doesn’t have any input or output parameters. Below we can see
|
||||
the implementation of the corresponding ``rpc`` callback, whose skeleton
|
||||
was automatically generated by the ``gen_northbound_callbacks`` tool:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:clear-rip-route
|
||||
*/
|
||||
static int clear_rip_route_rpc(const char *xpath, const struct list *input,
|
||||
struct list *output)
|
||||
{
|
||||
struct route_node *rp;
|
||||
struct rip_info *rinfo;
|
||||
struct list *list;
|
||||
struct listnode *listnode;
|
||||
|
||||
/* Clear received RIP routes */
|
||||
for (rp = route_top(rip->table); rp; rp = route_next(rp)) {
|
||||
list = rp->info;
|
||||
if (list == NULL)
|
||||
continue;
|
||||
|
||||
for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) {
|
||||
if (!rip_route_rte(rinfo))
|
||||
continue;
|
||||
|
||||
if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB))
|
||||
rip_zebra_ipv4_delete(rp);
|
||||
break;
|
||||
}
|
||||
|
||||
if (rinfo) {
|
||||
RIP_TIMER_OFF(rinfo->t_timeout);
|
||||
RIP_TIMER_OFF(rinfo->t_garbage_collect);
|
||||
listnode_delete(list, rinfo);
|
||||
rip_info_free(rinfo);
|
||||
}
|
||||
|
||||
if (list_isempty(list)) {
|
||||
list_delete_and_null(&list);
|
||||
rp->info = NULL;
|
||||
route_unlock_node(rp);
|
||||
}
|
||||
}
|
||||
|
||||
return NB_OK;
|
||||
}
|
||||
|
||||
If the ``clear-rip-route`` RPC had any input parameters, they would be
|
||||
available in the ``input`` list given as a parameter to the callback.
|
||||
Similarly, the ``output`` list can be used to append output parameters
|
||||
generated by the RPC, if any are defined in the YANG model.
|
||||
|
||||
The northbound clients (CLI and northbound plugins) have the
|
||||
responsibility to create and delete the ``input`` and ``output`` lists.
|
||||
However, in the cases where the RPC or action doesn’t have any input or
|
||||
output parameters, the northbound client can pass NULL pointers to the
|
||||
``rpc`` callback to avoid creating linked lists unnecessarily. We can
|
||||
see this happening in the example below:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:clear-rip-route
|
||||
*/
|
||||
DEFPY (clear_ip_rip,
|
||||
clear_ip_rip_cmd,
|
||||
"clear ip rip",
|
||||
CLEAR_STR
|
||||
IP_STR
|
||||
"Clear IP RIP database\n")
|
||||
{
|
||||
return nb_cli_rpc("/frr-ripd:clear-rip-route", NULL, NULL);
|
||||
}
|
||||
|
||||
``nb_cli_rpc()`` is a helper function that merely finds the appropriate
|
||||
``rpc`` callback based on the XPath provided in the first argument, and
|
||||
map the northbound error code from the ``rpc`` callback to a vty error
|
||||
code (e.g. ``CMD_SUCCESS``, ``CMD_WARNING``). The second and third
|
||||
arguments provided to the function refer to the ``input`` and ``output``
|
||||
lists. In this case, both arguments are set to NULL since the YANG RPC
|
||||
in question doesn’t have any input/output parameters.
|
||||
|
||||
Notifications
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
YANG notifations are sent using the ``nb_notification_send()`` function,
|
||||
documented in the *lib/northbound.h* file as follows:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* Send a YANG notification. This is a no-op unless the 'nb_notification_send'
|
||||
* hook was registered by a northbound plugin.
|
||||
*
|
||||
* xpath
|
||||
* xpath of the YANG notification
|
||||
*
|
||||
* arguments
|
||||
* linked list containing the arguments that should be sent. This list is
|
||||
* deleted after being used.
|
||||
*
|
||||
* Returns:
|
||||
* NB_OK on success, NB_ERR otherwise
|
||||
*/
|
||||
extern int nb_notification_send(const char *xpath, struct list *arguments);
|
||||
|
||||
The northbound doesn’t use callbacks for notifications because
|
||||
notifications are generated locally and sent to the northbound clients.
|
||||
This way, whenever a notification needs to be sent, it’s possible to
|
||||
call the appropriate function directly instead of finding a callback
|
||||
based on the XPath of the YANG notification.
|
||||
|
||||
As an example, the *ietf-rip* module contains the following
|
||||
notification:
|
||||
|
||||
.. code:: yang
|
||||
|
||||
notification authentication-failure {
|
||||
description
|
||||
"This notification is sent when the system
|
||||
receives a PDU with the wrong authentication
|
||||
information.";
|
||||
leaf interface-name {
|
||||
type string;
|
||||
description
|
||||
"Describes the name of the RIP interface.";
|
||||
}
|
||||
}
|
||||
|
||||
The following convenience function was implemented in *ripd* to send
|
||||
*authentication-failure* YANG notifications:
|
||||
|
||||
.. code:: c
|
||||
|
||||
/*
|
||||
* XPath: /frr-ripd:authentication-failure
|
||||
*/
|
||||
void ripd_notif_send_auth_failure(const char *ifname)
|
||||
{
|
||||
const char *xpath = "/frr-ripd:authentication-failure";
|
||||
struct list *arguments;
|
||||
char xpath_arg[XPATH_MAXLEN];
|
||||
struct yang_data *data;
|
||||
|
||||
arguments = yang_data_list_new();
|
||||
|
||||
snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath);
|
||||
data = yang_data_new_string(xpath_arg, ifname);
|
||||
listnode_add(arguments, data);
|
||||
|
||||
nb_notification_send(xpath, arguments);
|
||||
}
|
||||
|
||||
Now sending the *authentication-failure* YANG notification should be as
|
||||
simple as calling the above function and provide the appropriate
|
||||
interface name. The notification will be processed by all northbound
|
||||
plugins that subscribed a callback to the ``nb_notification_send`` hook.
|
||||
The ConfD and Sysrepo plugins, for instance, use this hook to relay the
|
||||
notifications to the *confd*/*sysrepod* daemons, which can generate
|
||||
NETCONF notifications to subscribed clients. When no northbound plugin
|
||||
is loaded, ``nb_notification_send()`` doesn’t do anything and the
|
||||
notifications are ignored.
|
137
doc/developer/northbound/plugins-sysrepo.rst
Normal file
137
doc/developer/northbound/plugins-sysrepo.rst
Normal file
@ -0,0 +1,137 @@
|
||||
Installation
|
||||
------------
|
||||
|
||||
Required dependencies
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
# apt-get install git cmake build-essential bison flex libpcre3-dev libev-dev \
|
||||
libavl-dev libprotobuf-c-dev protobuf-c-compiler libcmocka0 \
|
||||
libcmocka-dev doxygen libssl-dev libssl-dev libssh-dev
|
||||
|
||||
libyang
|
||||
^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
# apt-get install libyang0.16 libyang-dev
|
||||
|
||||
Sysrepo
|
||||
^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/sysrepo/sysrepo.git
|
||||
$ cd sysrepo/
|
||||
$ mkdir build; cd build
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Release -DGEN_LANGUAGE_BINDINGS=OFF .. && make
|
||||
# make install
|
||||
|
||||
libnetconf2
|
||||
^^^^^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/CESNET/libnetconf2.git
|
||||
$ cd libnetconf2/
|
||||
$ mkdir build; cd build
|
||||
$ cmake .. && make
|
||||
# make install
|
||||
|
||||
netopeer2
|
||||
^^^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/CESNET/Netopeer2.git
|
||||
$ cd Netopeer2
|
||||
$ cd server
|
||||
$ mkdir build; cd build
|
||||
$ cmake .. && make
|
||||
# make install
|
||||
|
||||
**Note:** If ``make install`` fails as it can’t find
|
||||
``libsysrepo.so.0.7``, then run ``ldconfig`` and try again as it might
|
||||
not have updated the lib search path
|
||||
|
||||
FRR
|
||||
^^^
|
||||
|
||||
Build and install FRR using the ``--enable-sysrepo`` configure-time
|
||||
option.
|
||||
|
||||
Initialization
|
||||
--------------
|
||||
|
||||
Install the FRR YANG modules in the Sysrepo datastore:
|
||||
|
||||
::
|
||||
|
||||
# sysrepoctl --install /usr/local/share/yang/ietf-interfaces@2018-01-09.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-vrf.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-interface.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-route-types.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-filter.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-route-map.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-isisd.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-ripd.yang
|
||||
# sysrepoctl --install /usr/local/share/yang/frr-ripngd.yang
|
||||
# sysrepoctl -c frr-vrf --owner frr --group frr
|
||||
# sysrepoctl -c frr-interface --owner frr --group frr
|
||||
# sysrepoctl -c frr-route-types --owner frr --group frr
|
||||
# sysrepoctl -c frr-filter --owner frr --group frr
|
||||
# sysrepoctl -c frr-route-map --owner frr --group frr
|
||||
# sysrepoctl -c frr-isisd --owner frr --group frr
|
||||
# sysrepoctl -c frr-ripd --owner frr --group frr
|
||||
# sysrepoctl -c frr-ripngd --owner frr --group frr
|
||||
|
||||
Start netopeer2-server:
|
||||
|
||||
::
|
||||
|
||||
# netopeer2-server -d &
|
||||
|
||||
Start the FRR daemons with the sysrepo module:
|
||||
|
||||
::
|
||||
|
||||
# isisd -M sysrepo --log=stdout
|
||||
|
||||
Managing the configuration
|
||||
--------------------------
|
||||
|
||||
The following NETCONF scripts can be used to show and edit the FRR
|
||||
configuration:
|
||||
https://github.com/rzalamena/ietf-hackathon-brazil-201907/tree/master/netconf-scripts
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
# ./netconf-edit.py 127.0.0.1
|
||||
# ./netconf-get-config.py 127.0.0.1
|
||||
<?xml version="1.0" encoding="UTF-8"?><data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><isis xmlns="http://frrouting.org/yang/isisd"><instance><area-tag>testnet</area-tag><is-type>level-1</is-type></instance></isis></data>
|
||||
|
||||
..
|
||||
|
||||
NOTE: the ncclient library needs to be installed first:
|
||||
``apt install -y python3-ncclient``
|
||||
|
||||
The *sysrepocfg* tool can also be used to show/edit the FRR
|
||||
configuration. Example:
|
||||
|
||||
::
|
||||
|
||||
# sysrepocfg --format=json --import=frr-isisd.json --datastore=running frr-isisd
|
||||
# sysrepocfg --format=json --export --datastore=running frr-isisd
|
||||
{
|
||||
"frr-isisd:isis": {
|
||||
"instance": [
|
||||
{
|
||||
"area-tag": "testnet",
|
||||
"is-type": "level-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
1632
doc/developer/northbound/ppr-basic-test-topology.rst
Normal file
1632
doc/developer/northbound/ppr-basic-test-topology.rst
Normal file
File diff suppressed because it is too large
Load Diff
1991
doc/developer/northbound/ppr-mpls-basic-test-topology.rst
Normal file
1991
doc/developer/northbound/ppr-mpls-basic-test-topology.rst
Normal file
File diff suppressed because it is too large
Load Diff
1916
doc/developer/northbound/retrofitting-configuration-commands.rst
Normal file
1916
doc/developer/northbound/retrofitting-configuration-commands.rst
Normal file
File diff suppressed because it is too large
Load Diff
244
doc/developer/northbound/transactional-cli.rst
Normal file
244
doc/developer/northbound/transactional-cli.rst
Normal file
@ -0,0 +1,244 @@
|
||||
Table of Contents
|
||||
-----------------
|
||||
|
||||
- `Introduction <#introduction>`__
|
||||
- `Configuration modes <#config-modes>`__
|
||||
- `New commands <#retrofitting-process>`__
|
||||
|
||||
- `commit check <#cmd1>`__
|
||||
- `commit <#cmd2>`__
|
||||
- `discard <#cmd3>`__
|
||||
- `configuration database max-transactions <#cmd4>`__
|
||||
- `configuration load <#cmd5>`__
|
||||
- `rollback configuration <#cmd6>`__
|
||||
- `show configuration candidate <#cmd7>`__
|
||||
- `show configuration compare <#cmd8>`__
|
||||
- `show configuration running <#cmd9>`__
|
||||
- `show configuration transaction <#cmd10>`__
|
||||
- `show yang module <#cmd11>`__
|
||||
- `show yang module-translator <#cmd12>`__
|
||||
- `update <#cmd13>`__
|
||||
- `yang module-translator load <#cmd14>`__
|
||||
- `yang module-translator unload <#cmd15>`__
|
||||
|
||||
Introduction
|
||||
~~~~~~~~~~~~
|
||||
|
||||
All FRR daemons have built-in support for the CLI, which can be accessed
|
||||
either through local telnet or via the vty socket (e.g. by using
|
||||
*vtysh*). This will not change with the introduction of the Northbound
|
||||
API. However, a new command-line option will be available for all FRR
|
||||
daemons: ``--tcli``. When given, this option makes the daemon start with
|
||||
a transactional CLI and configuration commands behave a bit different.
|
||||
Instead of editing the running configuration, they will edit the
|
||||
candidate configuration. In other words, the configuration commands
|
||||
won’t be applied immediately, that has to be done on a separate step
|
||||
using the new ``commit`` command.
|
||||
|
||||
The transactional CLI simply leverages the new capabilities provided by
|
||||
the Northbound API and exposes the concept of candidate configurations
|
||||
to CLI users too. When the transactional mode is not used, the
|
||||
configuration commands also edit the candidate configuration, but
|
||||
there’s an implicit ``commit`` after each command.
|
||||
|
||||
In order for the transactional CLI to work, all configuration commands
|
||||
need to be converted to the new northbound model. Commands not converted
|
||||
to the new northbound model will change the running configuration
|
||||
directly since they bypass the FRR northbound layer. For this reason,
|
||||
starting a daemon with the transactional CLI is not advisable unless all
|
||||
of its commands have already been converted. When that’s not the case,
|
||||
we can run into a situation like this:
|
||||
|
||||
::
|
||||
|
||||
ospfd(config)# router ospf
|
||||
ospfd(config-router)# ospf router-id 1.1.1.1
|
||||
[segfault in ospfd]
|
||||
|
||||
The segfault above can happen if ``router ospf`` edits the candidate
|
||||
configuration but ``ospf router-id 1.1.1.1`` edits the running
|
||||
configuration. The second command tries to set
|
||||
``ospf->router_id_static`` but, since the previous ``router ospf``
|
||||
command hasn’t been commited yet, the ``ospf`` global variable is set to
|
||||
NULL, which leads to the crash. Besides this problem, having a set of
|
||||
commands that edit the candidate configuration and others that edit the
|
||||
running configuration is confusing at best. The ``--tcli`` option should
|
||||
be used only by developers until the northbound retrofitting process is
|
||||
complete.
|
||||
|
||||
Configuration modes
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When using the transactional CLI (``--tcli``), FRR supports three
|
||||
different forms of the ``configure`` command: \* ``configure terminal``:
|
||||
in this mode, a single candidate configuration is shared by all users.
|
||||
This means that one user might delete a configuration object that’s
|
||||
being edited by another user, in which case the CLI will detect and
|
||||
report the problem. If one user issues the ``commit`` command, all
|
||||
changes done by all users are committed. \* ``configure private``: users
|
||||
have a private candidate configuration that is edited separately from
|
||||
the other users. The ``commit`` command commits only the changes done by
|
||||
the user. \* ``configure exclusive``: similar to ``configure private``,
|
||||
but also locks the running configuration to prevent other users from
|
||||
changing it. The configuration lock is released when the user exits the
|
||||
configuration mode.
|
||||
|
||||
When using ``configure terminal`` or ``configure private``, the
|
||||
candidate configuration being edited might become outdated if another
|
||||
user commits a different candidate configuration on another session.
|
||||
TODO: show image to illustrate the problem.
|
||||
|
||||
New commands
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The list below contains the new CLI commands introduced by Northbound
|
||||
API. The commands are available when a daemon is started using the
|
||||
transactional CLI (``--tcli``). Currently ``vtysh`` doesn’t support any
|
||||
of these new commands.
|
||||
|
||||
Please refer to the [[Demos]] page to see a demo of the transactional
|
||||
CLI in action.
|
||||
|
||||
--------------
|
||||
|
||||
``commit check``
|
||||
''''''''''''''''
|
||||
|
||||
Check if the candidate configuration is valid or not.
|
||||
|
||||
``commit [force] [comment LINE...]``
|
||||
''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Commit the changes done in the candidate configuration into the running
|
||||
configuration.
|
||||
|
||||
Options: \* ``force``: commit even if the candidate configuration is
|
||||
outdated. It’s usually a better option to use the ``update`` command
|
||||
instead. \* ``comment LINE...``: assign a comment to the configuration
|
||||
transaction. This comment is displayed when viewing the recorded
|
||||
transactions in the output of the ``show configuration transaction``
|
||||
command.
|
||||
|
||||
``discard``
|
||||
'''''''''''
|
||||
|
||||
Discard the changes done in the candidate configuration.
|
||||
|
||||
``configuration database max-transactions (1-100)``
|
||||
'''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Set the maximum number of transactions to store in the rollback log.
|
||||
|
||||
``configuration load <file [<json|xml> [translate WORD]] FILENAME|transaction (1-4294967296)> [replace]``
|
||||
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Load a new configuration into the candidate configuration. When loading
|
||||
the configuration from a file, it’s assumed that the configuration will
|
||||
be in the form of CLI commands by default. The ``json`` and ``xml``
|
||||
options can be used to load configurations in the JSON and XML formats,
|
||||
respectively. It’s also possible to load a configuration from a previous
|
||||
transaction by specifying the desired transaction ID
|
||||
(``(1-4294967296)``).
|
||||
|
||||
Options: \* ``translate WORD``: translate the JSON/XML configuration
|
||||
file using the YANG module translator. \* ``replace``: replace the
|
||||
candidate by the loaded configuration. The default is to merge the
|
||||
loaded configuration into the candidate configuration.
|
||||
|
||||
``rollback configuration (1-4294967296)``
|
||||
'''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Roll back the running configuration to a previous configuration
|
||||
identified by its transaction ID (``(1-4294967296)``).
|
||||
|
||||
``show configuration candidate [<json|xml> [translate WORD]] [<with-defaults|changes>]``
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Show the candidate configuration.
|
||||
|
||||
Options: \* ``json``: show the configuration in the JSON format. \*
|
||||
``xml``: show the configuration in the XML format. \*
|
||||
``translate WORD``: translate the JSON/XML output using the YANG module
|
||||
translator. \* ``with-defaults``: show default values that are hidden by
|
||||
default. \* ``changes``: show only the changes done in the candidate
|
||||
configuration.
|
||||
|
||||
``show configuration compare <candidate|running|transaction (1-4294967296)> <candidate|running|transaction (1-4294967296)> [<json|xml> [translate WORD]]``
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Show the difference between two different configurations.
|
||||
|
||||
Options: \* ``json``: show the configuration differences in the JSON
|
||||
format. \* ``xml``: show the configuration differences in the XML
|
||||
format. \* ``translate WORD``: translate the JSON/XML output using the
|
||||
YANG module translator.
|
||||
|
||||
``show configuration running [<json|xml> [translate WORD]] [with-defaults]``
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Show the running configuration.
|
||||
|
||||
Options: \* ``json``: show the configuration in the JSON format. \*
|
||||
``xml``: show the configuration in the XML format. \*
|
||||
``translate WORD``: translate the JSON/XML output using the YANG module
|
||||
translator. \* ``with-defaults``: show default values that are hidden by
|
||||
default.
|
||||
|
||||
NOTE: ``show configuration running`` shows only the running
|
||||
configuration as known by the northbound layer. Configuration
|
||||
commands not converted to the new northbound model will not be
|
||||
displayed. To show the full running configuration, the legacy
|
||||
``show running-config`` command must be used.
|
||||
|
||||
``show configuration transaction [(1-4294967296) [<json|xml> [translate WORD]] [changes]]``
|
||||
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
When a transaction ID (``(1-4294967296)``) is given, show the
|
||||
configuration associated to the previously committed transaction.
|
||||
|
||||
When a transaction ID is not given, show all recorded transactions in
|
||||
the rollback log.
|
||||
|
||||
Options: \* ``json``: show the configuration in the JSON format. \*
|
||||
``xml``: show the configuration in the XML format. \*
|
||||
``translate WORD``: translate the JSON/XML output using the YANG module
|
||||
translator. \* ``with-defaults``: show default values that are hidden by
|
||||
default. \* ``changes``: show changes compared to the previous
|
||||
transaction.
|
||||
|
||||
``show yang module [module-translator WORD] [WORD <summary|tree|yang|yin>]``
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
When a YANG module is not given, show all loaded YANG modules.
|
||||
Otherwise, show detailed information about the given module.
|
||||
|
||||
Options: \* ``module-translator WORD``: change the context to modules
|
||||
loaded by the specified YANG module translator. \* ``summary``: display
|
||||
summary information about the module. \* ``tree``: display module in the
|
||||
tree (RFC 8340) format. \* ``yang``: display module in the YANG format.
|
||||
\* ``yin``: display module in the YIN format.
|
||||
|
||||
``show yang module-translator``
|
||||
'''''''''''''''''''''''''''''''
|
||||
|
||||
Show all loaded YANG module translators.
|
||||
|
||||
``update``
|
||||
''''''''''
|
||||
|
||||
Rebase the candidate configuration on top of the latest running
|
||||
configuration. Conflicts are resolved automatically by giving preference
|
||||
to the changes done in the candidate configuration.
|
||||
|
||||
The candidate configuration might be outdated if the running
|
||||
configuration was updated after the candidate was created.
|
||||
|
||||
``yang module-translator load FILENAME``
|
||||
''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Load a YANG module translator from the filesystem.
|
||||
|
||||
``yang module-translator unload WORD``
|
||||
''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Unload a YANG module translator identified by its name.
|
629
doc/developer/northbound/yang-module-translator.rst
Normal file
629
doc/developer/northbound/yang-module-translator.rst
Normal file
@ -0,0 +1,629 @@
|
||||
Table of Contents
|
||||
-----------------
|
||||
|
||||
- `Introduction <#introduction>`__
|
||||
- `Deviation Modules <#deviation-modules>`__
|
||||
- `Translation Tables <#translation-tables>`__
|
||||
- `CLI Demonstration <#cli-demonstration>`__
|
||||
- `Implementation Details <#implementation-details>`__
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
One key requirement for the FRR northbound architecture is that it
|
||||
should be possible to configure/monitor FRR using different sets of YANG
|
||||
models. This is especially important considering that the industry
|
||||
hasn’t reached a consensus to provide a single source of standard models
|
||||
for network management. At this moment both the IETF and OpenConfig
|
||||
models are widely implemented and are unlikely to converge, at least not
|
||||
in the short term. In the ideal scenario, management applications should
|
||||
be able to use either IETF or OpenConfig models to configure and monitor
|
||||
FRR programatically (or even both at the same time!).
|
||||
|
||||
But how can FRR support multiple sets of YANG models at the same time?
|
||||
There must be only a single source of truth that models the existing
|
||||
implementation accurately (the native models). Writing different code
|
||||
paths or callbacks for different models would be inviable, it would lead
|
||||
to a lot of duplicated code and extra maintenance overhead.
|
||||
|
||||
In order to support different sets of YANG modules without introducing
|
||||
the overhead of writing additional code, the solution is to create a
|
||||
mechanism that dynamically translates YANG instance data between
|
||||
non-native models to native models and vice-versa. Based on this idea,
|
||||
an experimental YANG module translator was implemented within the FRR
|
||||
northbound layer. The translator works by translating XPaths at runtime
|
||||
using translation tables provided by the user. The translator itself is
|
||||
modeled using YANG and users can create translators using simple JSON
|
||||
files.
|
||||
|
||||
A YANG module translator consists of two components: deviation modules
|
||||
and translation tables.
|
||||
|
||||
Deviation Modules
|
||||
-----------------
|
||||
|
||||
The first step when writing a YANG module translator is to create a
|
||||
`deviations <https://tools.ietf.org/html/rfc7950#page-131>`__ module for
|
||||
each module that is going be translated. This is necessary because in
|
||||
most cases it won’t be possible to create a perfect translator that
|
||||
covers the non-native models on their entirety. Some non-native modules
|
||||
might contain nodes that can’t be mapped to a corresponding node in the
|
||||
FRR native models. This is either because the corresponding
|
||||
functionality is not implemented in FRR or because it’s modeled in a
|
||||
different way that is incompatible.
|
||||
|
||||
An an example, *ripd* doesn’t have BFD support yet, so we need to create
|
||||
a YANG deviation to modify the *ietf-rip* module and remove the ``bfd``
|
||||
container from it:
|
||||
|
||||
.. code:: yang
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:bfd" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
In the example below, while both the *frr-ripd* and *ietf-rip* modules
|
||||
support RIP authentication, they model the authentication data in
|
||||
different ways, making translation not possible given the constraints of
|
||||
the current module translator. A new deviation is necessary to remove
|
||||
the ``authentication`` container from the *ietf-rip* module:
|
||||
|
||||
.. code:: yang
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:authentication" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
..
|
||||
|
||||
NOTE: it should be possible to translate the
|
||||
``ietf-rip:authentication`` container if the *frr-ripd* module is
|
||||
modified to model the corresponding data in a compatible way. Another
|
||||
option is to improve the module translator to make more complex
|
||||
translations possible, instead of requiring one-to-one XPath
|
||||
mappings.
|
||||
|
||||
Sometimes creating a mapping between nodes from the native and
|
||||
non-native models is possible, but the nodes have different properties
|
||||
that need to be normalized to allow the translation. In the example
|
||||
below, a YANG deviation is used to change the type and the default value
|
||||
from a node from the ``ietf-rip`` module.
|
||||
|
||||
.. code:: yang
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:timers/ietf-rip:flush-interval" {
|
||||
deviate replace {
|
||||
default "120";
|
||||
}
|
||||
deviate replace {
|
||||
type uint32;
|
||||
}
|
||||
}
|
||||
|
||||
The deviation modules allow the management applications to know which
|
||||
parts of the custom modules (e.g. IETF/OC) can be used to configure and
|
||||
monitor FRR.
|
||||
|
||||
In order to facilitate the process of creating YANG deviation modules,
|
||||
the *gen_yang_deviations* tool was created to automate part of the
|
||||
process. This tool creates a “not-supported” deviation for all nodes
|
||||
from the given non-native module. Example:
|
||||
|
||||
::
|
||||
|
||||
$ tools/gen_yang_deviations ietf-rip > yang/ietf/frr-deviations-ietf-rip.yang
|
||||
$ head -n 40 yang/ietf/frr-deviations-ietf-rip.yang
|
||||
deviation "/ietf-rip:clear-rip-route" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-rip:clear-rip-route/ietf-rip:input" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-rip:clear-rip-route/ietf-rip:input/ietf-rip:rip-instance" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:enabled" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:route-policy" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:default-metric" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:distance" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:triggered-update-threshold" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
Once all existing nodes are listed in the deviation module, it’s easy to
|
||||
check the deviations that need to be removed or modified. This is more
|
||||
convenient than starting with a blank deviations module and listing
|
||||
manually all nodes that need to be deviated.
|
||||
|
||||
After removing and/or modifying the auto-generated deviations, the next
|
||||
step is to write the module XPath translation table as we’ll see in the
|
||||
next section. Before that, it’s possible to use the *yanglint* tool to
|
||||
check how the non-native module looks like after applying the
|
||||
deviations. Example:
|
||||
|
||||
::
|
||||
|
||||
$ yanglint -f tree yang/ietf/ietf-rip@2018-02-03.yang yang/ietf/frr-deviations-ietf-rip.yang
|
||||
module: ietf-rip
|
||||
|
||||
augment /ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol:
|
||||
+--rw rip
|
||||
+--rw originate-default-route
|
||||
| +--rw enabled? boolean <false>
|
||||
+--rw default-metric? uint8 <1>
|
||||
+--rw distance? uint8 <0>
|
||||
+--rw timers
|
||||
| +--rw update-interval? uint32 <30>
|
||||
| +--rw holddown-interval? uint32 <180>
|
||||
| +--rw flush-interval? uint32 <120>
|
||||
+--rw interfaces
|
||||
| +--rw interface* [interface]
|
||||
| +--rw interface ietf-interfaces:interface-ref
|
||||
| +--rw split-horizon? enumeration <simple>
|
||||
+--ro ipv4
|
||||
+--ro neighbors
|
||||
| +--ro neighbor* [ipv4-address]
|
||||
| +--ro ipv4-address ietf-inet-types:ipv4-address
|
||||
| +--ro last-update? ietf-yang-types:date-and-time
|
||||
| +--ro bad-packets-rcvd? ietf-yang-types:counter32
|
||||
| +--ro bad-routes-rcvd? ietf-yang-types:counter32
|
||||
+--ro routes
|
||||
+--ro route* [ipv4-prefix]
|
||||
+--ro ipv4-prefix ietf-inet-types:ipv4-prefix
|
||||
+--ro next-hop? ietf-inet-types:ipv4-address
|
||||
+--ro interface? ietf-interfaces:interface-ref
|
||||
+--ro metric? uint8
|
||||
|
||||
rpcs:
|
||||
+---x clear-rip-route
|
||||
|
||||
..
|
||||
|
||||
NOTE: the same output can be obtained using the
|
||||
``show yang module module-translator ietf ietf-rip tree`` command in
|
||||
FRR once the *ietf* module translator is loaded.
|
||||
|
||||
In the example above, it can be seen that the vast majority of the
|
||||
*ietf-rip* nodes were removed because of the “not-supported” deviations.
|
||||
When a module translator is loaded, FRR calculates the coverage of the
|
||||
translator by dividing the number of YANG nodes before applying the
|
||||
deviations by the number of YANG nodes after applying the deviations.
|
||||
The calculated coverage is displayed in the output of the
|
||||
``show yang module-translator`` command:
|
||||
|
||||
::
|
||||
|
||||
ripd# show yang module-translator
|
||||
Family Module Deviations Coverage (%)
|
||||
-----------------------------------------------------------------------
|
||||
ietf ietf-interfaces frr-deviations-ietf-interfaces 3.92
|
||||
ietf ietf-routing frr-deviations-ietf-routing 1.56
|
||||
ietf ietf-rip frr-deviations-ietf-rip 13.60
|
||||
|
||||
As it can be seen in the output above, the *ietf* module translator
|
||||
covers only ~13% of the original *ietf-rip* module. This is in part
|
||||
because the *ietf-rip* module models both RIPv2 and RIPng. Also,
|
||||
*ietf-rip.yang* contains several knobs that aren’t implemented in *ripd*
|
||||
yet (e.g. BFD support, per-interface timers, statistics, etc). Work can
|
||||
be done over time to increase the coverage to a more reasonable number.
|
||||
|
||||
Translation Tables
|
||||
------------------
|
||||
|
||||
Below is an example of a translator for the IETF family of models:
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"frr-module-translator:frr-module-translator": {
|
||||
"family": "ietf",
|
||||
"module": [
|
||||
{
|
||||
"name": "ietf-interfaces@2018-01-09",
|
||||
"deviations": "frr-deviations-ietf-interfaces",
|
||||
"mappings": [
|
||||
{
|
||||
"custom": "/ietf-interfaces:interfaces/interface[name='KEY1']",
|
||||
"native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-interfaces:interfaces/interface[name='KEY1']/description",
|
||||
"native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/description"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ietf-routing@2018-01-25",
|
||||
"deviations": "frr-deviations-ietf-routing",
|
||||
"mappings": [
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']",
|
||||
"native": "/frr-ripd:ripd/instance"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ietf-rip@2018-02-03",
|
||||
"deviations": "frr-deviations-ietf-rip",
|
||||
"mappings": [
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric",
|
||||
"native": "/frr-ripd:ripd/instance/default-metric"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/distance",
|
||||
"native": "/frr-ripd:ripd/instance/distance/default"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/originate-default-route/enabled",
|
||||
"native": "/frr-ripd:ripd/instance/default-information-originate"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/update-interval",
|
||||
"native": "/frr-ripd:ripd/instance/timers/update-interval"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/holddown-interval",
|
||||
"native": "/frr-ripd:ripd/instance/timers/holddown-interval"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/flush-interval",
|
||||
"native": "/frr-ripd:ripd/instance/timers/flush-interval"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']",
|
||||
"native": "/frr-ripd:ripd/instance/interface[.='KEY1']"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']/split-horizon",
|
||||
"native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/frr-ripd:rip/split-horizon"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']",
|
||||
"native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/last-update",
|
||||
"native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/last-update"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-packets-rcvd",
|
||||
"native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-packets-rcvd"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-routes-rcvd",
|
||||
"native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-routes-rcvd"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']",
|
||||
"native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/next-hop",
|
||||
"native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/next-hop"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/interface",
|
||||
"native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/interface"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/metric",
|
||||
"native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/metric"
|
||||
},
|
||||
{
|
||||
"custom": "/ietf-rip:clear-rip-route",
|
||||
"native": "/frr-ripd:clear-rip-route"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
The main motivation to use YANG itself to model YANG module translators
|
||||
was a practical one: leverage *libyang* to validate the structure of the
|
||||
user input (JSON files) instead of doing that manually in the
|
||||
*lib/yang_translator.c* file (tedious and error-prone work).
|
||||
|
||||
Module translators can be loaded using the following CLI command:
|
||||
|
||||
::
|
||||
|
||||
ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json
|
||||
% Module translator "ietf" loaded successfully.
|
||||
|
||||
Module translators can also be loaded/unloaded programatically using the
|
||||
``yang_translator_load()/yang_translator_unload()`` functions within the
|
||||
northbound plugins. These functions are documented in the
|
||||
*lib/yang_translator.h* file.
|
||||
|
||||
Each module translator must be assigned a “family” identifier
|
||||
(e.g. IETF, OpenConfig), and can contain mappings for multiple
|
||||
interrelated YANG modules. The mappings consist of pairs of
|
||||
custom/native XPath expressions that should be equivalent, despite
|
||||
belonging to different YANG modules.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric",
|
||||
"native": "/frr-ripd:ripd/instance/default-metric"
|
||||
},
|
||||
|
||||
The nodes pointed by the custom and native XPaths must have compatible
|
||||
types. In the case of the example above, both nodes point to a YANG leaf
|
||||
of type ``uint8``, so the mapping is valid.
|
||||
|
||||
In the example below, the “custom” XPath points to a YANG list
|
||||
(typeless), and the “native” XPath points to a YANG leaf-list of
|
||||
strings. In this exceptional case, the types are also considered to be
|
||||
compatible.
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']",
|
||||
"native": "/frr-ripd:ripd/instance/interface[.='KEY1']"
|
||||
},
|
||||
|
||||
The ``KEY1..KEY4`` values have a special meaning and are used to
|
||||
preserve the list keys while performing the XPath translation.
|
||||
|
||||
Once a YANG module translator is loaded and validated at a syntactic
|
||||
level using *libyang*, further validations are performed to check for
|
||||
missing mappings (after loading the deviation modules) and incompatible
|
||||
YANG types. Example:
|
||||
|
||||
::
|
||||
|
||||
ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json
|
||||
% Failed to load "/usr/local/share/yang/ietf/frr-ietf-translator.json"
|
||||
|
||||
Please check the logs for more details.
|
||||
|
||||
::
|
||||
|
||||
2018/09/03 15:18:45 RIP: yang_translator_validate_cb: YANG types are incompatible (xpath: "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/default-metric")
|
||||
2018/09/03 15:18:45 RIP: yang_translator_validate_cb: missing mapping for "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/distance"
|
||||
2018/09/03 15:18:45 RIP: yang_translator_validate: failed to validate "ietf" module translator: 2 error(s)
|
||||
|
||||
Overall, this translation mechanism based on XPath mappings is simple
|
||||
and functional, but only to a certain extent. The native models need to
|
||||
be reasonably similar to the models that are going be translated,
|
||||
otherwise the translation is compromised and a good coverage can’t be
|
||||
achieved. Other translation techniques must be investigated to address
|
||||
this shortcoming and make it possible to create more powerful YANG
|
||||
module translators.
|
||||
|
||||
YANG module translators can be evaluated based on the following metrics:
|
||||
\* Translation potential: is it possible to make complex translations,
|
||||
taking several variables into account? \* Complexity: measure of how
|
||||
easy or hard it is to write a module translator. \* Speed: measure of
|
||||
how fast the translation can be achieved. Translation speed is of
|
||||
fundamental importance, especially for operational data. \* Robustness:
|
||||
can the translator be checked for inconsistencies at load time? A module
|
||||
translator based on scripts wouldn’t fare well on this metric. \*
|
||||
Round-trip conversions: can the translated data be translated back to
|
||||
the original format without information loss?
|
||||
|
||||
CLI Demonstration
|
||||
-----------------
|
||||
|
||||
As of now the only northbound client that supports the YANG module
|
||||
translator is the FRR embedded CLI. The confd and sysrepo plugins need
|
||||
to be extended to support the module translator, which might be used not
|
||||
only for configuration data, but also for operational data, RPCs and
|
||||
notifications.
|
||||
|
||||
In this demonstration, we’ll use the CLI ``configuration load`` command
|
||||
to load the following JSON configuration file specified using the IETF
|
||||
data hierarchy:
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"ietf-interfaces:interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"description": "Engineering",
|
||||
"name": "eth0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ietf-routing:routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [
|
||||
{
|
||||
"name": "main",
|
||||
"type": "ietf-rip:ripv2",
|
||||
"ietf-rip:rip": {
|
||||
"default-metric": "2",
|
||||
"distance": "80",
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"interface": "eth0",
|
||||
"split-horizon": "poison-reverse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"originate-default-route": {
|
||||
"enabled": "true"
|
||||
},
|
||||
"timers": {
|
||||
"flush-interval": "241",
|
||||
"holddown-interval": "181",
|
||||
"update-interval": "31"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
In order to load this configuration file, it’s necessary to load the
|
||||
IETF module translator first. Then, when entering the
|
||||
``configuration load`` command, the ``translate ietf`` parameters must
|
||||
be given to specify that the input needs to be translated using the
|
||||
previously loaded ``ietf`` module translator. Example:
|
||||
|
||||
::
|
||||
|
||||
ripd(config)# configuration load file json /mnt/renato/git/frr/yang/example/ietf-rip.json
|
||||
% Failed to load configuration:
|
||||
|
||||
Unknown element "interfaces".
|
||||
ripd(config)#
|
||||
ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json
|
||||
% Module translator "ietf" loaded successfully.
|
||||
|
||||
ripd(config)#
|
||||
ripd(config)# configuration load file json translate ietf /mnt/renato/git/frr/yang/example/ietf-rip.json
|
||||
|
||||
Now let’s check the candidate configuration to see if the configuration
|
||||
file was loaded successfully:
|
||||
|
||||
::
|
||||
|
||||
ripd(config)# show configuration candidate
|
||||
Configuration:
|
||||
!
|
||||
frr version 5.1-dev
|
||||
frr defaults traditional
|
||||
!
|
||||
interface eth0
|
||||
description Engineering
|
||||
ip rip split-horizon poisoned-reverse
|
||||
!
|
||||
router rip
|
||||
default-metric 2
|
||||
distance 80
|
||||
network eth0
|
||||
default-information originate
|
||||
timers basic 31 181 241
|
||||
!
|
||||
end
|
||||
ripd(config)# show configuration candidate json
|
||||
{
|
||||
"frr-interface:lib": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"vrf": "default",
|
||||
"description": "Engineering",
|
||||
"frr-ripd:rip": {
|
||||
"split-horizon": "poison-reverse"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"frr-ripd:ripd": {
|
||||
"instance": {
|
||||
"default-metric": 2,
|
||||
"distance": {
|
||||
"default": 80
|
||||
},
|
||||
"interface": [
|
||||
"eth0"
|
||||
],
|
||||
"default-information-originate": true,
|
||||
"timers": {
|
||||
"flush-interval": 241,
|
||||
"holddown-interval": 181,
|
||||
"update-interval": 31
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
As it can be seen, the candidate configuration is identical to the one
|
||||
defined in the *ietf-rip.json* file, only the structure is different.
|
||||
This means that the *ietf-rip.json* file was translated successfully.
|
||||
|
||||
The ``ietf`` module translator can also be used to do the translation in
|
||||
other direction: transform data from the native format to the IETF
|
||||
format. This is shown below by altering the output of the
|
||||
``show configuration candidate json`` command using the
|
||||
``translate ietf`` parameter:
|
||||
|
||||
::
|
||||
|
||||
ripd(config)# show configuration candidate json translate ietf
|
||||
{
|
||||
"ietf-interfaces:interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"description": "Engineering"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ietf-routing:routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [
|
||||
{
|
||||
"type": "ietf-rip:ripv2",
|
||||
"name": "main",
|
||||
"ietf-rip:rip": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"interface": "eth0",
|
||||
"split-horizon": "poison-reverse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"default-metric": 2,
|
||||
"distance": 80,
|
||||
"originate-default-route": {
|
||||
"enabled": true
|
||||
},
|
||||
"timers": {
|
||||
"flush-interval": 241,
|
||||
"holddown-interval": 181,
|
||||
"update-interval": 31
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
As expected, this output is exactly identical to the configuration
|
||||
defined in the *ietf-rip.json* file. The module translator was able to
|
||||
do a round-trip conversion without information loss.
|
||||
|
||||
Implementation Details
|
||||
----------------------
|
||||
|
||||
A different libyang context is allocated for each YANG module
|
||||
translator. This is important to avoid collisions and ensure that
|
||||
non-native data can’t be instantiated in the running and candidate
|
||||
configurations.
|
106
doc/developer/northbound/yang-tools.rst
Normal file
106
doc/developer/northbound/yang-tools.rst
Normal file
@ -0,0 +1,106 @@
|
||||
yanglint cheat sheet
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
libyang project includes a feature-rich tool called yanglint(1) for
|
||||
validation and conversion of the schemas and YANG modeled data. The
|
||||
source codes are located at /tools/lint and can be used to explore
|
||||
how an application is supposed to use the libyang library.
|
||||
yanglint(1) binary as well as its man page are installed together
|
||||
with the library itself.
|
||||
|
||||
Validate a YANG module:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ yanglint -p <yang-search-path> module.yang
|
||||
|
||||
Generate tree representation of a YANG module:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ yanglint -p <yang-search-path> -f tree module.yang
|
||||
|
||||
Validate JSON/XML instance data:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ yanglint -p <yang-search-path> module.yang data.{json,xml}
|
||||
|
||||
Convert JSON/XML instance data to another format:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ yanglint -p <yang-search-path> -f xml module.yang data.json
|
||||
$ yanglint -p <yang-search-path> -f json module.yang data.xml
|
||||
|
||||
*yanglint* also features an interactive mode which is very useful when
|
||||
needing to validate data from multiple modules at the same time. The
|
||||
*yanglint* README provides several examples:
|
||||
https://github.com/CESNET/libyang/blob/master/tools/lint/examples/README.md
|
||||
|
||||
Man page (groff):
|
||||
https://github.com/CESNET/libyang/blob/master/tools/lint/yanglint.1
|
||||
|
||||
pyang cheat sheet
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
pyang is a YANG validator, transformator and code generator, written
|
||||
in python. It can be used to validate YANG modules for correctness,
|
||||
to transform YANG modules into other formats, and to generate code
|
||||
from the modules.
|
||||
|
||||
Obtaining and installing pyang:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ git clone https://github.com/mbj4668/pyang.git
|
||||
$ cd pyang/
|
||||
$ sudo python setup.py install
|
||||
|
||||
Validate a YANG module:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ pyang --ietf -p <yang-search-path> module.yang
|
||||
|
||||
Generate tree representation of a YANG module:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ pyang -f tree -p <yang-search-path> module.yang
|
||||
|
||||
Indent a YANG file:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ pyang -p <yang-search-path> \
|
||||
--keep-comments -f yang --yang-canonical \
|
||||
module.yang -o module.yang
|
||||
|
||||
Generate skeleton instance data: \* XML:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ pyang -p <yang-search-path> \
|
||||
-f sample-xml-skeleton --sample-xml-skeleton-defaults \
|
||||
module.yang [augmented-module1.yang ...] -o module.xml
|
||||
|
||||
- JSON:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ pyang -p <yang-search-path> \
|
||||
-f jsonxsl module.yang -o module.xsl
|
||||
$ xsltproc -o module.json module.xsl module.xml
|
||||
|
||||
Validate XML instance data (works only with YANG 1.0):
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ yang2dsdl -v module.xml module.yang
|
||||
|
||||
vim
|
||||
~~~
|
||||
|
||||
YANG syntax highlighting for vim:
|
||||
https://github.com/nathanalderson/yang.vim
|
@ -67,6 +67,19 @@ dev_RSTFILES = \
|
||||
doc/developer/workflow.rst \
|
||||
doc/developer/xrefs.rst \
|
||||
doc/developer/zebra.rst \
|
||||
doc/developer/northbound/advanced-topics.rst \
|
||||
doc/developer/northbound/architecture.rst \
|
||||
doc/developer/northbound/demos.rst \
|
||||
doc/developer/northbound/links.rst \
|
||||
doc/developer/northbound/northbound.rst \
|
||||
doc/developer/northbound/operational-data-rpcs-and-notifications.rst \
|
||||
doc/developer/northbound/plugins-sysrepo.rst \
|
||||
doc/developer/northbound/ppr-basic-test-topology.rst \
|
||||
doc/developer/northbound/ppr-mpls-basic-test-topology.rst \
|
||||
doc/developer/northbound/retrofitting-configuration-commands.rst \
|
||||
doc/developer/northbound/transactional-cli.rst \
|
||||
doc/developer/northbound/yang-module-translator.rst \
|
||||
doc/developer/northbound/yang-tools.rst \
|
||||
# end
|
||||
|
||||
EXTRA_DIST += \
|
||||
|
Loading…
Reference in New Issue
Block a user