From 6ce18c62d776f2198388345fb620435f024aaac7 Mon Sep 17 00:00:00 2001 From: Sachin Holla <51310506+sachinholla@users.noreply.github.com> Date: Wed, 26 Apr 2023 04:14:59 +0530 Subject: [PATCH] AppInterface enhancements for subscription (#67) * AppInterface enhancements for subscription Added new translateSubscribe() and processSubscribe() functions to appInterface as per HLD https://github.com/sonic-net/SONiC/pull/1287 Also added stub implementation of these functions to all existing app modules. It blindly treats all paths as non-db. Basic subscription features, without on_change, can be verified with this mapping. Signed-off-by: Sachin Holla * Update error types, test case styles, nil checks, etc * Remove tlerr import alias in yanglib_app * Non-pointer params in transleSubscribe and processSubscribe * Reorg old & new version comprision logic in EntryCompare func * appInterface method cleanup in acl_app --------- Signed-off-by: Sachin Holla --- translib/acl_app.go | 112 ++--------- translib/api_tests_app.go | 9 +- translib/app_interface.go | 48 ++--- translib/common_app.go | 57 +----- translib/internal/apis/db_diff.go | 115 ++++++++++++ translib/internal/apis/db_diff_test.go | 154 +++++++++++++++ translib/internal/apis/notify.go | 126 +++++++++++++ translib/intf_app.go | 36 +--- translib/lldp_app.go | 52 ++---- translib/pfm_app.go | 11 +- translib/subscribe_app_interface.go | 247 +++++++++++++++++++++++++ translib/subscribe_app_utils.go | 69 +++++++ translib/sys_app.go | 25 +-- translib/translib.go | 6 +- translib/yanglib_app.go | 42 +++-- 15 files changed, 836 insertions(+), 273 deletions(-) create mode 100644 translib/internal/apis/db_diff.go create mode 100644 translib/internal/apis/db_diff_test.go create mode 100644 translib/internal/apis/notify.go create mode 100644 translib/subscribe_app_interface.go create mode 100644 translib/subscribe_app_utils.go diff --git a/translib/acl_app.go b/translib/acl_app.go index ba86b5e471db..63981e72d50a 100644 --- a/translib/acl_app.go +++ b/translib/acl_app.go @@ -21,11 +21,11 @@ package translib import ( "bytes" - "errors" "fmt" "reflect" "strconv" "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/translib/ocbinds" "github.com/Azure/sonic-mgmt-common/translib/tlerr" @@ -127,109 +127,27 @@ func (app *AclApp) getAppRootObject() *ocbinds.OpenconfigAcl_Acl { } func (app *AclApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { - var err error - var keys []db.WatchKeys - log.Info("translateCreate:acl:path =", app.pathInfo.Template) - - keys, err = app.translateCRUCommon(d, CREATE) - return keys, err + return app.translateCRUCommon(d, CREATE) } func (app *AclApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { - var err error - var keys []db.WatchKeys - log.Info("translateUpdate:acl:path =", app.pathInfo.Template) - - keys, err = app.translateCRUCommon(d, UPDATE) - return keys, err + return app.translateCRUCommon(d, UPDATE) } func (app *AclApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { - var err error - var keys []db.WatchKeys - log.Info("translateReplace:acl:path =", app.pathInfo.Template) - - keys, err = app.translateCRUCommon(d, REPLACE) - return keys, err + return app.translateCRUCommon(d, REPLACE) } func (app *AclApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { - var err error - var keys []db.WatchKeys - log.Info("translateDelete:acl:path =", app.pathInfo.Template) - - return keys, err + return nil, nil } func (app *AclApp) translateGet(dbs [db.MaxDB]*db.DB) error { - var err error - log.Info("translateGet:acl:path =", app.pathInfo.Template) - return err + return nil } func (app *AclApp) translateAction(dbs [db.MaxDB]*db.DB) error { - err := errors.New("Not supported") - return err -} - -func (app *AclApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - pathInfo := NewPathInfo(path) - notifInfo := notificationInfo{dbno: db.ConfigDB} - notSupported := tlerr.NotSupportedError{ - Format: "Subscribe not supported", Path: path} - - if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/acl-sets") { - // Subscribing to top level ACL record is not supported. It requires listening - // to 2 tables (ACL and ACL_RULE); TransLib does not support it yet - if pathInfo.HasSuffix("/acl-sets") || - pathInfo.HasSuffix("/acl-set") || - pathInfo.HasSuffix("/acl-set{}{}") { - log.Errorf("Subscribe not supported for top level ACL %s", pathInfo.Template) - return nil, nil, notSupported - } - - t, err := getAclTypeOCEnumFromName(pathInfo.Var("type")) - if err != nil { - return nil, nil, err - } - - aclkey := getAclKeyStrFromOCKey(pathInfo.Var("name"), t) - - if strings.Contains(pathInfo.Template, "/acl-entry{}") { - // Subscribe for one rule - rulekey := "RULE_" + pathInfo.Var("sequence-id") - notifInfo.table = db.TableSpec{Name: RULE_TABLE} - notifInfo.key = asKey(aclkey, rulekey) - notifInfo.needCache = !pathInfo.HasSuffix("/acl-entry{}") - - } else if pathInfo.HasSuffix("/acl-entries") || pathInfo.HasSuffix("/acl-entry") { - // Subscribe for all rules of an ACL - notifInfo.table = db.TableSpec{Name: RULE_TABLE} - notifInfo.key = asKey(aclkey, "*") - - } else { - // Subscibe for ACL fields only - notifInfo.table = db.TableSpec{Name: ACL_TABLE} - notifInfo.key = asKey(aclkey) - notifInfo.needCache = true - } - - } else if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/interfaces") { - // Right now interface binding config is maintained within ACL - // table itself. Multiple ACLs can be bound to one intf; one - // inname can occur in multiple ACL entries. So we cannot map - // interface binding xpaths to specific ACL table entry keys. - // For now subscribe for full ACL table!! - notifInfo.table = db.TableSpec{Name: ACL_TABLE} - notifInfo.key = asKey("*") - notifInfo.needCache = true - - } else { - log.Errorf("Unknown path %s", pathInfo.Template) - return nil, nil, notSupported - } - - return nil, ¬ifInfo, nil + return tlerr.NotSupported("unsupported") } func (app *AclApp) processCreate(d *db.DB) (SetResponse, error) { @@ -295,10 +213,7 @@ func (app *AclApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { } func (app *AclApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { - var resp ActionResponse - err := errors.New("Not implemented") - - return resp, err + return ActionResponse{}, tlerr.New("not implemented") } func (app *AclApp) translateCRUCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { @@ -1716,10 +1631,19 @@ func getAclKeyStrFromOCKey(aclname string, acltype ocbinds.E_OpenconfigAcl_ACL_T return aclN + "_" + aclT } -/* Check if targetUriPath is child (subtree) of nodePath +/* +Check if targetUriPath is child (subtree) of nodePath The return value can be used to decide if subtrees needs to visited to fill the data or not. */ func isSubtreeRequest(targetUriPath string, nodePath string) bool { return strings.HasPrefix(targetUriPath, nodePath) } + +func (app *AclApp) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} + +func (app *AclApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") +} diff --git a/translib/api_tests_app.go b/translib/api_tests_app.go index 463a4b35c4ff..b80db81f4392 100644 --- a/translib/api_tests_app.go +++ b/translib/api_tests_app.go @@ -17,6 +17,7 @@ // // //////////////////////////////////////////////////////////////////////////////// +//go:build test // +build test package translib @@ -101,8 +102,12 @@ func (app *apiTests) translateAction(dbs [db.MaxDB]*db.DB) error { return nil } -func (app *apiTests) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - return nil, nil, nil +func (app *apiTests) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} + +func (app *apiTests) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } func (app *apiTests) processCreate(d *db.DB) (SetResponse, error) { diff --git a/translib/app_interface.go b/translib/app_interface.go index 7929a11eab0a..0c39a23fc5f6 100644 --- a/translib/app_interface.go +++ b/translib/app_interface.go @@ -11,7 +11,7 @@ // // // 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. // +// 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. // // // @@ -31,14 +31,15 @@ package translib import ( "errors" - log "github.com/golang/glog" - "github.com/openconfig/ygot/ygot" "reflect" "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" ) -//Structure containing app module information +// Structure containing app module information type appInfo struct { appType reflect.Type ygotRootType reflect.Type @@ -46,7 +47,7 @@ type appInfo struct { tablesToWatch []*db.TableSpec } -//Structure containing the app data coming from translib infra +// Structure containing the app data coming from translib infra type appData struct { path string payload []byte @@ -59,23 +60,23 @@ type appData struct { // These include RESTCONF query parameters like - depth, fields etc. type appOptions struct { - // depth limits subtree levels in the response data. - // 0 indicates unlimited depth. - // Valid for GET API only. - depth uint + // depth limits subtree levels in the response data. + // 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 + // 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 +// map containing the base path to app module info var appMap map[string]*appInfo -//array containing all the supported models +// array containing all the supported models var models []ModelData -//Interface for all App Modules +// Interface for all App Modules type appInterface interface { initialize(data appData) translateCreate(d *db.DB) ([]db.WatchKeys, error) @@ -84,16 +85,17 @@ type appInterface interface { translateDelete(d *db.DB) ([]db.WatchKeys, error) translateGet(dbs [db.MaxDB]*db.DB) error translateAction(dbs [db.MaxDB]*db.DB) error - translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) + translateSubscribe(req translateSubRequest) (translateSubResponse, error) processCreate(d *db.DB) (SetResponse, error) processUpdate(d *db.DB) (SetResponse, error) processReplace(d *db.DB) (SetResponse, error) processDelete(d *db.DB) (SetResponse, error) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) + processSubscribe(req processSubRequest) (processSubResponse, error) } -//App modules will use this function to register with App interface during boot up +// App modules will use this function to register with App interface during boot up func register(path string, info *appInfo) error { var err error log.Info("Registering for path =", path) @@ -114,7 +116,7 @@ func register(path string, info *appInfo) error { return err } -//Adds the model information to the supported models array +// Adds the model information to the supported models array func addModel(model *ModelData) error { var err error @@ -124,7 +126,7 @@ func addModel(model *ModelData) error { return err } -//Translib infra will use this function get the app info for a given path +// Translib infra will use this function get the app info for a given path func getAppModuleInfo(path string) (*appInfo, error) { log.Info("getAppModule called for path =", path) @@ -135,23 +137,23 @@ func getAppModuleInfo(path string) (*appInfo, error) { log.Info("found the entry in the map for path =", pattern) - return app, nil + return app, nil } /* If no specific app registered fallback to default/common app */ log.Infof("No app module registered for path %s hence fallback to default/common app", path) app := appMap["*"] - return app, nil + return app, nil } -//Get all the supported models +// Get all the supported models func getModels() []ModelData { return models } -//Creates a new app from the appType and returns it as an appInterface +// Creates a new app from the appType and returns it as an appInterface func getAppInterface(appType reflect.Type) (appInterface, error) { var err error appInstance := reflect.New(appType) diff --git a/translib/common_app.go b/translib/common_app.go index 725ab7707c79..f3af934c99ef 100644 --- a/translib/common_app.go +++ b/translib/common_app.go @@ -129,59 +129,12 @@ func (app *CommonApp) translateGet(dbs [db.MaxDB]*db.DB) error { return err } -func (app *CommonApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - var err error - var subscDt transformer.XfmrTranslateSubscribeInfo - var notifInfo notificationInfo - var notifOpts notificationOpts - txCache := new(sync.Map) - err = tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} - - log.Info("tranlateSubscribe:path", path) - subscDt, err = transformer.XlateTranslateSubscribe(path, dbs, txCache) - if subscDt.PType == transformer.OnChange { - notifOpts.pType = OnChange - } else { - notifOpts.pType = Sample - } - notifOpts.mInterval = subscDt.MinInterval - notifOpts.isOnChangeSupported = subscDt.OnChange - if err != nil { - log.Infof("returning: notificationOpts - %v, nil, error - %v", notifOpts, err) - return ¬ifOpts, nil, err - } - if subscDt.DbDataMap == nil { - log.Infof("DB data is nil so returning: notificationOpts - %v, nil, error - %v", notifOpts, err) - return ¬ifOpts, nil, err - } else { - for dbNo, dbDt := range(subscDt.DbDataMap) { - if (len(dbDt) == 0) { //ideally all tables for a given uri should be from same DB - continue - } - log.Infof("Adding to notifInfo, Db Data - %v for DB No - %v", dbDt, dbNo) - notifInfo.dbno = dbNo - // in future there will be, multi-table in a DB, support from translib, for now its just single table - for tblNm, tblDt := range(dbDt) { - notifInfo.table = db.TableSpec{Name:tblNm} - if (len(tblDt) == 1) { - for tblKy := range(tblDt) { - notifInfo.key = asKey(tblKy) - notifInfo.needCache = subscDt.NeedCache - } - } else { - if (len(tblDt) > 1) { - log.Warningf("More than one DB key found for subscription path - %v", path) - } else { - log.Warningf("No DB key found for subscription path - %v", path) - } - return ¬ifOpts, nil, err - } +func (app *CommonApp) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} - } - } - } - log.Infof("For path - %v, returning: notifOpts - %v, notifInfo - %v, error - nil", path, notifOpts, notifInfo) - return ¬ifOpts, ¬ifInfo, nil +func (app *CommonApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } func (app *CommonApp) translateAction(dbs [db.MaxDB]*db.DB) error { diff --git a/translib/internal/apis/db_diff.go b/translib/internal/apis/db_diff.go new file mode 100644 index 000000000000..56dea7bdf3c3 --- /dev/null +++ b/translib/internal/apis/db_diff.go @@ -0,0 +1,115 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2021 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 apis + +import ( + "fmt" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/db" +) + +// EntryDiff holds diff of two versions of a single db entry. +// It contains both old & new db.Value objects and list changed field names. +// Dummy "NULL" fields are ignored; array field names will have "@" suffix. +type EntryDiff struct { + OldValue db.Value // value before change; empty during entry create + NewValue db.Value // changed db value; empty during entry delete + EntryCreated bool // true if entry being created + EntryDeleted bool // true if entry being deleted + CreatedFields []string // fields added during entry update + UpdatedFields []string // fields modified during entry update + DeletedFields []string // fields deleted during entry update +} + +func (d *EntryDiff) String() string { + if d == nil { + return "" + } + return fmt.Sprintf( + "{EntryCreated=%t, EntryDeleted=%t, CreatedFields=%v, UpdatedFields=%v, DeletedFields=%v}", + d.EntryCreated, d.EntryDeleted, d.CreatedFields, d.UpdatedFields, d.DeletedFields) +} + +// IsEmpty returns true if this EntryDiff has no diff data -- either not initialized +// or both old and new values are identical. +func (d *EntryDiff) IsEmpty() bool { + return d == nil || (!d.EntryCreated && !d.EntryDeleted && + len(d.CreatedFields) == 0 && len(d.UpdatedFields) == 0 && len(d.DeletedFields) == 0) +} + +// EntryCompare function compares two db.Value objects representing two versions +// of a single db entry. Changes are returned as a DBEntryDiff pointer. +func EntryCompare(old, new db.Value) *EntryDiff { + diff := &EntryDiff{ + OldValue: old, + NewValue: new, + } + + switch oldExists, newExists := old.IsPopulated(), new.IsPopulated(); { + case !oldExists && newExists: + diff.EntryCreated = true + return diff + case oldExists && !newExists: + diff.EntryDeleted = true + return diff + case !oldExists && !newExists: + return diff + } + + // Both old & new versions exist.. compare fields + + for fldName := range old.Field { + if fldName == "NULL" { + continue + } + if _, fldOk := new.Field[fldName]; !fldOk { + diff.DeletedFields = append( + diff.DeletedFields, strings.TrimSuffix(fldName, "@")) + } + } + + for nf, nv := range new.Field { + if nf == "NULL" { + continue + } + if ov, exists := old.Field[nf]; !exists { + diff.CreatedFields = append( + diff.CreatedFields, strings.TrimSuffix(nf, "@")) + } else if ov != nv { + diff.UpdatedFields = append( + diff.UpdatedFields, strings.TrimSuffix(nf, "@")) + } + } + + return diff +} + +// EntryFields returns the list of field names in a DB entry. +// Ignores the dummy NULL field and also removes @ suffix of array fields. +func EntryFields(v db.Value) []string { + var fields []string + for f := range v.Field { + if f != "NULL" { + fields = append(fields, strings.TrimSuffix(f, "@")) + } + } + return fields +} diff --git a/translib/internal/apis/db_diff_test.go b/translib/internal/apis/db_diff_test.go new file mode 100644 index 000000000000..1a56f8ab7291 --- /dev/null +++ b/translib/internal/apis/db_diff_test.go @@ -0,0 +1,154 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2021 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 apis + +import ( + "reflect" + "sort" + "testing" + + "github.com/Azure/sonic-mgmt-common/translib/db" +) + +func TestDiff(t *testing.T) { + var testCases = []diffTestCase{ + { + Name: "empty", + Old: map[string]string{}, + New: map[string]string{}, + Diff: EntryDiff{}, + }, { + Name: "equal", + Old: map[string]string{"one": "1111", "two": "2222"}, + New: map[string]string{"one": "1111", "two": "2222"}, + Diff: EntryDiff{}, + }, { + Name: "equal_arr", + Old: map[string]string{"one": "1111", "two": "2222", "arr@": "1,2,3"}, + New: map[string]string{"one": "1111", "two": "2222", "arr@": "1,2,3"}, + Diff: EntryDiff{}, + }, { + Name: "equal_null", + Old: map[string]string{"NULL": "NULL"}, + New: map[string]string{"NULL": "NULL"}, + Diff: EntryDiff{}, + }, { + Name: "create_entry", + Old: map[string]string{}, + New: map[string]string{"one": "1111", "two": "2222", "arr@": "1,2,3"}, + Diff: EntryDiff{EntryCreated: true}, + }, { + Name: "create_null_entry", + Old: map[string]string{}, + New: map[string]string{"NULL": "NULL"}, + Diff: EntryDiff{EntryCreated: true}, + }, { + Name: "delete_entry", + Old: map[string]string{"one": "1111", "two": "2222", "arr@": "1,2,3"}, + New: map[string]string{}, + Diff: EntryDiff{EntryDeleted: true}, + }, { + Name: "add_field", + Old: map[string]string{"one": "1111"}, + New: map[string]string{"one": "1111", "two": "2222"}, + Diff: EntryDiff{CreatedFields: []string{"two"}}, + }, { + Name: "add_fields", + Old: map[string]string{"one": "1111"}, + New: map[string]string{"one": "1111", "two": "2222", "arr@": "1,2,3", "foo": "bar"}, + Diff: EntryDiff{CreatedFields: []string{"two", "arr", "foo"}}, + }, { + Name: "add_null", + Old: map[string]string{"one": "1111"}, + New: map[string]string{"one": "1111", "NULL": "NULL"}, + Diff: EntryDiff{}, + }, { + Name: "del_fields", + Old: map[string]string{"one": "1111", "two": "2222", "foo": "bar"}, + New: map[string]string{"one": "1111"}, + Diff: EntryDiff{DeletedFields: []string{"two", "foo"}}, + }, { + Name: "del_arr_fields", + Old: map[string]string{"one": "1111", "arr@": "1,2", "foo@": "bar"}, + New: map[string]string{"foo@": "bar"}, + Diff: EntryDiff{DeletedFields: []string{"one", "arr"}}, + }, { + Name: "del_null", + Old: map[string]string{"one": "1111", "NULL": "NULL"}, + New: map[string]string{"one": "1111"}, + Diff: EntryDiff{}, + }, { + Name: "mod_fields", + Old: map[string]string{"one": "1111", "two": "2222"}, + New: map[string]string{"one": "0001", "two": "2222"}, + Diff: EntryDiff{UpdatedFields: []string{"one"}}, + }, { + Name: "mod_arr_fields", + Old: map[string]string{"one": "1111", "foo@": "2222"}, + New: map[string]string{"one": "0001", "foo@": "1,2,3"}, + Diff: EntryDiff{UpdatedFields: []string{"one", "foo"}}, + }, { + Name: "cru_fields", + Old: map[string]string{"one": "1111", "foo@": "2222", "NULL": "NULL"}, + New: map[string]string{"one": "0001", "two": "2222"}, + Diff: EntryDiff{ + CreatedFields: []string{"two"}, + UpdatedFields: []string{"one"}, + DeletedFields: []string{"foo"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(tt *testing.T) { tc.run(tt) }) + } +} + +type diffTestCase struct { + Name string + Old map[string]string + New map[string]string + Diff EntryDiff +} + +func (tc *diffTestCase) run(t *testing.T) { + t.Helper() + old, new, exp := tc.Old, tc.New, tc.Diff + d := EntryCompare(db.Value{Field: old}, db.Value{Field: new}) + ok := reflect.DeepEqual(d.OldValue.Field, old) && + reflect.DeepEqual(d.NewValue.Field, new) && + d.EntryCreated == exp.EntryCreated && + d.EntryDeleted == exp.EntryDeleted && + arrayEquals(d.CreatedFields, exp.CreatedFields) && + arrayEquals(d.UpdatedFields, exp.UpdatedFields) && + arrayEquals(d.DeletedFields, exp.DeletedFields) + if !ok { + t.Errorf("Old values = %v", old) + t.Errorf("New values = %v", new) + t.Errorf("Expect diff = %v", exp.String()) + t.Errorf("Actual diff = %v", d.String()) + } +} + +func arrayEquals(a1, a2 []string) bool { + sort.Strings(a1) + sort.Strings(a2) + return reflect.DeepEqual(a1, a2) +} diff --git a/translib/internal/apis/notify.go b/translib/internal/apis/notify.go new file mode 100644 index 000000000000..4369599d0802 --- /dev/null +++ b/translib/internal/apis/notify.go @@ -0,0 +1,126 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2021 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 apis + +import ( + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygot/ygot" +) + +// ProcessOnChange is a callback function to generate notification messages +// from a DB entry change data. Apps can implement their own diff & notify logic +// and plugin into the translib onchange subscribe infra. +// This callback receives a NotificationContext object containing change details +// of one DB entry and a NotificationSender interface to push translated messages. +// NotificationContext.Path is the subscribed path, which indicates the yang +// attributes that can be included in the notification. Implementations should +// inspect the provided subscribe path and DB changes; and translate them into +// zero, one or more notifications and push them out. +type ProcessOnChange func(*NotificationContext, NotificationSender) + +func (p ProcessOnChange) String() string { + if p == nil { + return "" + } + fullName := runtime.FuncForPC(reflect.ValueOf(p).Pointer()).Name() + return strings.TrimPrefix(fullName, "github.com/Azure/sonic-mgmt-common/translib/") +} + +// NotificationContext contains the subscribed path and details of a DB entry +// change that may result in a notification message. +type NotificationContext struct { + Path *gnmi.Path // subscribe path, can include wildcards + Db *db.DB // db in which the entry was modified + Table *db.TableSpec // table for the modified entry + Key *db.Key // key for modified entry + EntryDiff // diff info for modified entry + AllDb [db.MaxDB]*db.DB + Opaque interface{} // app specific opaque data +} + +func (nc *NotificationContext) String() string { + b := new(strings.Builder) + fmt.Fprintf(b, "{Path='%s'", PathToString(nc.Path)) + fmt.Fprintf(b, ", dbno=%d, table=%v, key=%v", nc.Db.Opts.DBNo, nc.Table, nc.Key) + fmt.Fprintf(b, ", oldValue=%v, newValue=%v", nc.OldValue.Field, nc.NewValue.Field) + fmt.Fprintf(b, ", diff=%v}", &nc.EntryDiff) + return b.String() +} + +// NotificationSender provides methods to send notification message to +// the clients. Translib subscribe infra implements this interface. +type NotificationSender interface { + Send(*Notification) // Send a notification message to clients +} + +// Notification is a message containing deleted and updated values +// for a yang path. +type Notification struct { + // Path is an absolute gnmi path of the changed yang container + // or list instance. MUST NOT be a leaf path. + Path string + // Delete is the list of deleted subpaths (relative to Path). + // Should contain one empty string if the Path itself was deleted. + // Can be a nil or empty list if there are no delete paths. + Delete []string + // Update holds all the updated values (new+modified) within the Path. + // MUST be the YGOT struct corresponding to the Path. + // Can be nil if there are no updated values; or specified as UpdatePaths. + Update ygot.ValidatedGoStruct + // UpdatePaths holds the list of updated subpaths (relative to Path). + // Nil/empty if there are no updates or specified as Update ygot value. + // Update and UpdatePaths MUST NOT overlap to prevent duplicate notifications. + UpdatePaths []string +} + +// Minimum Interval for sample notification +const ( + SAMPLE_NOTIFICATION_MIN_INTERVAL = 20 +) + +// DeleteActionType indicates how db delete be handled w.r.t a path. +// By default, db delete will be treated as delete of mapped path. +type DeleteActionType uint8 + +const ( + // InspectPathOnDelete action attempts Get of mapped path + // on db delete; notifies delete/update accordingly + InspectPathOnDelete DeleteActionType = iota + 1 + // InspectLeafOnDelete action attempts Get of each mapped leaf + // on db delete; and notifies delete/update for each + InspectLeafOnDelete +) + +// PathToString returns a string representation of gnmi path +func PathToString(p *gnmi.Path) string { + if p == nil { + return "" + } + if s, err := ygot.PathToString(p); err == nil { + return s + } + return "" +} diff --git a/translib/intf_app.go b/translib/intf_app.go index b85fe95047d1..339567f569a0 100644 --- a/translib/intf_app.go +++ b/translib/intf_app.go @@ -250,36 +250,12 @@ func (app *IntfApp) translateAction(dbs [db.MaxDB]*db.DB) error { return err } -func (app *IntfApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - app.appDB = dbs[db.ApplDB] - pathInfo := NewPathInfo(path) - notifInfo := notificationInfo{dbno: db.ApplDB} - notSupported := tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} - - if isSubtreeRequest(pathInfo.Template, "/openconfig-interfaces:interfaces") { - if pathInfo.HasSuffix("/interface{}") || - pathInfo.HasSuffix("/config") || - pathInfo.HasSuffix("/state") { - log.Errorf("Subscribe not supported for %s!", pathInfo.Template) - return nil, nil, notSupported - } - ifKey := pathInfo.Var("name") - if len(ifKey) == 0 { - return nil, nil, errors.New("ifKey given is empty!") - } - log.Info("Interface name = ", ifKey) - err := app.validateInterface(app.appDB, ifKey, db.Key{Comp: []string{ifKey}}) - if err != nil { - return nil, nil, err - } - if pathInfo.HasSuffix("/state/oper-status") { - notifInfo.table = db.TableSpec{Name: "PORT_TABLE"} - notifInfo.key = asKey(ifKey) - notifInfo.needCache = true - return ¬ificationOpts{pType: OnChange}, ¬ifInfo, nil - } - } - return nil, nil, notSupported +func (app *IntfApp) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} + +func (app *IntfApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } func (app *IntfApp) processCreate(d *db.DB) (SetResponse, error) { diff --git a/translib/lldp_app.go b/translib/lldp_app.go index 08a4109c7630..2812765d2b4e 100644 --- a/translib/lldp_app.go +++ b/translib/lldp_app.go @@ -19,16 +19,17 @@ package translib import ( - "strconv" - "reflect" - "errors" - "github.com/Azure/sonic-mgmt-common/translib/db" - "github.com/Azure/sonic-mgmt-common/translib/ocbinds" - "github.com/openconfig/ygot/ygot" - log "github.com/golang/glog" - "strings" - "encoding/hex" - "github.com/Azure/sonic-mgmt-common/translib/tlerr" + "encoding/hex" + "errors" + "reflect" + "strconv" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" ) const ( @@ -137,31 +138,12 @@ func (app *lldpApp) translateAction(dbs [db.MaxDB]*db.DB) error { return err } -func (app *lldpApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - pathInfo := NewPathInfo(path) - notifInfo := notificationInfo{dbno: db.ApplDB} - notSupported := tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} - - if isSubtreeRequest(pathInfo.Template, "/openconfig-lldp:lldp/interfaces") { - if pathInfo.HasSuffix("/neighbors") || - pathInfo.HasSuffix("/config") || - pathInfo.HasSuffix("/state") { - log.Errorf("Subscribe not supported for %s!", pathInfo.Template) - return nil, nil, notSupported - } - ifKey := pathInfo.Var("name") - if len(ifKey) == 0 { - return nil, nil, errors.New("ifKey given is empty!") - } - log.Info("Interface name = ", ifKey) - if pathInfo.HasSuffix("/interface{}") { - notifInfo.table = db.TableSpec{Name: "LLDP_ENTRY_TABLE"} - notifInfo.key = asKey(ifKey) - notifInfo.needCache = true - return ¬ificationOpts{pType: OnChange}, ¬ifInfo, nil - } - } - return nil, nil, notSupported +func (app *lldpApp) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} + +func (app *lldpApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } func (app *lldpApp) processCreate(d *db.DB) (SetResponse, error) { diff --git a/translib/pfm_app.go b/translib/pfm_app.go index 18a28a81cce0..d7f8884f59ef 100644 --- a/translib/pfm_app.go +++ b/translib/pfm_app.go @@ -24,6 +24,7 @@ import ( "errors" "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" "github.com/openconfig/ygot/ygot" log "github.com/golang/glog" ) @@ -77,12 +78,14 @@ func (app *PlatformApp) translateAction(dbs [db.MaxDB]*db.DB) error { return err } -func (app *PlatformApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - - var err error - return nil, nil, err +func (app *PlatformApp) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} +func (app *PlatformApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } + func (app *PlatformApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { var err error var keys []db.WatchKeys diff --git a/translib/subscribe_app_interface.go b/translib/subscribe_app_interface.go new file mode 100644 index 000000000000..00ec2adaed68 --- /dev/null +++ b/translib/subscribe_app_interface.go @@ -0,0 +1,247 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2021 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" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/internal/apis" + "github.com/openconfig/gnmi/proto/gnmi" +) + +/** + *This file contains type definitions to be used by app modules to + * handle subscribe requests. + */ + +// translateSubRequest is the input for translateSubscribe callback +type translateSubRequest struct { + ctxID interface{} // request id for logging + path string // subscribe path + mode NotificationType // requested notification type + recurse bool // whether mappings for child paths are required + dbs [db.MaxDB]*db.DB // DB objects for querying, if needed +} + +// translateSubResponse is the output returned by app modules +// from translateSubscribe callback. +type translateSubResponse struct { + // ntfAppInfoTrgt includes the notificationAppInfo mappings for top + // level tables corresponding to the subscribe path. At least one + // such mapping should be present. + ntfAppInfoTrgt []*notificationAppInfo + + // ntfAppInfoTrgtChlds includes notificationAppInfo mappings for the + // dependent tables of the entries in ntfAppInfoTrgt. Should be nil + // if there are no dependent tables. + ntfAppInfoTrgtChlds []*notificationAppInfo +} + +// notificationAppInfo contains the details for monitoring db notifications +// for a given path. App modules provide these details for each subscribe +// path. One notificationAppInfo object must inclue details for one db table. +// One subscribe path can map to multiple notificationAppInfo. +type notificationAppInfo struct { + // database index for the DB key represented by this notificationAppInfo. + // Should be db.MaxDB for non-DB data provider cases. + dbno db.DBNum + + // table name. Should be nil for non-DB case. + table *db.TableSpec + + // key components without table name prefix. Can include wildcards. + // Should be nil for non-DB case. + key *db.Key + + // keyGroupComps holds component indices that uniquely identify the path. + // Required only when the db entry represents leaf-list instances. + keyGroupComps []int + + // path to which the key maps to. Can include wildcard keys. + // Should match request path -- should not point to any node outside + // the yang segment of request path. + path *gnmi.Path + + // handlerFunc is the custom on_change event handler callback. + // Apps can implement their own diff & translate logic in this callback. + handlerFunc apis.ProcessOnChange + + // dbFieldYangPathMap is the mapping of db entry field to the yang + // field (leaf/leaf-list) for the input path. + dbFldYgPathInfoList []*dbFldYgPathInfo + + // deleteAction indicates how entry delete be handled for this path. + // Required only when db entry represents partial data for the path, + // or to workaround out of order deletes due to backend limitations. + deleteAction apis.DeleteActionType + + //fldScanPattern indicates the scan type is based on field names and + // also the pattern to match the field names in the given table + fieldScanPattern string + + // isOnChangeSupported indicates if on-change notification is + // supported for the input path. Table and key mappings should + // be filled even if on-change is not supported. + isOnChangeSupported bool + + // mInterval indicates the minimum sample interval supported for + // the input path. Can be set to 0 (default value) to indicate + // system default interval. + mInterval int + + // pType indicates the preferred notification type for the input + // path. Used when gNMI client subscribes with "TARGET_DEFINED" mode. + pType NotificationType + + // opaque data can be used to store context information to assist + // future key-to-path translations. This is an optional data item. + // Apps can store any context information based on their logic. + // Translib passes this back to the processSubscribe function when + // it detects changes to the DB entry for current key or key pattern. + opaque interface{} + + // isDataSrcDynamic can be used to identify the type of the data source + isDataSrcDynamic bool +} + +type dbFldYgPathInfo struct { + rltvPath string + dbFldYgPathMap map[string]string //db field to leaf / rel. path to leaf +} + +// processSubRequest is the input for app module's processSubscribe function. +// It includes a path template (with wildcards) and one db key that needs to +// be mapped to the path. +type processSubRequest struct { + ctxID interface{} // context id for logging + path *gnmi.Path // path template to be filled -- contains wildcards + + // DB entry info to be used for filling the path template + dbno db.DBNum + table *db.TableSpec + key *db.Key + entry *db.Value // updated or deleted db entry. DO NOT MODIFY + + // List of all DB objects. Apps should only use these DB objects + // to query db if they need additional data for translation. + dbs [db.MaxDB]*db.DB + + // App specific opaque data -- can be used to pass context data + // between translateSubscribe and processSubscribe. + opaque interface{} +} + +// processSubResponse is the output data structure of processSubscribe +// function. Includes the path with wildcards resolved. Translib validates +// if this path matches the template in processSubRequest. +type processSubResponse struct { + // path with wildcards resolved + path *gnmi.Path +} + +func (ni *notificationAppInfo) String() string { + if ni == nil { + return "" + } + var b strings.Builder + fmt.Fprintf(&b, "{path='%s'", apis.PathToString(ni.path)) + fmt.Fprintf(&b, ", db=%s, ts=%v, key=%v", ni.dbno, tableInfo(ni.table), keyInfo(ni.key)) + if len(ni.keyGroupComps) != 0 { + fmt.Fprintf(&b, ", keyGrp=%v", ni.keyGroupComps) + } + if len(ni.fieldScanPattern) != 0 { + fmt.Fprintf(&b, ", fieldScanPattern=%v", ni.fieldScanPattern) + } + fmt.Fprintf(&b, ", dynamic=%v", ni.isDataSrcDynamic) + fmt.Fprintf(&b, ", fields={") + for i, fi := range ni.dbFldYgPathInfoList { + if i != 0 { + fmt.Fprintf(&b, ", ") + } + fmt.Fprintf(&b, "%s=%v", fi.rltvPath, fi.dbFldYgPathMap) + } + fmt.Fprintf(&b, "}, delAction=%v", ni.deleteAction) + if ni.handlerFunc != nil { + fmt.Fprintf(&b, ", handlerFunc=%s", ni.handlerFunc) + } + fmt.Fprintf(&b, ", onchange=%v, preferred=%v, m_int=%d", ni.isOnChangeSupported, ni.pType, ni.mInterval) + fmt.Fprintf(&b, "}") + return b.String() +} + +// isNonDB returns true if the notificationAppInfo ni is a non-DB mapping. +func (ni *notificationAppInfo) isNonDB() bool { + return ni.dbno < 0 || ni.dbno >= db.MaxDB || ni.table == nil || ni.key == nil +} + +// isLeafPath returns true if the notificationAppInfo has a leaf path. +func (ni *notificationAppInfo) isLeafPath() bool { + // when notificationAppInfo.path is a leaf path, following conditions + // MUST be true. + // - ni.dbFldYgPathInfoList) has only 1 entry + // - ni.dbFldYgPathInfoList[0].rltvPath == "" + // - ni.dbFldYgPathInfoList[0].dbFldYgPathMap has only 1 entry + // with empty yang field (map value) + for _, pmap := range ni.dbFldYgPathInfoList { + if len(pmap.rltvPath) != 0 { + return false + } + for _, yfield := range pmap.dbFldYgPathMap { + if len(yfield) != 0 && yfield[0] != '{' { + return false + } + } + } + return true +} + +func (r processSubResponse) String() string { + return fmt.Sprintf("{path=\"%s\"}", apis.PathToString(r.path)) +} + +// dbInfo returns display information for a db object +func dbInfo(d *db.DB) interface{} { + if d != nil { + return d.Opts.DBNo + } + return nil +} + +// keyInfo returns display information for a db key object +func keyInfo(k *db.Key) interface{} { + if k != nil { + return k.Comp + } + return nil +} + +// tableInfo returns display information for a db table object +func tableInfo(t *db.TableSpec) interface{} { + switch { + case t == nil: + return nil + case t.CompCt == 0: + return t.Name + default: + return fmt.Sprintf("%s.%d", t.Name, t.CompCt) + } +} diff --git a/translib/subscribe_app_utils.go b/translib/subscribe_app_utils.go new file mode 100644 index 000000000000..ded978fb0cc4 --- /dev/null +++ b/translib/subscribe_app_utils.go @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2023 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" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/openconfig/ygot/ygot" +) + +// emptySubscribeResponse returns a translateSubResponse containing a non-db mapping +// for the given path +func emptySubscribeResponse(reqPath string) (translateSubResponse, error) { + p, err := ygot.StringToStructuredPath(reqPath) + if err != nil { + return translateSubResponse{}, err + } + appInfo := ¬ificationAppInfo{ + path: p, + dbno: db.MaxDB, // non-DB + isOnChangeSupported: false, + } + return translateSubResponse{ + ntfAppInfoTrgt: []*notificationAppInfo{appInfo}, + }, nil +} + +// translateSubscribeBridge calls the new translateSubscribe() on an app and returns the +// responses as per old signature. Will be removed after enhancing translib.Subscribe() API +func translateSubscribeBridge(path string, app appInterface, dbs [db.MaxDB]*db.DB) (*notificationOpts, *notificationInfo, error) { + var nAppInfo *notificationAppInfo + resp, err := app.translateSubscribe(translateSubRequest{path: path, dbs: dbs}) + if err == nil && len(resp.ntfAppInfoTrgt) != 0 { + nAppInfo = resp.ntfAppInfoTrgt[0] + } + if nAppInfo == nil { + return nil, nil, fmt.Errorf("subscribe not supported (%w)", err) + } + + nOpts := ¬ificationOpts{ + isOnChangeSupported: nAppInfo.isOnChangeSupported, + pType: nAppInfo.pType, + mInterval: nAppInfo.mInterval, + } + nInfo := ¬ificationInfo{dbno: nAppInfo.dbno} + if !nAppInfo.isNonDB() { + nInfo.table, nInfo.key = *nAppInfo.table, *nAppInfo.key + } + + return nOpts, nInfo, nil +} diff --git a/translib/sys_app.go b/translib/sys_app.go index 950cb2b822ba..08502e627172 100644 --- a/translib/sys_app.go +++ b/translib/sys_app.go @@ -20,12 +20,14 @@ package translib import ( "errors" - log "github.com/golang/glog" - "github.com/openconfig/ygot/ygot" "reflect" "strconv" + "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" ) type SysApp struct { @@ -77,14 +79,16 @@ func (app *SysApp) getAppRootObject() *ocbinds.OpenconfigSystem_System { } func (app *SysApp) translateAction(dbs [db.MaxDB]*db.DB) error { - err := errors.New("Not supported") - return err + err := errors.New("Not supported") + return err } -func (app *SysApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { - var err error +func (app *SysApp) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} - return nil, nil, err +func (app *SysApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } func (app *SysApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { @@ -346,9 +350,8 @@ func (app *SysApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { } func (app *SysApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { - var resp ActionResponse - err := errors.New("Not implemented") + var resp ActionResponse + err := errors.New("Not implemented") - return resp, err + return resp, err } - diff --git a/translib/translib.go b/translib/translib.go index 0e8167732529..9b5a9693c181 100644 --- a/translib/translib.go +++ b/translib/translib.go @@ -883,7 +883,7 @@ func Subscribe(req SubscribeRequest) ([]*IsSubscribeResponse, error) { continue } - nOpts, nInfo, errApp := (*app).translateSubscribe(dbs, path) + nOpts, nInfo, errApp := translateSubscribeBridge(path, *app, dbs) if nOpts != nil { if nOpts.mInterval != 0 { @@ -910,7 +910,7 @@ func Subscribe(req SubscribeRequest) ([]*IsSubscribeResponse, error) { continue } else { - if nInfo == nil { + if nInfo == nil || !resp[i].IsOnChangeSupported { sErr = tlerr.NotSupportedError{ Format: "Subscribe not supported", Path: path} resp[i].Err = sErr @@ -980,7 +980,7 @@ func IsSubscribeSupported(req IsSubscribeRequest) ([]*IsSubscribeResponse, error continue } - nOpts, _, errApp := (*app).translateSubscribe(dbs, path) + nOpts, _, errApp := translateSubscribeBridge(path, *app, dbs) if nOpts != nil { if nOpts.mInterval != 0 { diff --git a/translib/yanglib_app.go b/translib/yanglib_app.go index 9168eb5a1fbf..d12df0bc56d6 100644 --- a/translib/yanglib_app.go +++ b/translib/yanglib_app.go @@ -28,7 +28,7 @@ import ( "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/tlerr" "github.com/Azure/sonic-mgmt-common/translib/transformer" "github.com/golang/glog" @@ -87,19 +87,19 @@ func (app *yanglibApp) initialize(data appData) { } func (app *yanglibApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { - return nil, errors.NotSupported("Unsupported") + return nil, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { - return nil, errors.NotSupported("Unsupported") + return nil, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { - return nil, errors.NotSupported("Unsupported") + return nil, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { - return nil, errors.NotSupported("Unsupported") + return nil, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) translateGet(dbs [db.MaxDB]*db.DB) error { @@ -107,31 +107,35 @@ func (app *yanglibApp) translateGet(dbs [db.MaxDB]*db.DB) error { } func (app *yanglibApp) translateAction(dbs [db.MaxDB]*db.DB) error { - return errors.NotSupported("Unsupported") + return tlerr.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) translateSubscribe(req translateSubRequest) (translateSubResponse, error) { + return emptySubscribeResponse(req.path) +} + +func (app *yanglibApp) processSubscribe(req processSubRequest) (processSubResponse, error) { + return processSubResponse{}, tlerr.New("not implemented") } func (app *yanglibApp) processCreate(d *db.DB) (SetResponse, error) { - return SetResponse{}, errors.NotSupported("Unsupported") + return SetResponse{}, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) processUpdate(d *db.DB) (SetResponse, error) { - return SetResponse{}, errors.NotSupported("Unsupported") + return SetResponse{}, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) processReplace(d *db.DB) (SetResponse, error) { - return SetResponse{}, errors.NotSupported("Unsupported") + return SetResponse{}, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) processDelete(d *db.DB) (SetResponse, error) { - return SetResponse{}, errors.NotSupported("Unsupported") + return SetResponse{}, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { - return ActionResponse{}, errors.NotSupported("Unsupported") + return ActionResponse{}, tlerr.NotSupported("Unsupported") } func (app *yanglibApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { @@ -175,7 +179,7 @@ func (app *yanglibApp) copyOneModuleInfo(fromMods *ocbinds.IETFYangLibrary_Modul 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) + return tlerr.NotFound("Module %s@%s not found", key.Name, key.Revision) } switch pt := app.pathInfo.Template; { @@ -184,7 +188,7 @@ func (app *yanglibApp) copyOneModuleInfo(fromMods *ocbinds.IETFYangLibrary_Modul if len(from.Deviation) != 0 { to.Deviation = from.Deviation } else { - return errors.NotFound("Module %s@%s has no deviations", key.Name, key.Revision) + return tlerr.NotFound("Module %s@%s has no deviations", key.Name, key.Revision) } case strings.Contains(pt, "/deviation{}{}"): @@ -195,7 +199,7 @@ func (app *yanglibApp) copyOneModuleInfo(fromMods *ocbinds.IETFYangLibrary_Modul if devmod := from.Deviation[devkey]; devmod != nil { *to.Deviation[devkey] = *devmod } else { - return errors.NotFound("Module %s@%s has no deviation %s@%s", + return tlerr.NotFound("Module %s@%s has no deviation %s@%s", key.Name, key.Revision, devkey.Name, devkey.Revision) } @@ -204,7 +208,7 @@ func (app *yanglibApp) copyOneModuleInfo(fromMods *ocbinds.IETFYangLibrary_Modul if len(from.Submodule) != 0 { to.Submodule = from.Submodule } else { - return errors.NotFound("Module %s@%s has no submodules", key.Name, key.Revision) + return tlerr.NotFound("Module %s@%s has no submodules", key.Name, key.Revision) } case strings.Contains(pt, "/submodule{}{}"): @@ -215,7 +219,7 @@ func (app *yanglibApp) copyOneModuleInfo(fromMods *ocbinds.IETFYangLibrary_Modul if submod := from.Submodule[subkey]; submod != nil { *to.Submodule[subkey] = *submod } else { - return errors.NotFound("Module %s@%s has no submodule %s@%s", + return tlerr.NotFound("Module %s@%s has no submodule %s@%s", key.Name, key.Revision, subkey.Name, subkey.Revision) } @@ -316,7 +320,7 @@ func (yb *yanglibBuilder) loadYangs() error { } if err := mods.Read(f); err != nil { glog.Errorf("Failed to parse %s; err=%v", f, err) - return errors.New("System error") + return tlerr.New("System error") } parsed++ }