Skip to content

Commit 1d730df

Browse files
authored
RFC7895 yang module library implementation (sonic-net#15)
Added new yang ietf-yang-library.yang, which defines data models for RFC7895 yang library discovery feature. New app module yanglib_app.go to handle ietf-yang-library.yang APIs. REST and gNMI requests for '/ietf-yang-library:modules-state' and its child paths will be serviced by this app module. Parses all NB yangs using goyang parser and builds the module info data tree as per ietf-yang-library.yang. Yangs are parsed upon first request and cached thereafter. Uses translib.GetModel() API to identify the yang modules that are implemented by apps. Other modules are marked as 'import' in yang library response data. Transformer annotation files (*_annot.yang) are ignored -- they do not define any data model. Uses hardcoded module-set-id value for now. Should be changed to use "yang bundle version" number when yang versioning feature is in. Yang schema URL is included in the response only if main program set the root URL through translib.SetSchemaRootURL() API. Schema URL is prepared by appending the yang file name to the root URL.
1 parent cc01ce4 commit 1d730df

File tree

7 files changed

+1000
-5
lines changed

7 files changed

+1000
-5
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ $(GOYANG_BIN): $(GO_DEPS)
8181
cd vendor/github.com/openconfig/goyang && \
8282
$(GO) build -o $@ *.go
8383

84-
clean: models-clean translib-clean cvl-clean
84+
clean: models-clean translib-clean cvl-clean go-deps-clean
8585
git check-ignore debian/* | xargs -r $(RM) -r
8686
$(RM) -r $(BUILD_DIR)
8787

88-
cleanall: clean go-deps-clean
88+
cleanall: clean
8989
git clean -fdX tools

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
3535
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
3636
github.com/golang/protobuf v1.4.0-rc.4 h1:+EOh4OY6tjM6ZueeUKinl1f0U2820HzQOuf1iqMnsks=
3737
github.com/golang/protobuf v1.4.0-rc.4/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
38+
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 h1:aRz0NBceriICVtjhCgKkDvl+RudKu1CT6h0ZvUTrNfE=
3839
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
3940
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
4041
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -121,6 +122,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
121122
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
122123
google.golang.org/protobuf v1.20.1 h1:ESRXHgpUBG5D2I5mmsQIyYxB/tQIZfSZ8wLyFDf/N/U=
123124
google.golang.org/protobuf v1.20.1/go.mod h1:KqelGeouBkcbcuB3HCk4/YH2tmNLk6YSWA5LIWeI/lY=
125+
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
124126
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
125127
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
126128
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

models/yang/ietf-yang-library.yang

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
module ietf-yang-library {
2+
namespace "urn:ietf:params:xml:ns:yang:ietf-yang-library";
3+
prefix "yanglib";
4+
5+
import ietf-yang-types {
6+
prefix yang;
7+
}
8+
import ietf-inet-types {
9+
prefix inet;
10+
}
11+
12+
organization
13+
"IETF NETCONF (Network Configuration) Working Group";
14+
15+
contact
16+
"WG Web: <https://datatracker.ietf.org/wg/netconf/>
17+
WG List: <mailto:[email protected]>
18+
19+
WG Chair: Mehmet Ersue
20+
21+
22+
WG Chair: Mahesh Jethanandani
23+
24+
25+
Editor: Andy Bierman
26+
27+
28+
Editor: Martin Bjorklund
29+
30+
31+
Editor: Kent Watsen
32+
<mailto:[email protected]>";
33+
34+
description
35+
"This module contains monitoring information about the YANG
36+
modules and submodules that are used within a YANG-based
37+
server.
38+
39+
Copyright (c) 2016 IETF Trust and the persons identified as
40+
authors of the code. All rights reserved.
41+
42+
Redistribution and use in source and binary forms, with or
43+
without modification, is permitted pursuant to, and subject
44+
to the license terms contained in, the Simplified BSD License
45+
set forth in Section 4.c of the IETF Trust's Legal Provisions
46+
Relating to IETF Documents
47+
(http://trustee.ietf.org/license-info).
48+
49+
This version of this YANG module is part of RFC 7895; see
50+
the RFC itself for full legal notices.";
51+
52+
revision 2016-06-21 {
53+
description
54+
"Initial revision.";
55+
reference
56+
"RFC 7895: YANG Module Library.";
57+
}
58+
59+
/*
60+
* Typedefs
61+
*/
62+
63+
typedef revision-identifier {
64+
type string {
65+
pattern '\d{4}-\d{2}-\d{2}';
66+
}
67+
description
68+
"Represents a specific date in YYYY-MM-DD format.";
69+
}
70+
71+
/*
72+
* Groupings
73+
*/
74+
75+
grouping module-list {
76+
description
77+
"The module data structure is represented as a grouping
78+
so it can be reused in configuration or another monitoring
79+
data structure.";
80+
81+
grouping common-leafs {
82+
description
83+
"Common parameters for YANG modules and submodules.";
84+
85+
leaf name {
86+
type yang:yang-identifier;
87+
description
88+
"The YANG module or submodule name.";
89+
}
90+
leaf revision {
91+
type union {
92+
type revision-identifier;
93+
type string { length 0; }
94+
}
95+
description
96+
"The YANG module or submodule revision date.
97+
A zero-length string is used if no revision statement
98+
is present in the YANG module or submodule.";
99+
}
100+
}
101+
102+
grouping schema-leaf {
103+
description
104+
"Common schema leaf parameter for modules and submodules.";
105+
106+
leaf schema {
107+
type inet:uri;
108+
description
109+
"Contains a URL that represents the YANG schema
110+
resource for this module or submodule.
111+
112+
This leaf will only be present if there is a URL
113+
available for retrieval of the schema for this entry.";
114+
}
115+
}
116+
117+
list module {
118+
key "name revision";
119+
description
120+
"Each entry represents one revision of one module
121+
currently supported by the server.";
122+
123+
uses common-leafs;
124+
uses schema-leaf;
125+
126+
leaf namespace {
127+
type inet:uri;
128+
mandatory true;
129+
description
130+
"The XML namespace identifier for this module.";
131+
}
132+
leaf-list feature {
133+
type yang:yang-identifier;
134+
description
135+
"List of YANG feature names from this module that are
136+
supported by the server, regardless of whether they are
137+
defined in the module or any included submodule.";
138+
}
139+
list deviation {
140+
key "name revision";
141+
description
142+
"List of YANG deviation module names and revisions
143+
used by this server to modify the conformance of
144+
the module associated with this entry. Note that
145+
the same module can be used for deviations for
146+
multiple modules, so the same entry MAY appear
147+
within multiple 'module' entries.
148+
149+
The deviation module MUST be present in the 'module'
150+
list, with the same name and revision values.
151+
The 'conformance-type' value will be 'implement' for
152+
the deviation module.";
153+
uses common-leafs;
154+
}
155+
leaf conformance-type {
156+
type enumeration {
157+
enum implement {
158+
description
159+
"Indicates that the server implements one or more
160+
protocol-accessible objects defined in the YANG module
161+
identified in this entry. This includes deviation
162+
statements defined in the module.
163+
164+
For YANG version 1.1 modules, there is at most one
165+
module entry with conformance type 'implement' for a
166+
particular module name, since YANG 1.1 requires that,
167+
at most, one revision of a module is implemented.
168+
169+
For YANG version 1 modules, there SHOULD NOT be more
170+
than one module entry for a particular module name.";
171+
}
172+
enum import {
173+
description
174+
"Indicates that the server imports reusable definitions
175+
from the specified revision of the module but does
176+
not implement any protocol-accessible objects from
177+
this revision.
178+
179+
Multiple module entries for the same module name MAY
180+
exist. This can occur if multiple modules import the
181+
same module but specify different revision dates in
182+
the import statements.";
183+
}
184+
}
185+
mandatory true;
186+
description
187+
"Indicates the type of conformance the server is claiming
188+
for the YANG module identified by this entry.";
189+
}
190+
list submodule {
191+
key "name revision";
192+
description
193+
"Each entry represents one submodule within the
194+
parent module.";
195+
uses common-leafs;
196+
uses schema-leaf;
197+
}
198+
}
199+
}
200+
201+
/*
202+
* Operational state data nodes
203+
*/
204+
205+
container modules-state {
206+
config false;
207+
description
208+
"Contains YANG module monitoring information.";
209+
210+
leaf module-set-id {
211+
type string;
212+
mandatory true;
213+
description
214+
"Contains a server-specific identifier representing
215+
the current set of modules and submodules. The
216+
server MUST change the value of this leaf if the
217+
information represented by the 'module' list instances
218+
has changed.";
219+
}
220+
221+
uses module-list;
222+
}
223+
224+
/*
225+
* Notifications
226+
*/
227+
228+
notification yang-library-change {
229+
description
230+
"Generated when the set of modules and submodules supported
231+
by the server has changed.";
232+
leaf module-set-id {
233+
type leafref {
234+
path "/yanglib:modules-state/yanglib:module-set-id";
235+
}
236+
mandatory true;
237+
description
238+
"Contains the module-set-id value representing the
239+
set of modules and submodules supported at the server at
240+
the time the notification is generated.";
241+
}
242+
}
243+
244+
}
245+

