Internet-Draft | YANG Full Embed | July 2024 |
Quilbeuf, et al. | Expires 6 January 2025 | [Page] |
YANG lacks re-usability of models defined outside of the grouping and augmentation mechanisms. For instance, it is almost impossible to reuse a model defined for a device in the context of the network, i.e by encapsulating it in a list indexed by device IDs. [RFC8528] defines the YANG mount mechanism, partially solving the problem by allowing to mount an arbitrary set of schemas at an arbitrary point. However, YANG mount is only focusing on deploy or runtime. This document aims to provide the same mechanism at design time.¶
This note is to be removed before publishing as an RFC.¶
Source for this draft and issue tracker can be found on github.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 6 January 2025.¶
Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Section 1 of [RFC8528] introduces the challenges of reusing existing YANG modules, especially when including the full subtree of YANG module under a specific node of another module. In that RFC, three different phases of data model life cycle are identified: "design time", "implementation time" and "run time". Only the last two are covered. We focus here on the first phase of the life cycle, that is inserting modules at design time.¶
We identified some use cases that require this design time definition of which modules need to be included in the top-level module. They have in common the need to re-use YANG modules defined for the devices in the context of a network-level module. Also, they both aim to define a model that is independent of the underlying devices.¶
YANG Schema Mount [RFC8528] and Peer Mount [I-D.clemm-netmod-peermount] focus on mounting a given part of an existing data instance into another data instance. Although the final goal is the same: being able to reuse modules defined elsewhere in order to avoid redefining them, the approach is more focused on the runtime than the design time. In the first case, the mapping between the mount points and the existing modules to be mounted at that mount point is left to the NETCONF [RFC6241] server. Thus, to guarantee that the contents under a given mount point conforms to a predefined schema requires the proper configuration of the server. In the case of Peer mount, the focus is on synchronizing a given subtree of a server (remote or local) with a subtree of the local server. Again, the contents under the local subtree cannot be enforced from the design time.¶
The notion of reusing an existing schema within a new schema is not new. Several schema definition languages propose this feature, such as RELAX NG, Protobuf or json-schema.¶
In this document, we propose a new extension, named full embed. This extension enables reusing imported modules by rooting them at an arbitrary point of the data model. The concept of mount point from [RFC8528] is replaced by an anydata statement containing list of "full:embed" statement, each statement corresponding to the inclusion of one imported module at that location. In that sense, the design time solution is a pure YANG solution that does not rely on external configuration to specify the list of mounted modules, hence the term full embed rather than mount. Also, we use 'embed' not to conflict with the native 'include' statement in YANG [RFC7950].¶
The obtained data model that we want to associate to our construct is similar to the one obtained by specifying a mount point and binding it to the same set of modules. Therefore, we can reuse the concepts of the YANG schema mount to define the semantics of our new extension.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The following terms are defined in [RFC7950]:¶
The following terms are defined in [RFC8528]:¶
This document defines the following terms:¶
As said above, the full embed mechanism defined in this document completes [RFC8528], by providing a mechanism to "mount" modules at design time, which is left out of scope in [RFC8528].¶
In [RFC8528], the list of modules to mount in each mount point is left to the NETCONF server. In this document, we propose the full embed mechanism to define this mapping directly in the embedding YANG module, by listing the modules to implement in each embedding-point.¶
The approach for supporting the full embed mechanism is to keep the semantics of [RFC8528] for the resulting data model:¶
To ensure interoperability with clients that do not support the full embed extension, the full embed statement can only appear within an anydata node. Clients that do not support the extension will see the contents of the embedded model as arbitrary data. Clients that support the extension will be able to interpret the contents of the anydata node according to the semantics of the embedded YANG modules.¶
In the sequel, we use "full" as the prefix for the module 'ietf-yang-full-embed' (see Section 5). Thus, "full:embed" refers to the extension 'embed' defined in that module.¶
The "full:embed" statement MAY appear as a sub-statement of anydata and MUST not appear anywhere else.¶
The "full:embed" statement takes a prefix as argument. That prefix MUST be the prefix associated to an imported module. Modules can contain multiple uses of the "full:embed" statement. An "anydata" statement MAY contain multiple uses of the "full:embed" statement. These multiple uses define the full list of modules to be embedded, rooted in the anydata node where the "full:embed" statement is used.¶
The "full:embed" statement can be interpreted using YANG Schema Mount [RFC8528], by following these steps:¶
As a consequence, a module embedded in a given embedding point can only refer to other modules embedded in the same embedding point (i.e. via a leafref, must, augment, deviate, when or any other YANG statement requiring a path). Embedding a module that refers to a node defined outside of modules embedded in the same embedding point MUST trigger a compilation error.¶
A module MUST NOT use the "full:embed" statement with its own prefix as argument. This rule prevents any infinite recursion in the embedded schemas. See Section 4.2 for more details.¶
An example of module using "full:embed" and its translation into a similar YANG Schema mount version is presented in Appendix B.¶
The following sub-statements are allowed in the "full:embed" statement:¶
Both statements have the same meaning as in [RFC7950]. The when statement MUST NOT refer to nodes which are in the embedded module designated by the "full:embed" statement.¶
We call recursive embedding the case where a schema is embedded into a sub node of itself. Recursive embedding is an issue as it creates an infinite data model, in the sense that the tree representation [RFC8340] of the data model would be infinite. Recursive embedding MUST be rejected by the YANG compiler. An example, not allowed here, of use case for recursive embedding would be a module defining an expression, where the module embeds itself in each place where a sub-expression is needed. Such a pattern mimics the grammar where the non-terminal for an expression is reused in each place where a sub-expression is needed.¶
It is however allowed to define multiple embedding levels, as in the case where module A embeds module B which embeds module C. This pattern is also allowed in YANG Schema Mount as explained in Section 3.4 of [RFC8528].¶
In this section, we argue that standard YANG rules combined with the extension presented in this document do not allow recursive embeddings. In order to have a recursive embedding, it is necessary to embed the data model as a sub-node of itself. In other world, we need to construct a set of YANG modules such that resolving augments, deviations and grouping yields a data model with one anydata node embedding this set of YANG modules. Let’s assume that such a set exists and show that we have a contradiction.¶
As specified in Section 4, embedded modules must be imported by the embedding module. This creates a dependency from the embedding module to the embedded modules. Note that indirect dependencies also cover the case of multiple embedding levels presented in the second paragraph of this Section. Similarly, every module augmenting, deviating or reusing a grouping from a module using the full:embed statement will depend on the modules embedded by these statements and cannot be imported by these embedded modules.¶
As specified in Section 4, a module cannot embed itself. Since every module in the set is embedded, and cannot be embedded by itself, it is necessarily a dependency on another module of the set. Therefore, by following that inverse dependency relation we would always find a next node and eventually discover a dependency loop. Since YANG prohibits circular dependencies, the set of modules creating a recursive embedding would not be accepted by the compiler.¶
We present in this section the YANG module defining the "full-embed" extension. The module in itself defines solely the 'embed' extension. A module importing this extension SHOULD use the prefix 'full', so that the statement reads "full:embed" when used in the code.¶
<CODE BEGINS> file "[email protected]" module ietf-yang-full-embed { yang-version 1.1; namespace "urn:ietf:params:xml:ns:yang:ietf-yang-full-embed"; prefix full; organization "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; contact "WG Web: <https://datatracker.ietf.org/wg/netmod/> WG List: <mailto:[email protected]> Editor: "; description "This module defines a YANG extension statement that can be used to incorporate data models defined in other YANG modules in a module. The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document are to be interpreted as described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, they appear in all capitals, as shown here. Copyright (c) 2023 IETF Trust and the persons identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, is permitted pursuant to, and subject to the license terms contained in, the Revised BSD License set forth in Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info). This version of this YANG module is part of RFC XXXX; see the RFC itself for full legal notices."; revision 2023-11-05 { description "Initial revision."; reference "RFC XXXX: YANG Full Embed"; } extension embed { argument prefix; description "The argument 'prefix' MUST be the prefix of a module imported by the calling module. The 'embed' statement MUST NOT be used in a YANG version 1 module, neither explicitly nor via a 'uses' statement. The 'embed' statement MAY be present as a substatement of 'anydata' and MUST NOT be present elsewhere. Whenever a sequence of 'embed' statements is used, the schema tree defined by the set of the included modules is inserted in the schema tree of the calling module, at the place where the sequence is declared"; } } <CODE ENDS>¶
The YANG Library model [RFC8525] provides the list of YANG modules, along with their features and deviations, supported by a given server. In this data model, specifying the supported YANG modules is done by grouping them into module sets, then grouping these module sets into schemas and finally assigning schemas to the supported datastores. As stated in Section 4, the list of full:embed statement defines a schema in the YANG library model. In this section, we augment the YANG library module to specify the mapping of these schemas to each embedding point.¶
As a result, the global schema for a given datastore is split into the "root" schema as existing before this extension and the schemas for all embedding points defined under that root schema. Modules that are embedded appear in at least twice of these schemas. Modules that are both embedded and implemented in the "root" schema will appear as implemented in both of the corresponding schemas. The server might assign different features in each case. Modules that are only implemented in the embedded part appear as imported, i.e. in the 'import-only-module' list, for the "root" schema and as implemented in the schema mapped to the embedding point in which they belong.¶
The module ietf-full-embed-library augments the yang-library container to include the mapping of schemas to embedding points. We present in Figure 1 the tree representation of the augmented yang-library container according to [RFC8340].¶
The mapping of schemas to embedding points is done in the 'embedding-points' list. The keys of that list are identifying an embedding point by specifying the datastore and the path to the embedding-point. The path, stored in 'embedding-path' MUST be an absolute path, and MUST NOT contain any predicates. Finally, the 'schema' leaf-ref points to the schema associated to the embedding point defined by the datastore and the path.¶
We present below the YANG module augmenting the ietf-yang-library module ([RFC8525]).¶
<CODE BEGINS> file "[email protected]" module ietf-yang-full-embed-library { yang-version 1.1; namespace "urn:ietf:params:xml:ns:yang:ietf-yang-full-embed-library"; prefix emblib; import ietf-datastores { prefix ds; reference "RFC 8342: Network Management Datastore Architecture (NMDA)"; } import ietf-yang-library { prefix yanglib; reference "RFC 8525: YANG Library"; } import ietf-yang-types { prefix yang; reference "RFC 6991: Common YANG Data Types"; } organization "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; contact "WG Web: <https://datatracker.ietf.org/wg/netmod/> WG List: <mailto:[email protected]> Editor: "; description "This module augments the ietf-yang-library module to indicate which modules are available in each embedding point. The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document are to be interpreted as described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, they appear in all capitals, as shown here. Copyright (c) 2023 IETF Trust and the persons identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, is permitted pursuant to, and subject to the license terms contained in, the Revised BSD License set forth in Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info). This version of this YANG module is part of RFC XXXX; see the RFC itself for full legal notices."; revision 2023-11-05 { description "Initial revision."; reference "RFC XXXX: YANG Full Embed"; } augment "/yanglib:yang-library" { description "Add mapping for embedding points"; list embedding-points { key "datastore embedding-path"; description "Mapping of each embedding point to its schema. An embedding-point is defined by a datastore and a YANG path to the anydata node containing the embedded schema."; leaf datastore { type ds:datastore-ref; description "Identity of the datastore containing the embedding point"; } leaf embedding-path { type yang:xpath1.0; description "Path to the embedding point in the datastore. The XPath must be an absolute path. It is evaluated in the context of the specified datastore."; } leaf schema { type leafref { path "../../yanglib:schema/yanglib:name"; } mandatory true; description "A reference to the schema supported by the specified embedding point. All non-import modules of the schema are implemented in the given embedding point with their associated features and deviations."; } } } } <CODE ENDS>¶
TODO¶
YANG Schema Mount includes a mechanism to make some nodes from the embedding model available to the embedded model for validation purposes. We could achieve the same by adding a second extension, which can also only appear under a "full:embed" nodes. That extension, for instance named "full:embed-parent-refs" would take a Xpath expression as the in the "parent-reference" leaflist defined in the YANG Schema Mount and would have the same semantics. If several XPath are needed for clarity, the statement can be repeated with several values.¶
As an example, Figure 2 restates the parent-references example from [RFC8528] using this new extension. We might want to put some restrictions on the nodes that can be referred to in the Xpath argument.¶
01 -> 02¶
00 -> 01¶
In this section we present some minimalistic examples in order to illustrate the "full:embed" statement. For these examples, we are in a situation where we have a device-level module already defined and we want to have a network-level module that represent a list of device, each having an independent instance of the device-level module. This situation might arise if we want to simplify the network management by presenting a unified model for the network. In that case, the heterogeneity of the devices should be handled by mapping their model to the device-level module (which is clearly out of scope for this draft).¶
In our simplistic example, the device-level module simply exposes the hostname and the cpu-usage of the device. Note that we cannot modify this device-level module, because in a more realistic example we would be reusing standard modules. The tree representation ([RFC8340]) of the 'device-level' module is depicted in Figure 3.¶
For the network-level module, we have a list of devices indexed by their 'device-id'. The tree representation ([RFC8340]) of such a module is depicted in Figure 4.¶
The goal is now to complete this stub so that the full contents of the 'device-level' is added under the "device" list.¶
We propose in this section a YANG module for 'network-level'. The YANG code is presented in Figure 5.¶
At the moment, this code is accepted by the YANG compilers, but since the extension is not implemented, it simply ignores it. Note that all the information (which modules to embed, where to embed them) is defined in this module. More specifically, the line 'full:embed "dev-l";' states that the full schema of the 'device-level' module, identified by its prefix "dev-l" must be embedded at that location. By adding more occurrences of "full:embed" there, one can define a more complex schema to be embedded at that location.¶
In this section, we show how a similar result could be attained using YANG Schema Mount. The network-level module is presented in Figure 6.¶
As explained in Section 4, the yang-library corresponding to the modules to embed, as well as the data required by 'ietf-yang-mount' needs to be specified in some other files. Using the 'yanglint' tool from libyang (https://github.com/CESNET/libyang), this module can be compiled to provide a tree representation as shown in Figure 7.¶
The command for obtaining that schema is 'yanglint -f tree -p . -x extension-data.xml -Y network-level-yanglib.xml yang/network-level.yang', assuming all the YANG modules and the two xml files are in the current folder. The file 'network-level-yanglib.xml' contains the YANG Library data for the network-level module. The file 'extension-data.xml' contains the YANG Library data defining the schema to use at the mount point, as well as the data required by YANG Schema Mount. Both are reproduced in Appendix B.3.¶
The code of the 'device-level' module is given in Figure 8. Then the data files 'network-level-yanglib.xml' and 'extension_data.xml' are provided. These files are needed to compile the Schema Mount version of our example with yanglint.¶
<CODE BEGINS> file "network-level-yanglib.xml" <yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library" xmlns:ds="urn:ietf:params:xml:ns:yang:ietf-datastores"> <module-set> <name>main-set</name> <module> <name>ietf-datastores</name> <revision>2018-02-14</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-datastores </namespace> </module> <module> <name>ietf-yang-library</name> <revision>2019-01-04</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-yang-library </namespace> </module> <module> <name>ietf-yang-schema-mount</name> <revision>2019-01-14</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount </namespace> </module> <module> <name>network-level</name> <namespace>urn:network-level</namespace> </module> <import-only-module> <name>ietf-yang-types</name> <revision>2013-07-15</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-yang-types </namespace> </import-only-module> <import-only-module> <name>ietf-inet-types</name> <revision>2013-07-15</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-inet-types </namespace> </import-only-module> </module-set> <schema> <name>main-schema</name> <module-set>main-set</module-set> </schema> <datastore> <name>ds:running</name> <schema>main-schema</schema> </datastore> <datastore> <name>ds:operational</name> <schema>main-schema</schema> </datastore> <content-id>1</content-id> </yang-library> <modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> <module-set-id>2</module-set-id> </modules-state> <CODE ENDS>¶
<CODE BEGINS> file "extension_data.xml" <yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library" xmlns:ds="urn:ietf:params:xml:ns:yang:ietf-datastores"> <module-set> <name>mountee-set</name> <module> <name>device-level</name> <namespace>urn:device-level</namespace> </module> <module> <name>ietf-datastores</name> <revision>2018-02-14</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-datastores </namespace> </module> <module> <name>ietf-yang-library</name> <revision>2019-01-04</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-yang-library </namespace> </module> <import-only-module> <name>ietf-yang-types</name> <revision>2013-07-15</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-yang-types </namespace> </import-only-module> <import-only-module> <name>ietf-inet-types</name> <revision>2013-07-15</revision> <namespace> urn:ietf:params:xml:ns:yang:ietf-inet-types </namespace> </import-only-module> </module-set> <schema> <name>test-schema</name> <module-set>mountee-set</module-set> </schema> <datastore> <name>ds:running</name> <schema>test-schema</schema> </datastore> <datastore> <name>ds:operational</name> <schema>test-schema</schema> </datastore> <content-id>2</content-id> </yang-library> <modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> <module-set-id>2</module-set-id> </modules-state> <schema-mounts xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"> <mount-point> <module>network-level</module> <label>device-schema</label> <shared-schema/> </mount-point> </schema-mounts> <CODE ENDS>¶
Thanks to Ladislav Lhotka, Ignacio Dominguez Martinez-Casanueva and Andy Bierman for their reviews and comments.¶