diff --git a/Makefile b/Makefile
index d1b0bf18b385..8d8bb8420597 100644
--- a/Makefile
+++ b/Makefile
@@ -81,9 +81,9 @@ $(GOYANG_BIN): $(GO_DEPS)
cd vendor/github.com/openconfig/goyang && \
$(GO) build -o $@ *.go
-clean: models-clean translib-clean cvl-clean
+clean: models-clean translib-clean cvl-clean go-deps-clean
git check-ignore debian/* | xargs -r $(RM) -r
$(RM) -r $(BUILD_DIR)
-cleanall: clean go-deps-clean
+cleanall: clean
git clean -fdX tools
diff --git a/go.sum b/go.sum
index 77b875b4d8cf..4f0831d843fe 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4 h1:+EOh4OY6tjM6ZueeUKinl1f0U2820HzQOuf1iqMnsks=
github.com/golang/protobuf v1.4.0-rc.4/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 h1:aRz0NBceriICVtjhCgKkDvl+RudKu1CT6h0ZvUTrNfE=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.20.1 h1:ESRXHgpUBG5D2I5mmsQIyYxB/tQIZfSZ8wLyFDf/N/U=
google.golang.org/protobuf v1.20.1/go.mod h1:KqelGeouBkcbcuB3HCk4/YH2tmNLk6YSWA5LIWeI/lY=
+google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
diff --git a/models/yang/ietf-yang-library.yang b/models/yang/ietf-yang-library.yang
new file mode 100644
index 000000000000..7d84e64ed880
--- /dev/null
+++ b/models/yang/ietf-yang-library.yang
@@ -0,0 +1,245 @@
+module ietf-yang-library {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-yang-library";
+ prefix "yanglib";
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import ietf-inet-types {
+ prefix inet;
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "WG Web:
+ WG List:
+
+ WG Chair: Mehmet Ersue
+
+
+ WG Chair: Mahesh Jethanandani
+
+
+ Editor: Andy Bierman
+
+
+ Editor: Martin Bjorklund
+
+
+ Editor: Kent Watsen
+ ";
+
+ description
+ "This module contains monitoring information about the YANG
+ modules and submodules that are used within a YANG-based
+ server.
+
+ Copyright (c) 2016 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 Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 7895; see
+ the RFC itself for full legal notices.";
+
+ revision 2016-06-21 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7895: YANG Module Library.";
+ }
+
+ /*
+ * Typedefs
+ */
+
+ typedef revision-identifier {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ description
+ "Represents a specific date in YYYY-MM-DD format.";
+ }
+
+ /*
+ * Groupings
+ */
+
+ grouping module-list {
+ description
+ "The module data structure is represented as a grouping
+ so it can be reused in configuration or another monitoring
+ data structure.";
+
+ grouping common-leafs {
+ description
+ "Common parameters for YANG modules and submodules.";
+
+ leaf name {
+ type yang:yang-identifier;
+ description
+ "The YANG module or submodule name.";
+ }
+ leaf revision {
+ type union {
+ type revision-identifier;
+ type string { length 0; }
+ }
+ description
+ "The YANG module or submodule revision date.
+ A zero-length string is used if no revision statement
+ is present in the YANG module or submodule.";
+ }
+ }
+
+ grouping schema-leaf {
+ description
+ "Common schema leaf parameter for modules and submodules.";
+
+ leaf schema {
+ type inet:uri;
+ description
+ "Contains a URL that represents the YANG schema
+ resource for this module or submodule.
+
+ This leaf will only be present if there is a URL
+ available for retrieval of the schema for this entry.";
+ }
+ }
+
+ list module {
+ key "name revision";
+ description
+ "Each entry represents one revision of one module
+ currently supported by the server.";
+
+ uses common-leafs;
+ uses schema-leaf;
+
+ leaf namespace {
+ type inet:uri;
+ mandatory true;
+ description
+ "The XML namespace identifier for this module.";
+ }
+ leaf-list feature {
+ type yang:yang-identifier;
+ description
+ "List of YANG feature names from this module that are
+ supported by the server, regardless of whether they are
+ defined in the module or any included submodule.";
+ }
+ list deviation {
+ key "name revision";
+ description
+ "List of YANG deviation module names and revisions
+ used by this server to modify the conformance of
+ the module associated with this entry. Note that
+ the same module can be used for deviations for
+ multiple modules, so the same entry MAY appear
+ within multiple 'module' entries.
+
+ The deviation module MUST be present in the 'module'
+ list, with the same name and revision values.
+ The 'conformance-type' value will be 'implement' for
+ the deviation module.";
+ uses common-leafs;
+ }
+ leaf conformance-type {
+ type enumeration {
+ enum implement {
+ description
+ "Indicates that the server implements one or more
+ protocol-accessible objects defined in the YANG module
+ identified in this entry. This includes deviation
+ statements defined in the module.
+
+ For YANG version 1.1 modules, there is at most one
+ module entry with conformance type 'implement' for a
+ particular module name, since YANG 1.1 requires that,
+ at most, one revision of a module is implemented.
+
+ For YANG version 1 modules, there SHOULD NOT be more
+ than one module entry for a particular module name.";
+ }
+ enum import {
+ description
+ "Indicates that the server imports reusable definitions
+ from the specified revision of the module but does
+ not implement any protocol-accessible objects from
+ this revision.
+
+ Multiple module entries for the same module name MAY
+ exist. This can occur if multiple modules import the
+ same module but specify different revision dates in
+ the import statements.";
+ }
+ }
+ mandatory true;
+ description
+ "Indicates the type of conformance the server is claiming
+ for the YANG module identified by this entry.";
+ }
+ list submodule {
+ key "name revision";
+ description
+ "Each entry represents one submodule within the
+ parent module.";
+ uses common-leafs;
+ uses schema-leaf;
+ }
+ }
+ }
+
+ /*
+ * Operational state data nodes
+ */
+
+ container modules-state {
+ config false;
+ description
+ "Contains YANG module monitoring information.";
+
+ leaf module-set-id {
+ type string;
+ mandatory true;
+ description
+ "Contains a server-specific identifier representing
+ the current set of modules and submodules. The
+ server MUST change the value of this leaf if the
+ information represented by the 'module' list instances
+ has changed.";
+ }
+
+ uses module-list;
+ }
+
+ /*
+ * Notifications
+ */
+
+ notification yang-library-change {
+ description
+ "Generated when the set of modules and submodules supported
+ by the server has changed.";
+ leaf module-set-id {
+ type leafref {
+ path "/yanglib:modules-state/yanglib:module-set-id";
+ }
+ mandatory true;
+ description
+ "Contains the module-set-id value representing the
+ set of modules and submodules supported at the server at
+ the time the notification is generated.";
+ }
+ }
+
+}
+
diff --git a/translib/path_utils.go b/translib/path_utils.go
index 1083f1054336..dc724b6723d5 100644
--- a/translib/path_utils.go
+++ b/translib/path_utils.go
@@ -42,6 +42,12 @@ type PathInfo struct {
Vars map[string]string
}
+// HasVar checks if the PathInfo contains given variable.
+func (p *PathInfo) HasVar(name string) bool {
+ _, exists := p.Vars[name]
+ return exists
+}
+
// Var returns the string value for a path variable. Returns
// empty string if no such variable exists.
func (p *PathInfo) Var(name string) string {
@@ -91,6 +97,14 @@ func NewPathInfo(path string) *PathInfo {
name := readUntil(r, '=')
value := readUntil(r, ']')
+
+ // Handle duplicate parameter names by suffixing "#N" to it.
+ // N is the number of occurance of that parameter name from left.
+ namePrefix := name
+ for k := 2; info.HasVar(name); k++ {
+ name = fmt.Sprintf("%s#%d", namePrefix, k)
+ }
+
if len(name) != 0 {
fmt.Fprintf(&template, "{}")
info.Vars[name] = value
@@ -104,12 +118,17 @@ func NewPathInfo(path string) *PathInfo {
func readUntil(r *strings.Reader, delim byte) string {
var buff strings.Builder
+ var escaped bool
+
for {
c, err := r.ReadByte()
- if err == nil && c != delim {
- buff.WriteByte(c)
- } else {
+ if err != nil || (c == delim && !escaped) {
break
+ } else if c == '\\' && !escaped {
+ escaped = true
+ } else {
+ escaped = false
+ buff.WriteByte(c)
}
}
diff --git a/translib/path_utils_test.go b/translib/path_utils_test.go
index 1d108086d46e..33b8194aa5a2 100644
--- a/translib/path_utils_test.go
+++ b/translib/path_utils_test.go
@@ -162,3 +162,68 @@ func TestGetObjectFieldName(t *testing.T) {
}
}
}
+
+func TestNewPathInfo_empty(t *testing.T) {
+ testPathInfo(t, "", "", mkmap())
+}
+
+func TestNewPathInfo_novar(t *testing.T) {
+ testPathInfo(t, "/test/simple", "/test/simple", mkmap())
+}
+
+func TestNewPathInfo_var1(t *testing.T) {
+ testPathInfo(t, "/test/xx[one=1]", "/test/xx{}", mkmap("one", "1"))
+}
+
+func TestNewPathInfo_vars(t *testing.T) {
+ testPathInfo(t, "/test/xx[one=1][two=2]/new[three=3]", "/test/xx{}{}/new{}",
+ mkmap("one", "1", "two", "2", "three", "3"))
+}
+
+func TestNewPathInfo_dup1(t *testing.T) {
+ testPathInfo(t, "/test/xx[one=1][two=2]/new[one=0001]", "/test/xx{}{}/new{}",
+ mkmap("one", "1", "two", "2", "one#2", "0001"))
+}
+
+func TestNewPathInfo_dups(t *testing.T) {
+ testPathInfo(t, "/test/one[xx=1]/two[yy=2]/three[xx=3]/four[zz=4]/five[yy=5]/six[xx=6]",
+ "/test/one{}/two{}/three{}/four{}/five{}/six{}",
+ mkmap("xx", "1", "yy", "2", "xx#2", "3", "zz", "4", "yy#2", "5", "xx#3", "6"))
+}
+
+func TestNewPathInfo_escaped_name(t *testing.T) {
+ testPathInfo(t, "/test/xx[one\\==1][two[\\]=2]", "/test/xx{}{}",
+ mkmap("one=", "1", "two[]", "2"))
+}
+
+func TestNewPathInfo_escaped_valu(t *testing.T) {
+ testPathInfo(t, "/test/xx[one=[1\\]][two=\\0\\02 [\\.\\D]", "/test/xx{}{}",
+ mkmap("one", "[1]", "two", "002 [.D"))
+}
+
+func testPathInfo(t *testing.T, path, expTemplate string, expVars map[string]string) {
+ info := NewPathInfo(path)
+ if info == nil {
+ t.Errorf("NewPathInfo() returned null!")
+ } else if info.Path != path {
+ t.Errorf("Expected info.Path = %s", path)
+ t.Errorf("Actual info.Path = %s", info.Path)
+ } else if info.Template != expTemplate {
+ t.Errorf("Expected info.Template = %s", expTemplate)
+ t.Errorf("Actual info.Template = %s", info.Template)
+ } else if reflect.DeepEqual(info.Vars, expVars) == false {
+ t.Errorf("Expected info.Vars = %v", expVars)
+ t.Errorf("Actual info.Vars = %v", info.Vars)
+ }
+ if t.Failed() {
+ t.Fatalf("NewPathInfo() failed to parse \"%s\"", path)
+ }
+}
+
+func mkmap(args ...string) map[string]string {
+ m := make(map[string]string)
+ for i := 0; (i + 1) < len(args); i += 2 {
+ m[args[i]] = args[i+1]
+ }
+ return m
+}
diff --git a/translib/yanglib_app.go b/translib/yanglib_app.go
new file mode 100644
index 000000000000..c235a97677e0
--- /dev/null
+++ b/translib/yanglib_app.go
@@ -0,0 +1,512 @@
+////////////////////////////////////////////////////////////////////////////////
+// //
+// Copyright 2020 Broadcom. The term Broadcom refers to Broadcom Inc. and/or //
+// its subsidiaries. //
+// //
+// Licensed under the Apache License, Version 2.0 (the "License"); //
+// you may not use this file except in compliance with the License. //
+// You may obtain a copy of the License at //
+// //
+// http://www.apache.org/licenses/LICENSE-2.0 //
+// //
+// Unless required by applicable law or agreed to in writing, software //
+// distributed under the License is distributed on an "AS IS" BASIS, //
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
+// See the License for the specific language governing permissions and //
+// limitations under the License. //
+// //
+////////////////////////////////////////////////////////////////////////////////
+
+package translib
+
+import (
+ "path/filepath"
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/Azure/sonic-mgmt-common/translib/db"
+ "github.com/Azure/sonic-mgmt-common/translib/ocbinds"
+ errors "github.com/Azure/sonic-mgmt-common/translib/tlerr"
+ "github.com/Azure/sonic-mgmt-common/translib/transformer"
+
+ "github.com/golang/glog"
+ "github.com/openconfig/goyang/pkg/yang"
+)
+
+// yanglibApp implements app interface for the
+// ietf-yang-library module
+type yanglibApp struct {
+ pathInfo *PathInfo
+ ygotRoot *ocbinds.Device
+ ygotTarget *interface{}
+}
+
+// theYanglibMutex synchronizes all cache loads
+var theYanglibMutex sync.Mutex
+
+// theYanglibCache holds parsed yanglib info. Populated on first
+// request.
+var theYanglibCache *ocbinds.IETFYangLibrary_ModulesState
+
+// theSchemaRootURL is the base URL for the yang file download URL.
+// Main program must set the value through SetSchemaRootURL() API.
+// Individual file URL is obtained by appending file name to it.
+var theSchemaRootURL string
+
+func init() {
+ err := register("/ietf-yang-library:modules-state",
+ &appInfo{
+ appType: reflect.TypeOf(yanglibApp{}),
+ ygotRootType: reflect.TypeOf(ocbinds.IETFYangLibrary_ModulesState{}),
+ isNative: false,
+ })
+ if err != nil {
+ glog.Fatal("register() failed for yanglibApp;", err)
+ }
+
+ err = addModel(&ModelData{
+ Name: "ietf-yang-library",
+ Org: "IETF NETCONF (Network Configuration) Working Group",
+ Ver: "2016-06-21",
+ })
+ if err != nil {
+ glog.Fatal("addModel() failed for yanglibApp;", err)
+ }
+}
+
+/*
+ * App interface functions
+ */
+
+func (app *yanglibApp) initialize(data appData) {
+ app.pathInfo = NewPathInfo(data.path)
+ app.ygotRoot = (*data.ygotRoot).(*ocbinds.Device)
+ app.ygotTarget = data.ygotTarget
+}
+
+func (app *yanglibApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) {
+ return nil, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) {
+ return nil, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) {
+ return nil, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) {
+ return nil, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) translateGet(dbs [db.MaxDB]*db.DB) error {
+ return nil // NOOP! everyting is in processGet
+}
+
+func (app *yanglibApp) translateAction(dbs [db.MaxDB]*db.DB) error {
+ return errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) {
+ return nil, nil, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) processCreate(d *db.DB) (SetResponse, error) {
+ return SetResponse{}, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) processUpdate(d *db.DB) (SetResponse, error) {
+ return SetResponse{}, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) processReplace(d *db.DB) (SetResponse, error) {
+ return SetResponse{}, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) processDelete(d *db.DB) (SetResponse, error) {
+ return SetResponse{}, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) {
+ return ActionResponse{}, errors.NotSupported("Unsupported")
+}
+
+func (app *yanglibApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) {
+ glog.Infof("path = %s", app.pathInfo.Template)
+ glog.Infof("vars = %s", app.pathInfo.Vars)
+
+ var resp GetResponse
+ ylib, err := getYanglibInfo()
+ if err != nil {
+ return resp, err
+ }
+
+ switch {
+ case app.pathInfo.HasSuffix("/module-set-id"): // only module-set-id
+ app.ygotRoot.ModulesState.ModuleSetId = ylib.ModuleSetId
+
+ case app.pathInfo.HasVar("name"): // only one module
+ err = app.copyOneModuleInfo(ylib)
+
+ default: // all modules
+ app.ygotRoot.ModulesState = ylib
+ }
+
+ if err == nil {
+ resp.Payload, err = generateGetResponsePayload(
+ app.pathInfo.Path, app.ygotRoot, app.ygotTarget)
+ }
+
+ return resp, err
+}
+
+// copyOneModuleInfo fills one module from given ygot IETFYangLibrary_ModulesState
+// object into app.ygotRoot.
+func (app *yanglibApp) copyOneModuleInfo(fromMods *ocbinds.IETFYangLibrary_ModulesState) error {
+ key := ocbinds.IETFYangLibrary_ModulesState_Module_Key{
+ Name: app.pathInfo.Var("name"), Revision: app.pathInfo.Var("revision")}
+
+ glog.Infof("Copying module %s@%s", key.Name, key.Revision)
+
+ to := app.ygotRoot.ModulesState.Module[key]
+ from := fromMods.Module[key]
+ if from == nil {
+ glog.Errorf("No module %s in yanglib", key)
+ return errors.NotFound("Module %s@%s not found", key.Name, key.Revision)
+ }
+
+ switch pt := app.pathInfo.Template; {
+ case strings.HasSuffix(pt, "/deviation"):
+ // Copy only deviations.
+ if len(from.Deviation) != 0 {
+ to.Deviation = from.Deviation
+ } else {
+ return errors.NotFound("Module %s@%s has no deviations", key.Name, key.Revision)
+ }
+
+ case strings.Contains(pt, "/deviation{}{}"):
+ // Copy only one deviation info
+ devkey := ocbinds.IETFYangLibrary_ModulesState_Module_Deviation_Key{
+ Name: app.pathInfo.Var("name#2"), Revision: app.pathInfo.Var("revision#2")}
+
+ if devmod := from.Deviation[devkey]; devmod != nil {
+ *to.Deviation[devkey] = *devmod
+ } else {
+ return errors.NotFound("Module %s@%s has no deviation %s@%s",
+ key.Name, key.Revision, devkey.Name, devkey.Revision)
+ }
+
+ case strings.HasSuffix(pt, "/submodule"):
+ // Copy only submodules..
+ if len(from.Submodule) != 0 {
+ to.Submodule = from.Submodule
+ } else {
+ return errors.NotFound("Module %s@%s has no submodules", key.Name, key.Revision)
+ }
+
+ case strings.Contains(pt, "/submodule{}{}"):
+ // Copy only one submodule info
+ subkey := ocbinds.IETFYangLibrary_ModulesState_Module_Submodule_Key{
+ Name: app.pathInfo.Var("name#2"), Revision: app.pathInfo.Var("revision#2")}
+
+ if submod := from.Submodule[subkey]; submod != nil {
+ *to.Submodule[subkey] = *submod
+ } else {
+ return errors.NotFound("Module %s@%s has no submodule %s@%s",
+ key.Name, key.Revision, subkey.Name, subkey.Revision)
+ }
+
+ default:
+ // Copy full module
+ app.ygotRoot.ModulesState.Module[key] = from
+ }
+
+ return nil
+}
+
+/*
+ * Yang parsing utilities
+ */
+
+// yanglibBuilder is the utility for parsing and loading yang files into
+// ygot IETFYangLibrary_ModulesState object.
+type yanglibBuilder struct {
+ // yangDir is the directory with all yang files
+ yangDir string
+
+ // implModules contains top level yang module names implemented
+ // by this system. Values are discovered from translib.getModels() API
+ implModules map[string]bool
+
+ // yangModules is the temporary cache of all parsed yang modules.
+ // Populated by loadYangs() function.
+ yangModules *yang.Modules
+
+ // ygotModules is the output ygot object tree containing all
+ // yang module info
+ ygotModules *ocbinds.IETFYangLibrary_ModulesState
+}
+
+// getYanglibInfo returns the ygot IETFYangLibrary_ModulesState object
+// with all yang library information.
+func getYanglibInfo() (ylib *ocbinds.IETFYangLibrary_ModulesState, err error) {
+ theYanglibMutex.Lock()
+ if theYanglibCache == nil {
+ glog.Infof("Building yanglib cache")
+ theYanglibCache, err = newYanglibInfo()
+ glog.Infof("Yanglib cache ready; err=%v", err)
+ }
+
+ ylib = theYanglibCache
+ theYanglibMutex.Unlock()
+ return
+}
+
+// newYanglibInfo loads all eligible yangs and fills yanglib info into the
+// ygot IETFYangLibrary_ModulesState object
+func newYanglibInfo() (*ocbinds.IETFYangLibrary_ModulesState, error) {
+ var yb yanglibBuilder
+ if err := yb.prepare(); err != nil {
+ return nil, err
+ }
+ if err := yb.loadYangs(); err != nil {
+ return nil, err
+ }
+ if err := yb.translate(); err != nil {
+ return nil, err
+ }
+
+ return yb.ygotModules, nil
+}
+
+// prepare function initializes the yanglibBuilder object for
+// parsing yangs and translating into ygot.
+func (yb *yanglibBuilder) prepare() error {
+ yb.yangDir = GetYangPath()
+ glog.Infof("yanglibBuilder.prepare: yangDir = %s", yb.yangDir)
+ glog.Infof("yanglibBuilder.prepare: baseURL = %s", theSchemaRootURL)
+
+ // Load supported model information
+ yb.implModules = make(map[string]bool)
+ for _, m := range getModels() {
+ yb.implModules[m.Name] = true
+ }
+
+ yb.ygotModules = &ocbinds.IETFYangLibrary_ModulesState{}
+ return nil
+}
+
+// loadYangs reads eligible yang files into yang.Modules object.
+// Skips transformer annotation yangs.
+func (yb *yanglibBuilder) loadYangs() error {
+ glog.Infof("Loading yangs from %s directory", yb.yangDir)
+ var parsed, ignored uint32
+ mods := yang.NewModules()
+ start := time.Now()
+
+ files, _ := filepath.Glob(filepath.Join(yb.yangDir, "*.yang"))
+ for _, f := range files {
+ // ignore transformer annotation yangs
+ if strings.HasSuffix(filepath.Base(f), "-annot.yang") {
+ ignored++
+ continue
+ }
+ if err := mods.Read(f); err != nil {
+ glog.Errorf("Failed to parse %s; err=%v", f, err)
+ return errors.New("System error")
+ }
+ parsed++
+ }
+
+ glog.Infof("%d yang files loaded in %s; %d ignored", parsed, time.Since(start), ignored)
+ yb.yangModules = mods
+ return nil
+}
+
+// translate function fills parsed yang.Modules info into the
+// ygot IETFYangLibrary_ModulesState object.
+func (yb *yanglibBuilder) translate() error {
+ var modsWithDeviation []*yang.Module
+
+ // First iteration -- create ygot module entry for each yang.Module
+ for _, mod := range yb.yangModules.Modules {
+ m, _ := yb.ygotModules.NewModule(mod.Name, mod.Current())
+ if m == nil {
+ // ignore; yang.Modules map contains dupicate entries - one for name and
+ // other for name@rev. NewModule() will return nil if entry exists.
+ continue
+ }
+
+ // Fill basic properties into ygot module
+ yb.fillModuleInfo(m, mod)
+
+ // Mark the yang.Module with "deviation" statements for 2nd iteration. We need reverse
+ // mapping of deviation target -> current module in ygot. Hence 2nd iteration..
+ if len(mod.Deviation) != 0 {
+ modsWithDeviation = append(modsWithDeviation, mod)
+ }
+ }
+
+ // 2nd iteration -- fill deviations.
+ for _, mod := range modsWithDeviation {
+ yb.translateDeviations(mod)
+ }
+
+ // 3rd iteration -- fill conformance type
+ for _, m := range yb.ygotModules.Module {
+ if yb.implModules[*m.Name] {
+ m.ConformanceType = ocbinds.IETFYangLibrary_ModulesState_Module_ConformanceType_implement
+ } else {
+ m.ConformanceType = ocbinds.IETFYangLibrary_ModulesState_Module_ConformanceType_import
+ }
+ }
+
+ // Use yang bundle version as module-set-id
+ msetID := GetYangModuleSetID()
+ yb.ygotModules.ModuleSetId = &msetID
+
+ return nil
+}
+
+// fillModuleInfo yang module info from yang.Module to ygot IETFYangLibrary_ModulesState_Module
+// object.. Deviation information is not filled.
+func (yb *yanglibBuilder) fillModuleInfo(to *ocbinds.IETFYangLibrary_ModulesState_Module, from *yang.Module) {
+ to.Namespace = &from.Namespace.Name
+ to.Schema = yb.getSchemaURL(from)
+
+ // Fill the "feature" info from yang even though we dont have full
+ // support for yang features.
+ for _, f := range from.Feature {
+ to.Feature = append(to.Feature, f.Name)
+ }
+
+ // Iterate thru "include" statements to resolve submodules
+ for _, inc := range from.Include {
+ submod := yb.yangModules.FindModule(inc)
+ if submod == nil { // should not happen
+ glog.Errorf("No sub-module %s; @%s", inc.Name, inc.Statement().Location())
+ continue
+ }
+
+ // NewSubmodule() returns nil if submodule entry already exists.. Ignore it.
+ if sm, _ := to.NewSubmodule(submod.Name, submod.Current()); sm != nil {
+ sm.Schema = yb.getSchemaURL(submod)
+ }
+ }
+}
+
+// fillModuleDeviation creates a deviation module info in the ygot structure
+// for a given main module.
+func (yb *yanglibBuilder) fillModuleDeviation(main *yang.Module, deviation *yang.Module) {
+ key := ocbinds.IETFYangLibrary_ModulesState_Module_Key{
+ Name: main.Name, Revision: main.Current()}
+
+ if m, ok := yb.ygotModules.Module[key]; ok {
+ m.NewDeviation(deviation.Name, deviation.Current())
+
+ // Mark the deviation module as "implemented" if main module is also "implemented"
+ if yb.implModules[main.Name] {
+ yb.implModules[deviation.Name] = true
+ }
+ } else {
+ glog.Errorf("Ygot module entry %s not found", key)
+ }
+}
+
+// translateDeviations function will process all "devaiation" statements of
+// a yang.Module and fill deviation info into corresponding ygot module objects.
+func (yb *yanglibBuilder) translateDeviations(mod *yang.Module) error {
+ deviationTargets := make(map[string]bool)
+
+ // Loop thru deviation statements and find modules deviated by current module
+ for _, d := range mod.Deviation {
+ if !strings.HasPrefix(d.Name, "/") {
+ glog.Errorf("Deviation path \"%s\" is not absolute! @%s", d.Name, d.Statement().Location())
+ continue
+ }
+
+ // Get prefix of root node from the deviation path. First split the path
+ // by "/" char and then split 1st part by ":".
+ // Eg, find "acl" from "/acl:scl-sets/config/something"
+ root := strings.SplitN(strings.SplitN(d.Name, "/", 3)[1], ":", 2)
+ if len(root) != 2 {
+ glog.Errorf("Deviation path \"%s\" has no prefix for root element! @%s",
+ d.Name, d.Statement().Location())
+ } else {
+ deviationTargets[root[0]] = true
+ }
+ }
+
+ glog.V(2).Infof("Module %s has deviations for %d modules", mod.FullName(), len(deviationTargets))
+
+ // Deviation target prefixes must be in the import list.. Find the target
+ // modules by matching the prefix in imports.
+ for _, imp := range mod.Import {
+ prefix := imp.Name
+ if imp.Prefix != nil {
+ prefix = imp.Prefix.Name
+ }
+ if !deviationTargets[prefix] {
+ continue
+ }
+
+ if m := yb.yangModules.FindModule(imp); m != nil {
+ yb.fillModuleDeviation(m, mod)
+ } else {
+ glog.Errorf("No module for prefix \"%s\"", prefix)
+ }
+ }
+
+ return nil
+}
+
+// getSchemaURL resolves the URL for downloading yang file from current
+// device. Returns nil if yang URL could not be prepared.
+func (yb *yanglibBuilder) getSchemaURL(m *yang.Module) *string {
+ if len(theSchemaRootURL) == 0 {
+ return nil // Base URL not resolved; hence no yang URL
+ }
+
+ // Ugly hack to get source file name from yang.Module. See implementation
+ // of yang.Statement.Location() function.
+ // TODO: any better way to get source file path from yang.Module??
+ toks := strings.Split(m.Source.Location(), ":")
+ if len(toks) != 1 && len(toks) != 3 {
+ glog.Warningf("Could not resolve file path for module %s; location=%s",
+ m.FullName(), m.Source.Location())
+ return nil
+ }
+
+ uri := theSchemaRootURL + filepath.Base(toks[0])
+ return &uri
+}
+
+// SetSchemaRootURL sets root URL for yang file download URLs.
+func SetSchemaRootURL(url string) {
+ theYanglibMutex.Lock()
+ defer theYanglibMutex.Unlock()
+
+ newURL := url
+ if len(url) != 0 && !strings.HasSuffix(url, "/") {
+ newURL += "/"
+ }
+
+ if theSchemaRootURL != newURL {
+ theSchemaRootURL = newURL
+ theYanglibCache = nil // reset cache
+ }
+}
+
+// GetYangPath returns directory containing yang files. Use
+// transformer.YangPath for now.
+func GetYangPath() string {
+ return transformer.YangPath
+}
+
+// GetYangModuleSetID returns the ietf-yang-library's module-set-id value.
+func GetYangModuleSetID() string {
+ return "0.1.0" //FIXME use YangBundleVersion when API versioning is available.
+}
diff --git a/translib/yanglib_app_test.go b/translib/yanglib_app_test.go
new file mode 100644
index 000000000000..111d808e22cf
--- /dev/null
+++ b/translib/yanglib_app_test.go
@@ -0,0 +1,152 @@
+////////////////////////////////////////////////////////////////////////////////
+// //
+// Copyright 2020 Broadcom. The term Broadcom refers to Broadcom Inc. and/or //
+// its subsidiaries. //
+// //
+// Licensed under the Apache License, Version 2.0 (the "License"); //
+// you may not use this file except in compliance with the License. //
+// You may obtain a copy of the License at //
+// //
+// http://www.apache.org/licenses/LICENSE-2.0 //
+// //
+// Unless required by applicable law or agreed to in writing, software //
+// distributed under the License is distributed on an "AS IS" BASIS, //
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
+// See the License for the specific language governing permissions and //
+// limitations under the License. //
+// //
+////////////////////////////////////////////////////////////////////////////////
+
+package translib
+
+import (
+ "encoding/json"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/sonic-mgmt-common/translib/tlerr"
+)
+
+func TestYanglibGetAll(t *testing.T) {
+ data := getYanglibDataT(t, "", "", "")
+ v := data["ietf-yang-library:modules-state"]
+ if v1, ok := v.(map[string]interface{}); ok {
+ validateMSetID(t, v1["module-set-id"])
+ v = v1["module"]
+ }
+ if v1, ok := v.([]interface{}); !ok || len(v1) == 0 {
+ t.Fatalf("App returned incorrect info.. %v", data)
+ }
+}
+
+func TestYanglibGetMsetID(t *testing.T) {
+ data := getYanglibDataT(t, "", "", "module-set-id")
+ if len(data) != 1 {
+ t.Fatalf("App returned incorrect info.. %v", data)
+ }
+ validateMSetID(t, data["ietf-yang-library:module-set-id"])
+}
+
+func validateMSetID(t *testing.T, msetID interface{}) {
+ if m, ok := msetID.(string); !ok || m != GetYangModuleSetID() {
+ t.Fatalf("App returned incorrect module-set-id \"%s\"; expected \"%s\"",
+ msetID, GetYangModuleSetID())
+ }
+}
+
+func TestYanglibGetOne(t *testing.T) {
+ data := getYanglibDataT(t, "ietf-yang-library", "2016-06-21", "")
+
+ var m map[string]interface{}
+ if v, ok := data["ietf-yang-library:module"].([]interface{}); ok && len(v) == 1 {
+ m, ok = v[0].(map[string]interface{})
+ }
+
+ if m["name"] != "ietf-yang-library" ||
+ m["revision"] != "2016-06-21" ||
+ m["namespace"] != "urn:ietf:params:xml:ns:yang:ietf-yang-library" ||
+ m["conformance-type"] != "implement" {
+ t.Fatalf("App returned incorrect info.. %v", data)
+ }
+}
+
+func TestYanglibGetOneAttr(t *testing.T) {
+ data := getYanglibDataT(t, "ietf-yang-library", "2016-06-21", "namespace")
+ if data["ietf-yang-library:namespace"] != "urn:ietf:params:xml:ns:yang:ietf-yang-library" {
+ t.Fatalf("App returned incorrect info.. %v", data)
+ }
+}
+
+func TestYanglibSchemaURL(t *testing.T) {
+ defer SetSchemaRootURL("")
+
+ t.Run("default", testYlibSchema(nil))
+
+ SetSchemaRootURL("https://localhost/schema1")
+ t.Run("no_slash", testYlibSchema("https://localhost/schema1/ietf-yang-library.yang"))
+
+ SetSchemaRootURL("https://localhost/schema2/")
+ t.Run("with_slash", testYlibSchema("https://localhost/schema2/ietf-yang-library.yang"))
+
+ SetSchemaRootURL("")
+ t.Run("reset", testYlibSchema(nil))
+}
+
+func testYlibSchema(expURL interface{}) func(*testing.T) {
+ return func(t *testing.T) {
+ data := getYanglibDataT(t, "ietf-yang-library", "2016-06-21", "schema")
+ if data["ietf-yang-library:schema"] != expURL {
+ t.Fatalf("Expected schema url '%s', found '%s'",
+ expURL, data["ietf-yang-library:schema"])
+ }
+ }
+}
+
+func TestYanglibConformance(t *testing.T) {
+ t.Run("ietf-yang-library", testConfType("ietf-yang-library", "2016-06-21", "implement"))
+ t.Run("ietf-yang-types", testConfType("ietf-yang-types", "2013-07-15", "import"))
+ t.Run("ietf-inet-types", testConfType("ietf-inet-types", "2013-07-15", "import"))
+}
+
+func testConfType(mod, rev, exp string) func(*testing.T) {
+ return func(t *testing.T) {
+ data := getYanglibDataT(t, mod, rev, "conformance-type")
+ if data["ietf-yang-library:conformance-type"] != exp {
+ t.Fatalf("App returned unexpected conformance-type for %s@%s; found=%s, exp=%s",
+ mod, rev, data["ietf-yang-library:conformance-type"], exp)
+ }
+ }
+}
+
+func TestYanglibGetUnknown(t *testing.T) {
+ _, err := getYanglibData("unknown", "0000-00-00", "")
+ if _, ok := err.(tlerr.NotFoundError); !ok {
+ t.Fatalf("Expected NotFoundError, got %T", err)
+ }
+}
+
+func getYanglibData(name, rev, attr string) (map[string]interface{}, error) {
+ u := "/ietf-yang-library:modules-state"
+ if name != "" || rev != "" {
+ u += fmt.Sprintf("/module[name=%s][revision=%s]", name, rev)
+ }
+ if attr != "" {
+ u += ("/" + attr)
+ }
+
+ data := make(map[string]interface{})
+ response, err := Get(GetRequest{Path: u})
+ if err == nil {
+ err = json.Unmarshal(response.Payload, &data)
+ }
+
+ return data, err
+}
+
+func getYanglibDataT(t *testing.T, name, rev, attr string) map[string]interface{} {
+ data, err := getYanglibData(name, rev, attr)
+ if err != nil {
+ t.Fatalf("Unexpected erorr: %v", err)
+ }
+ return data
+}