From dbf109378559343f859a6031f4dc2b3993b7222e Mon Sep 17 00:00:00 2001 From: anand-kumar-subramanian <51383315+anand-kumar-subramanian@users.noreply.github.com> Date: Wed, 30 Sep 2020 16:50:41 -0700 Subject: [PATCH] Translib support for authorization, yang versioning and Delete flag (#21) Translib support for user authorization, yang versioning and Delete flag to indicate if the object needs to be deleted on last field delete needed for CLI --- debian/sonic-mgmt-common.install | 1 + models/yang/README.md | 51 +++++++ models/yang/version.xml | 36 +++++ translib/app_interface.go | 4 + translib/authorize.go | 81 +++++++++++ translib/tlerr/app_errors.go | 8 ++ translib/tlerr/tlerr.go | 11 ++ translib/translib.go | 71 +++++++++- translib/version.go | 168 +++++++++++++++++++++-- translib/version_test.go | 226 +++++++++++++++++++++++++++++++ translib/yanglib_app.go | 2 +- 11 files changed, 642 insertions(+), 17 deletions(-) create mode 100644 models/yang/README.md create mode 100644 models/yang/version.xml create mode 100644 translib/authorize.go create mode 100755 translib/version_test.go diff --git a/debian/sonic-mgmt-common.install b/debian/sonic-mgmt-common.install index b9bdf7ff3815..4d28ffda7bf3 100644 --- a/debian/sonic-mgmt-common.install +++ b/debian/sonic-mgmt-common.install @@ -6,6 +6,7 @@ models/yang/sonic/*.yang usr/models/yang models/yang/sonic/common/*.yang usr/models/yang models/yang/annotations/*.yang usr/models/yang config/transformer/models_list usr/models/yang +models/yang/version.xml usr/models/yang # CVL files build/cvl/schema usr/sbin diff --git a/models/yang/README.md b/models/yang/README.md new file mode 100644 index 000000000000..1bcc3a1d0549 --- /dev/null +++ b/models/yang/README.md @@ -0,0 +1,51 @@ +# YANG directory + +## Directory structure + + yang/ --> Standard YANGs + |-- annotations/ --> Transformer annotations + |-- common/ --> Dependencies for standard YANGs + |-- extensions/ --> Extenstions for standard YANGs + |-- sonic/ --> SONiC yangs + |-- testdata/ --> Test YANGs - ignored + `-- version.xml --> YANG bundle version configuration file + +All supported standard YANG files (OpenConfig and IETF) are kept in this **yang** directory. Usual practice is to keep only top level YANG module here and keep dependent YANGs, submodules in **yang/common** directory. + +Example: openconfig-platform.yang is kept in top **yang** directory and openconfig-platform-types.yang in **yang/common** directory. + +All extenstion YANGs **MUST** be kept in **yang/extensions** directory. + +## version.xml + +version.xml file maintains the yang bundle version number in **Major.Minor.Patch** format. +It is the collective version number for all the YANG modules defined here. +**UPDATE THIS VERSION NUMBER FOR EVERY YANG CHANGE.** + +**Major version** should be incremented if YANG model is changed in a non backward compatible manner. +Such changes should be avoided. + +* Delete, rename or relocate data node +* Change list key attributes +* Change data type of a node to an incompatible type +* Change leafref target + +**Minor version** should be incremented if the YANG change modifies the API in a backward +compatible way. Patch version should be reset to 0. +Candidate YANG changes for this category are: + +* Add new YANG module +* Add new YANG data nodes +* Mark a YANG data node as deprecated +* Change data type of a node to a compatible type +* Add new enum or identity + +**Patch version** should incremented for cosmetic fixes that do not change YANG API. +Candidate YANG changes for this category are: + +* Change description, beautification. +* Expand pattern or range of a node to wider set. +* Change must expression to accept more cases. +* Error message or error tag changes. + + diff --git a/models/yang/version.xml b/models/yang/version.xml new file mode 100644 index 000000000000..3028d6b5875b --- /dev/null +++ b/models/yang/version.xml @@ -0,0 +1,36 @@ + + + + 1 + 0 + 0 + + + + diff --git a/translib/app_interface.go b/translib/app_interface.go index bb56988a0fdc..7929a11eab0a 100644 --- a/translib/app_interface.go +++ b/translib/app_interface.go @@ -63,6 +63,10 @@ type appOptions struct { // 0 indicates unlimited depth. // Valid for GET API only. depth uint + + // deleteEmptyEntry indicates if the db entry should be deleted upon + // deletion of last field. This is a non standard option. + deleteEmptyEntry bool } //map containing the base path to app module info diff --git a/translib/authorize.go b/translib/authorize.go new file mode 100644 index 000000000000..c84b4b9bef0c --- /dev/null +++ b/translib/authorize.go @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 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 defines the functions to be used to authorize + +an incoming user. It also includes caching of the UserDB data + +needed to authorize the user. + +*/ + +package translib + +func isAuthorizedForSet(req SetRequest) bool { + if !req.AuthEnabled { + return true + } + for _, r := range req.User.Roles { + if r == "admin" { + return true + } + } + return false +} + +func isAuthorizedForBulk(req BulkRequest) bool { + if !req.AuthEnabled { + return true + } + for _, r := range req.User.Roles { + if r == "admin" { + return true + } + } + return false +} + +func isAuthorizedForGet(req GetRequest) bool { + if !req.AuthEnabled { + return true + } + return true +} + +func isAuthorizedForSubscribe(req SubscribeRequest) bool { + if !req.AuthEnabled { + return true + } + return true +} + +func isAuthorizedForIsSubscribe(req IsSubscribeRequest) bool { + if !req.AuthEnabled { + return true + } + return true +} + +func isAuthorizedForAction(req ActionRequest) bool { + if !req.AuthEnabled { + return true + } + return true +} diff --git a/translib/tlerr/app_errors.go b/translib/tlerr/app_errors.go index d6959543269c..289cb84b37b6 100644 --- a/translib/tlerr/app_errors.go +++ b/translib/tlerr/app_errors.go @@ -45,6 +45,9 @@ type NotSupportedError errordata // InternalError indicates a generic error during app execution. type InternalError errordata +// AuthorizationError indicates the user is not authorized for an operation. +type AuthorizationError errordata + ///////////// func (e InvalidArgsError) Error() string { @@ -90,3 +93,8 @@ func (e InternalError) Error() string { func New(msg string, args ...interface{}) InternalError { return InternalError{Format: msg, Args: args} } + +func (e AuthorizationError) Error() string { + return p.Sprintf(e.Format, e.Args...) +} + diff --git a/translib/tlerr/tlerr.go b/translib/tlerr/tlerr.go index a920e67f43f6..350a8602166b 100644 --- a/translib/tlerr/tlerr.go +++ b/translib/tlerr/tlerr.go @@ -101,3 +101,14 @@ type TranslibSyntaxValidationError struct { func (e TranslibSyntaxValidationError) Error() string { return p.Sprintf("%s", e.ErrorStr) } + +type TranslibUnsupportedClientVersion struct { + ClientVersion string + ServerVersion string + ServerBaseVersion string +} + +func (e TranslibUnsupportedClientVersion) Error() string { + return p.Sprintf("Unsupported client version %s", e.ClientVersion) +} + diff --git a/translib/translib.go b/translib/translib.go index c222503ceb77..737d530fa086 100644 --- a/translib/translib.go +++ b/translib/translib.go @@ -47,7 +47,7 @@ var writeMutex = &sync.Mutex{} //Interval value for interval based subscription needs to be within the min and max //minimum global interval for interval based subscribe in secs var minSubsInterval = 20 -//minimum global interval for interval based subscribe in secs +//maximum global interval for interval based subscribe in secs var maxSubsInterval = 600 type ErrSource int @@ -68,6 +68,7 @@ type SetRequest struct { User UserRoles AuthEnabled bool ClientVersion Version + DeleteEmptyEntry bool } type SetResponse struct { @@ -183,6 +184,12 @@ func Create(req SetRequest) (SetResponse, error) { var resp SetResponse path := req.Path payload := req.Payload + if !isAuthorizedForSet(req) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Create Operation", + Path: path, + } + } log.Info("Create request received with path =", path) log.Info("Create request received with payload =", string(payload)) @@ -251,6 +258,13 @@ func Update(req SetRequest) (SetResponse, error) { var resp SetResponse path := req.Path payload := req.Payload + if !isAuthorizedForSet(req) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Update Operation", + Path: path, + } + } + log.Info("Update request received with path =", path) log.Info("Update request received with payload =", string(payload)) @@ -320,6 +334,12 @@ func Replace(req SetRequest) (SetResponse, error) { var resp SetResponse path := req.Path payload := req.Payload + if !isAuthorizedForSet(req) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Replace Operation", + Path: path, + } + } log.Info("Replace request received with path =", path) log.Info("Replace request received with payload =", string(payload)) @@ -388,6 +408,12 @@ func Delete(req SetRequest) (SetResponse, error) { var keys []db.WatchKeys var resp SetResponse path := req.Path + if !isAuthorizedForSet(req) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Delete Operation", + Path: path, + } + } log.Info("Delete request received with path =", path) @@ -398,7 +424,8 @@ func Delete(req SetRequest) (SetResponse, error) { return resp, err } - err = appInitialize(app, appInfo, path, nil, nil, DELETE) + opts := appOptions{deleteEmptyEntry: req.DeleteEmptyEntry} + err = appInitialize(app, appInfo, path, nil, &opts, DELETE) if err != nil { resp.ErrSrc = AppErr @@ -454,6 +481,12 @@ func Get(req GetRequest) (GetResponse, error) { var payload []byte var resp GetResponse path := req.Path + if !isAuthorizedForGet(req) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Get Operation", + Path: path, + } + } log.Info("Received Get request for path = ", path) @@ -499,6 +532,12 @@ func Action(req ActionRequest) (ActionResponse, error) { var resp ActionResponse path := req.Path + if !isAuthorizedForAction(req) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Action Operation", + Path: path, + } + } log.Info("Received Action request for path = ", path) @@ -560,6 +599,13 @@ func Bulk(req BulkRequest) (BulkResponse, error) { UpdateResponse: updateResp, CreateResponse: createResp} + + if (!isAuthorizedForBulk(req)) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Action Operation", + } + } + writeMutex.Lock() defer writeMutex.Unlock() @@ -581,6 +627,7 @@ func Bulk(req BulkRequest) (BulkResponse, error) { for i := range req.DeleteRequest { path := req.DeleteRequest[i].Path + opts := appOptions{deleteEmptyEntry: req.DeleteRequest[i].DeleteEmptyEntry} log.Info("Delete request received with path =", path) @@ -591,7 +638,7 @@ func Bulk(req BulkRequest) (BulkResponse, error) { goto BulkDeleteError } - err = appInitialize(app, appInfo, path, nil, nil, DELETE) + err = appInitialize(app, appInfo, path, nil, &opts, DELETE) if err != nil { errSrc = AppErr @@ -807,6 +854,12 @@ func Subscribe(req SubscribeRequest) ([]*IsSubscribeResponse, error) { Err: nil} } + if (!isAuthorizedForSubscribe(req)) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Action Operation", + } + } + isGetCase := true dbs, err := getAllDbs(isGetCase) @@ -903,6 +956,12 @@ func IsSubscribeSupported(req IsSubscribeRequest) ([]*IsSubscribeResponse, error Err: nil} } + if (!isAuthorizedForIsSubscribe(req)) { + return resp, tlerr.AuthorizationError{ + Format: "User is unauthorized for Action Operation", + } + } + isGetCase := true dbs, err := getAllDbs(isGetCase) @@ -1075,7 +1134,7 @@ func getDBOptionsWithSeparator(dbNo db.DBNum, initIndicator string, tableSeparat InitIndicator: initIndicator, TableNameSeparator: tableSeparator, KeySeparator: keySeparator, - //IsWriteDisabled: isWriteDisabled, //Will be enabled once the DB access layer changes are checked in + IsWriteDisabled: isWriteDisabled, }) } @@ -1088,6 +1147,10 @@ func getAppModule(path string, clientVer Version) (*appInterface, *appInfo, erro return nil, aInfo, err } + if err := validateClientVersion(clientVer, path, aInfo); err != nil { + return nil, aInfo, err + } + app, err = getAppInterface(aInfo.appType) if err != nil { diff --git a/translib/version.go b/translib/version.go index c066cfcba19a..4676a2703457 100644 --- a/translib/version.go +++ b/translib/version.go @@ -1,7 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// 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 ( - "fmt" + "encoding/xml" + "fmt" + "os" + "path/filepath" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + "github.com/golang/glog" ) // theYangBundleVersion indicates the current yang bundle version. @@ -16,24 +40,144 @@ var theYangBaseVersion Version // Version represents the semantic version number in Major.Minor.Patch // format. type Version struct { - Major uint32 // Major version number - Minor uint32 // Minor version number - Patch uint32 // Patch number + Major uint32 // Major version number + Minor uint32 // Minor version number + Patch uint32 // Patch number } // NewVersion creates a Version object from given version string func NewVersion(s string) (Version, error) { - var v Version - err := v.Set(s) - return v, err + var v Version + err := v.Set(s) + return v, err +} + +func (v Version) String() string { + return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) } // Set parses a version string in X.Y.Z into a Version object v. func (v *Version) Set(s string) error { - _, err := fmt.Sscanf(s, "%d.%d.%d", &v.Major, &v.Minor, &v.Patch) - if err == nil { - err = fmt.Errorf("Invalid version \"%s\"", s) - } + _, err := fmt.Sscanf(s, "%d.%d.%d", &v.Major, &v.Minor, &v.Patch) + if err == nil && v.IsNull() { + err = fmt.Errorf("Invalid version \"%s\"", s) + } + + return err +} + +// IsNull checks if the version v is a null version (0.0.0) +func (v *Version) IsNull() bool { + return v == nil || (v.Major == 0 && v.Minor == 0 && v.Patch == 0) +} + +// GreaterThan checks if the Version v is more than another version +func (v *Version) GreaterThan(other Version) bool { + return (v.Major > other.Major) || + (v.Major == other.Major && v.Minor > other.Minor) || + (v.Major == other.Major && v.Minor == other.Minor && v.Patch > other.Patch) +} + +// GetCompatibleBaseVersion returns the compatible base version for +// current version. +func (v *Version) GetCompatibleBaseVersion() Version { + switch v.Major { + case 0: + // 0.x.x versions are always considered as developement versions + // and are not backward compatible. So, base = current + return *v + case 1: + // Base is 1.0.0 if yang bundle version is 1.x.x + return Version{Major: 1} + default: + // For 2.x.x or higher versions the base will be (N-1).0.0 + return Version{Major: v.Major - 1} + } +} + +// validateClientVersion verifies API client client is compatible with this server. +func validateClientVersion(clientVer Version, path string, appInfo *appInfo) error { + if clientVer.IsNull() { + // Client did not povide version info + return nil + } + + if appInfo.isNative { + return validateClientVersionForNonYangAPI(clientVer, path, appInfo) + } + + return validateClientVersionForYangAPI(clientVer, path, appInfo) +} + +// validateClientVersionForYangAPI checks theYangBaseVersion <= clientVer <= theYangBundleVersion +func validateClientVersionForYangAPI(clientVer Version, path string, appInfo *appInfo) error { + if theYangBaseVersion.GreaterThan(clientVer) { + glog.Errorf("Client version %s is less than base version %s", clientVer, theYangBaseVersion) + } else if clientVer.GreaterThan(theYangBundleVersion) { + glog.Errorf("Client version %s is more than server vesion %s", clientVer, theYangBundleVersion) + } else { + return nil + } + + return tlerr.TranslibUnsupportedClientVersion{ + ClientVersion: clientVer.String(), + ServerVersion: theYangBundleVersion.String(), + ServerBaseVersion: theYangBaseVersion.String(), + } +} + +// validateClientVersionForNonYangAPI checks client version for non-yang APIs +func validateClientVersionForNonYangAPI(clientVer Version, path string, appInfo *appInfo) error { + // Version checks are not supported for non-yang APIs. + // Client versions are ignored. + return nil +} + +//========= initialization ========= + +func init() { + path := filepath.Join(GetYangPath(), "version.xml") + if err := initYangVersionConfigFromFile(path); err != nil { + glog.Errorf("Error loading version config file; err=%v", err) + glog.Errorf("API VERSION CHECK IS DISABLED") + } else { + glog.Infof("*** Yang bundle version = %s", theYangBundleVersion) + glog.Infof("*** Yang base version = %s", theYangBaseVersion) + } +} + +// Load version config from file +func initYangVersionConfigFromFile(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + + defer f.Close() + + var configData struct { + YangBundleVersion Version `xml:"yang-bundle-version"` + } + + err = xml.NewDecoder(f).Decode(&configData) + if err != nil { + return err + } + + theYangBundleVersion = configData.YangBundleVersion + theYangBaseVersion = theYangBundleVersion.GetCompatibleBaseVersion() + + return nil +} + +// GetYangBundleVersion returns the API version for yang bundle hosted +// on this server. +func GetYangBundleVersion() Version { + return theYangBundleVersion +} - return err +// GetYangBaseVersion returns the base version or min version of yang APIs +// supported by this server. +func GetYangBaseVersion() Version { + return theYangBaseVersion } diff --git a/translib/version_test.go b/translib/version_test.go new file mode 100755 index 000000000000..eb9a093f2d10 --- /dev/null +++ b/translib/version_test.go @@ -0,0 +1,226 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// 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 ( + "fmt" + "testing" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +func ver(major, minor, patch uint32) Version { + return Version{Major: major, Minor: minor, Patch: patch} +} + +func TestVersionParseStr(t *testing.T) { + t.Run("empty", testVerSet("", ver(0, 0, 0), false)) + t.Run("0.0.0", testVerSet("0.0.0", ver(0, 0, 0), false)) + t.Run("1.0.0", testVerSet("1.0.0", ver(1, 0, 0), true)) + t.Run("1.2.3", testVerSet("1.2.3", ver(1, 2, 3), true)) + t.Run("1.-.-", testVerSet("1", Version{}, false)) + t.Run("1.2.-", testVerSet("1.2", Version{}, false)) + t.Run("1.-.3", testVerSet("1..2", Version{}, false)) + t.Run("neg_majr", testVerSet("-1.0.0", Version{}, false)) + t.Run("bad_majr", testVerSet("A.2.3", Version{}, false)) + t.Run("neg_minr", testVerSet("1.-2.0", Version{}, false)) + t.Run("bad_minr", testVerSet("1.B.3", Version{}, false)) + t.Run("neg_pat", testVerSet("1.2.-3", Version{}, false)) + t.Run("bad_pat", testVerSet("1.2.C", Version{}, false)) + t.Run("invalid", testVerSet("invalid", Version{}, false)) +} + +func testVerSet(vStr string, exp Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + v, err := NewVersion(vStr) + if err != nil { + if expSuccess == true { + t.Fatalf("Failed to parse \"%s\"; err=%v. Expected %v", vStr, err, exp) + } + return + } + + if !expSuccess { + t.Fatalf("Version string \"%s\" was expected to fail; but got %v", vStr, v) + } + + if v != exp { + t.Fatalf("Failed to parse \"%s\"; expected %v", vStr, v) + } + } +} + +func TestVersionGetBase(t *testing.T) { + t.Run("0.0.0", testGetBase(ver(0, 0, 0), ver(0, 0, 0))) + t.Run("0.0.1", testGetBase(ver(0, 0, 1), ver(0, 0, 1))) + t.Run("0.1.2", testGetBase(ver(0, 1, 2), ver(0, 1, 2))) + t.Run("1.0.0", testGetBase(ver(1, 0, 0), ver(1, 0, 0))) + t.Run("1.0.1", testGetBase(ver(1, 0, 1), ver(1, 0, 0))) + t.Run("1.2.1", testGetBase(ver(1, 2, 1), ver(1, 0, 0))) + t.Run("2.0.0", testGetBase(ver(2, 0, 0), ver(1, 0, 0))) + t.Run("2.3.4", testGetBase(ver(2, 3, 4), ver(1, 0, 0))) + t.Run("3.0.0", testGetBase(ver(5, 6, 7), ver(4, 0, 0))) +} + +func testGetBase(v, expBase Version) func(*testing.T) { + return func(t *testing.T) { + base := v.GetCompatibleBaseVersion() + if base != expBase { + t.Fatalf("Got base of %s as %s; expected %s", v, base, expBase) + } + } +} + +func setYangBundleVersion(v Version) { + theYangBundleVersion = v + theYangBaseVersion = v.GetCompatibleBaseVersion() +} + +func TestVersionCheck(t *testing.T) { + // Set yang bundle version tp 2.3.4 and try various ClientVersion + testVer, _ := NewVersion("2.3.4") + origVer := theYangBundleVersion + setYangBundleVersion(testVer) + defer setYangBundleVersion(origVer) // restore original version + + testVCheck(t, "", true) + testVCheck(t, "0.0.9", false) + testVCheck(t, "0.9.9", false) + testVCheck(t, "1.0.0", true) + testVCheck(t, "1.2.3", true) + testVCheck(t, "2.0.0", true) + testVCheck(t, "2.1.9", true) + testVCheck(t, "2.3.2", true) + testVCheck(t, "2.3.4", true) + testVCheck(t, "2.3.9", false) + testVCheck(t, "2.4.0", false) + testVCheck(t, "3.0.0", false) +} + +func testVCheck(t *testing.T, ver string, expSuccess bool) { + v, err := NewVersion(ver) + if ver != "" && err != nil { + t.Fatalf("Bad version \"%s\"", ver) + } + + t.Run(fmt.Sprintf("get_%s", ver), vGet(v, expSuccess)) + t.Run(fmt.Sprintf("create_%s", ver), vCreate(v, expSuccess)) + t.Run(fmt.Sprintf("update_%s", ver), vUpdate(v, expSuccess)) + t.Run(fmt.Sprintf("delete_%s", ver), vDelete(v, expSuccess)) + t.Run(fmt.Sprintf("replace_%s", ver), vReplace(v, expSuccess)) + t.Run(fmt.Sprintf("action_%s", ver), vAction(v, expSuccess)) + t.Run(fmt.Sprintf("subs_%s", ver), vSubscribe(v, expSuccess)) + t.Run(fmt.Sprintf("is_subs_%s", ver), vIsSubscribe(v, expSuccess)) +} + +var ( + tPath = "/openconfig-acl:acl" + tBody = []byte("{}") +) + +func vCreate(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Create(SetRequest{Path:tPath, Payload:tBody, ClientVersion: v}) + checkErr(t, err, expSuccess) + } +} + +func vUpdate(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Update(SetRequest{Path:tPath, Payload:tBody, ClientVersion: v}) + checkErr(t, err, expSuccess) + } +} + +func vReplace(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Replace(SetRequest{Path:tPath, Payload:tBody, ClientVersion: v}) + checkErr(t, err, expSuccess) + } +} + +func vDelete(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Delete(SetRequest{Path:tPath, ClientVersion: v}) + checkErr(t, err, expSuccess) + } +} + +func vGet(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Get(GetRequest{Path: tPath, ClientVersion: v}) + checkErr(t, err, expSuccess) + } +} + +func vAction(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Action(ActionRequest{Path: tPath, ClientVersion: v}) + checkErr(t, ignoreNotImpl(err), expSuccess) + } +} + +func vSubscribe(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + _, err := Subscribe(SubscribeRequest{Paths: []string{tPath}, ClientVersion: v}) + checkErr(t, ignoreNotImpl(err), expSuccess) + } +} + +func vIsSubscribe(v Version, expSuccess bool) func(*testing.T) { + return func(t *testing.T) { + req := IsSubscribeRequest{Paths: []string{tPath}, ClientVersion: v} + resp, err := IsSubscribeSupported(req) + if err == nil && len(resp) == 1 && resp[0].Err != nil { + err = resp[0].Err + } + checkErr(t, ignoreNotImpl(err), expSuccess) + } +} + +func isVersionError(err error) bool { + if _, ok := err.(tlerr.TranslibUnsupportedClientVersion); ok { + return true + } + return false +} + +func checkErr(t *testing.T, err error, expSuccess bool) { + if expSuccess && err != nil { + t.Fatalf("Unexpected %T %v", err, err) + } + if !expSuccess && !isVersionError(err) { + t.Fatalf("Unexpected %T %v; expected TranslibUnsupportedClientVersion", err, err) + } +} + +func ignoreNotImpl(err error) error { + switch err.(type) { + case nil: + return nil + case tlerr.NotSupportedError: + return nil + default: + e := err.Error() + if e == "Not supported" || e == "Not implemented" { + return nil + } + } + return err +} diff --git a/translib/yanglib_app.go b/translib/yanglib_app.go index c235a97677e0..9168eb5a1fbf 100644 --- a/translib/yanglib_app.go +++ b/translib/yanglib_app.go @@ -508,5 +508,5 @@ func GetYangPath() string { // 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. + return GetYangBundleVersion().String() }