translib/path_utils.go

+22-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ type PathInfo struct {
4242
Vars map[string]string
4343
}
4444

45+
// HasVar checks if the PathInfo contains given variable.
46+
func (p *PathInfo) HasVar(name string) bool {
47+
_, exists := p.Vars[name]
48+
return exists
49+
}
50+
4551
// Var returns the string value for a path variable. Returns
4652
// empty string if no such variable exists.
4753
func (p *PathInfo) Var(name string) string {
@@ -91,6 +97,14 @@ func NewPathInfo(path string) *PathInfo {
9197

9298
name := readUntil(r, '=')
9399
value := readUntil(r, ']')
100+
101+
// Handle duplicate parameter names by suffixing "#N" to it.
102+
// N is the number of occurance of that parameter name from left.
103+
namePrefix := name
104+
for k := 2; info.HasVar(name); k++ {
105+
name = fmt.Sprintf("%s#%d", namePrefix, k)
106+
}
107+
94108
if len(name) != 0 {
95109
fmt.Fprintf(&template, "{}")
96110
info.Vars[name] = value
@@ -104,12 +118,17 @@ func NewPathInfo(path string) *PathInfo {
104118

105119
func readUntil(r *strings.Reader, delim byte) string {
106120
var buff strings.Builder
121+
var escaped bool
122+
107123
for {
108124
c, err := r.ReadByte()
109-
if err == nil && c != delim {
110-
buff.WriteByte(c)
111-
} else {
125+
if err != nil || (c == delim && !escaped) {
112126
break
127+
} else if c == '\\' && !escaped {
128+
escaped = true
129+
} else {
130+
escaped = false
131+
buff.WriteByte(c)
113132
}
114133
}
115134

translib/path_utils_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,68 @@ func TestGetObjectFieldName(t *testing.T) {
162162
}
163163
}
164164
}
165+
166+
func TestNewPathInfo_empty(t *testing.T) {
167+
testPathInfo(t, "", "", mkmap())
168+
}
169+
170+
func TestNewPathInfo_novar(t *testing.T) {
171+
testPathInfo(t, "/test/simple", "/test/simple", mkmap())
172+
}
173+
174+
func TestNewPathInfo_var1(t *testing.T) {
175+
testPathInfo(t, "/test/xx[one=1]", "/test/xx{}", mkmap("one", "1"))
176+
}
177+
178+
func TestNewPathInfo_vars(t *testing.T) {
179+
testPathInfo(t, "/test/xx[one=1][two=2]/new[three=3]", "/test/xx{}{}/new{}",
180+
mkmap("one", "1", "two", "2", "three", "3"))
181+
}
182+
183+
func TestNewPathInfo_dup1(t *testing.T) {
184+
testPathInfo(t, "/test/xx[one=1][two=2]/new[one=0001]", "/test/xx{}{}/new{}",
185+
mkmap("one", "1", "two", "2", "one#2", "0001"))
186+
}
187+
188+
func TestNewPathInfo_dups(t *testing.T) {
189+
testPathInfo(t, "/test/one[xx=1]/two[yy=2]/three[xx=3]/four[zz=4]/five[yy=5]/six[xx=6]",
190+
"/test/one{}/two{}/three{}/four{}/five{}/six{}",
191+
mkmap("xx", "1", "yy", "2", "xx#2", "3", "zz", "4", "yy#2", "5", "xx#3", "6"))
192+
}
193+
194+
func TestNewPathInfo_escaped_name(t *testing.T) {
195+
testPathInfo(t, "/test/xx[one\\==1][two[\\]=2]", "/test/xx{}{}",
196+
mkmap("one=", "1", "two[]", "2"))
197+
}
198+
199+
func TestNewPathInfo_escaped_valu(t *testing.T) {
200+
testPathInfo(t, "/test/xx[one=[1\\]][two=\\0\\02 [\\.\\D]", "/test/xx{}{}",
201+
mkmap("one", "[1]", "two", "002 [.D"))
202+
}
203+
204+
func testPathInfo(t *testing.T, path, expTemplate string, expVars map[string]string) {
205+
info := NewPathInfo(path)
206+
if info == nil {
207+
t.Errorf("NewPathInfo() returned null!")
208+
} else if info.Path != path {
209+
t.Errorf("Expected info.Path = %s", path)
210+
t.Errorf("Actual info.Path = %s", info.Path)
211+
} else if info.Template != expTemplate {
212+
t.Errorf("Expected info.Template = %s", expTemplate)
213+
t.Errorf("Actual info.Template = %s", info.Template)
214+
} else if reflect.DeepEqual(info.Vars, expVars) == false {
215+
t.Errorf("Expected info.Vars = %v", expVars)
216+
t.Errorf("Actual info.Vars = %v", info.Vars)
217+
}
218+
if t.Failed() {
219+
t.Fatalf("NewPathInfo() failed to parse \"%s\"", path)
220+
}
221+
}
222+
223+
func mkmap(args ...string) map[string]string {
224+
m := make(map[string]string)
225+
for i := 0; (i + 1) < len(args); i += 2 {
226+
m[args[i]] = args[i+1]
227+
}
228+
return m
229+
}

0 commit comments

Comments
 (0)