From 01853bfb88c6c51dc0e619ad829a58e42d82dbec Mon Sep 17 00:00:00 2001 From: Niranjani Vivek Date: Fri, 22 May 2026 14:37:44 +0000 Subject: [PATCH] Draft PR for Components Root Signed-off-by: Niranjani Vivek --- .../openconfig-platform-annot.yang | 16 + translib/transformer/xfmr_platform.go | 583 +++++++++++++++ translib/transformer/xfmr_platform_test.go | 696 ++++++++++++++++++ 3 files changed, 1295 insertions(+) create mode 100644 models/yang/annotations/openconfig-platform-annot.yang create mode 100644 translib/transformer/xfmr_platform.go create mode 100644 translib/transformer/xfmr_platform_test.go diff --git a/models/yang/annotations/openconfig-platform-annot.yang b/models/yang/annotations/openconfig-platform-annot.yang new file mode 100644 index 000000000..59ef8b65d --- /dev/null +++ b/models/yang/annotations/openconfig-platform-annot.yang @@ -0,0 +1,16 @@ +module openconfig-platform-annot { + + yang-version "1"; + namespace "http://openconfig.net/yang/platform-annot"; + prefix "oc-platform-annot"; + + import openconfig-platform { prefix oc-platform; } + import sonic-extensions { prefix sonic-ext; } + + deviation /oc-platform:components/oc-platform:component { + deviate add { + sonic-ext:subtree-transformer "pfm_components_xfmr"; + sonic-ext:path-transformer "pfm_components_path_xfmr"; + } + } +} diff --git a/translib/transformer/xfmr_platform.go b/translib/transformer/xfmr_platform.go new file mode 100644 index 000000000..0c4530e0e --- /dev/null +++ b/translib/transformer/xfmr_platform.go @@ -0,0 +1,583 @@ +package transformer + +import ( + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "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 ( + NODE_CFG_TBL = "NODE_CFG" + + IC_NAME_PREFIX = "integrated_circuit" + + /** Upper-level URIs **/ + COMP = "/openconfig-platform:components/component" + COMP_ST = "/openconfig-platform:components/component/state" + COMP_STATE_MFG_NAME = "/openconfig-platform:components/component/state/mfg-name" + COMP_STATE_NAME = "/openconfig-platform:components/component/state/name" + COMP_STATE_FIRM_VER = "/openconfig-platform:components/component/state/firmware-version" + COMP_STATE_TEMP_CTR = "/openconfig-platform:components/component/state/temperature" + COMP_STATE_PART_NO = "/openconfig-platform:components/component/state/part-no" + COMP_STATE_SERIAL_NO = "/openconfig-platform:components/component/state/serial-no" + COMP_STATE_HW_VER = "/openconfig-platform:components/component/state/hardware-version" + + COMP_STATE_PARENT = "/openconfig-platform:components/component/state/parent" + COMP_STATE_INSTALL_POSITION = "/openconfig-platform:components/component/state/install-position" + COMP_STATE_INSTALL_COMPONENT = "/openconfig-platform:components/component/state/install-component" +) + +type componentType int64 + +const ( + CompTypeInvalid componentType = iota + CompTypeIC +) + +type PathType int + +const ( + /* Represents all paths under /components/component */ + AllPaths PathType = iota + /* Represents all paths under /components/component/state */ + StatePaths +) + +func (pt PathType) String() string { + switch pt { + case AllPaths: + return "AllPaths" + case StatePaths: + return "StatePaths" + } + return fmt.Sprintf("%s", pt) +} +func (ct componentType) String() string { + switch ct { + case CompTypeInvalid: + return "CompTypeInvalid" + case CompTypeIC: + return "CompTypeIC" + } + return fmt.Sprintf("%s", ct) +} + +var compTblMap = map[componentType][]string{ + CompTypeIC: {NODE_CFG_TBL, IC_NAME_PREFIX + "*"}, +} + +func init() { + XlateFuncBind("DbToYangPath_pfm_components_path_xfmr", DbToYangPath_pfm_components_path_xfmr) + XlateFuncBind("Subscribe_pfm_components_xfmr", Subscribe_pfm_components_xfmr) + XlateFuncBind("YangToDb_pfm_components_xfmr", YangToDb_pfm_components_xfmr) + XlateFuncBind("DbToYang_pfm_components_xfmr", DbToYang_pfm_components_xfmr) +} + +var compTypeCache sync.Map + +func validICName(name *string) bool { + if name == nil || *name == "" { + return false + } + // Expect node name of form integrated-circuitX, where X is an integer + if !strings.HasPrefix(*name, IC_NAME_PREFIX) { + return false + } + + sp := strings.SplitAfter(*name, IC_NAME_PREFIX) + if len(sp) < 2 { + return false + } + + if _, err := strconv.Atoi(sp[1]); err != nil { + return false + } + return true +} + +func getCompTypeByName(compName string) (componentType, error) { + switch { + case validICName(&compName): + return CompTypeIC, nil + default: + return CompTypeInvalid, fmt.Errorf("component name %s did not match with supported types.", compName) + } +} +func getCompType(name string, d *db.DB) componentType { + if val, ok := compTypeCache.Load(name); ok { + return val.(componentType) + } + compType, err := getCompTypeByName(name) + if err == nil { + compTypeCache.Store(name, compType) + return compType + } + return CompTypeInvalid +} + +/* Helper for the main subscribe transformer handling the TRANSLATE_EXISTS case. */ +func translateExists(inParams XfmrSubscInParams, key string) (XfmrSubscOutParams, error) { + var result XfmrSubscOutParams + dbNum := db.StateDB + d := inParams.dbs[dbNum] + if d == nil { + dbNum := db.ConfigDB + d = inParams.dbs[dbNum] + } + if d == nil { + return result, fmt.Errorf("translateExists: No usable DB client in inParams (checked %v and %v)", db.StateDB.Name(), db.ConfigDB.Name()) + } + compType := getCompType(key, d) + if compType == CompTypeInvalid { + return result, nil + } + tblInfo, ok := compTblMap[compType] + if !ok { + return result, errors.New("table not found.") + } + tblName := tblInfo[0] + tblKey := key + switch compType { + case CompTypeIC: + dbNum = db.ConfigDB + } + result.dbDataMap = RedisDbSubscribeMap{dbNum: {tblName: {tblKey: {}}}} + log.V(3).Infof("+++ Subscribe_pfm_components_xfmr result: %v %v %v +++", dbNum, tblName, tblKey) + return result, nil +} + +var Subscribe_pfm_components_xfmr SubTreeXfmrSubscribe = func(inParams XfmrSubscInParams) (XfmrSubscOutParams, error) { + var result XfmrSubscOutParams + key := NewPathInfo(inParams.uri).Var("name") + + log.V(3).Infof("+++ Subscribe_pfm_components_xfmr uri (%v) key(%s) mode(%v) +++", inParams.uri, key, inParams.subscProc) + log.V(3).Infof("+++ Subscribe_pfm_components_xfmr requestUri (%v) +++", inParams.requestURI) + + pathInfo := NewPathInfo(inParams.requestURI) + targetUriPath, err := getYangPathFromUri(pathInfo.Path) + if err != nil { + return result, err + } + + if key == "" || strings.Contains(key, "_sensor") { + /* no need to verify dB data if we are requesting ALL + components or if request is for sensor */ + result.isVirtualTbl = true + return result, err + } + + if inParams.subscProc == TRANSLATE_EXISTS { + return translateExists(inParams, key) + } + if inParams.subscProc == TRANSLATE_SUBSCRIBE { + return translateSubscribe(inParams, key, targetUriPath) + } + + return result, err +} + +func getPfmRootObject(s *ygot.GoStruct) *ocbinds.OpenconfigPlatform_Components { + if s == nil { + return nil + } + deviceObj := (*s).(*ocbinds.Device) + return deviceObj.Components +} + +var YangToDb_pfm_components_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + pathInfo := NewPathInfo(inParams.uri) + key := pathInfo.Var("name") + if key == "" { + return nil, nil + } + log.V(3).Infof("YangToDb_pfm_components_xfmr: uri %s, name %s, requestURI %s, op %v", inParams.uri, key, inParams.requestUri, inParams.oper) + pfmObj := getPfmRootObject(inParams.ygRoot) + if pfmObj == nil || pfmObj.Component == nil || len(pfmObj.Component) < 1 { + return nil, tlerr.NotSupported("YangToDb_pfm_components_xfmr: Empty component.") + } + comp, ok := pfmObj.Component[key] + if !ok || comp == nil { + return nil, fmt.Errorf("YangToDb_pfm_components_xfmr: Invalid component name: %s", key) + } + inParams.key = key + var tblName string + cType := CompTypeInvalid + if validICName(&key) { + tblName = NODE_CFG_TBL + cType = CompTypeIC + } else { + return nil, fmt.Errorf("YangToDb_pfm_components_xfmr: Unable to identify component type for key: %s", key) + } + inParams.table = tblName + memMap := make(map[string]map[string]db.Value) + if inParams.oper == DELETE { + switch cType { + case CompTypeIC: + /* We only support deletion on the following path: + * /components/component/integrated-circuit/config/node-id */ + memMap[NODE_CFG_TBL] = map[string]db.Value{key: db.Value{Field: map[string]string{"node-id": ""}}} + + } + } else { + if comp.Config != nil { + fields := db.Value{Field: make(map[string]string)} + if comp.Config.Name != nil { + if inParams.key != *comp.Config.Name { + return nil, fmt.Errorf("Mismatch between component name key: (%s) and name to be configured: (%s)", inParams.key, *comp.Config.Name) + } + fields.Set("name", *comp.Config.Name) + } + memMap[tblName] = map[string]db.Value{key: fields} + } + if comp.IntegratedCircuit != nil && comp.IntegratedCircuit.Config != nil { + if cType != CompTypeIC { + return nil, fmt.Errorf("Component name \"%s\" not identified as an Integrated Circuit but contains an integrated-circuit subtree..", key) + } + dbVal := db.Value{Field: make(map[string]string)} + if _, ok := memMap[NODE_CFG_TBL]; !ok { + memMap[NODE_CFG_TBL] = make(map[string]db.Value) + } + if _, ok := memMap[NODE_CFG_TBL][key]; !ok { + memMap[NODE_CFG_TBL][key] = dbVal + } else { + dbVal = memMap[NODE_CFG_TBL][key] + } + } + } + log.V(3).Infof("YangToDb_pfm_components_xfmr: result %v", memMap) + return memMap, nil +} +var DbToYangPath_pfm_components_path_xfmr PathXfmrDbToYangFunc = func(inParams XfmrDbToYgPathParams) error { + rootPath := COMP + + log.V(3).Infof("DbToYangPath_pfm_path_xfmr: inParams: %#v", inParams) + + if len(inParams.tblKeyComp) == 0 { + return fmt.Errorf("Invalid tblKeyCom for pfm path xmfr:%v", inParams.tblKeyComp) + } + + tblKey := inParams.tblKeyComp[0] + inParams.ygPathKeys[rootPath+"/name"] = tblKey + log.V(3).Info("DbToYangPath_pfm_path_xfmr:- params.ygPathKeys: ", inParams.ygPathKeys) + + return nil +} + +/* Helper for the main subscribe transformer handling the TRANSLATE_SUBSCRIBE case. */ +func translateSubscribe(inParams XfmrSubscInParams, key, targetUriPath string) (XfmrSubscOutParams, error) { + var result XfmrSubscOutParams + result.dbDataMap = make(RedisDbSubscribeMap) + /* Handle TRANSLATE_SUBSCRIBE by expanding the wildcard yang key to a set of + * DB tables and keys. If the key is not a wildcard then identify the set + * of DB tables and keys which apply to it. */ + result.isVirtualTbl = false + result.needCache = true + result.onChange = OnchangeEnable + result.nOpts = ¬ificationOpts{mInterval: 0, pType: OnChange} + + /* Use the requested path to create a positive filter of component types to + * process. Note that a completely empty filter means no filtering is + * required. */ + compTypeFilter := []componentType{} + cType := getCompType(key, inParams.dbs[db.StateDB]) + if cType == CompTypeInvalid { + return result, nil + } + compTypeFilter = []componentType{cType} + for cType, tblNames := range compTblMap { + /* An empty filter means no filtering is required. */ + if len(compTypeFilter) > 0 { + /* Filtering is required, skip all component types not present in the + * filter. */ + filteredOut := true + for _, ct := range compTypeFilter { + if ct == cType { + filteredOut = false + break + } + } + if filteredOut { + continue + } + } + tblName := tblNames[0] + tblKey := tblNames[1] + tblDb := db.StateDB + tblKey = key + if result.dbDataMap[tblDb] == nil { + result.dbDataMap[tblDb] = make(map[string]map[string]map[string]string) + } + if result.dbDataMap[tblDb][tblName] == nil { + result.dbDataMap[tblDb][tblName] = make(map[string]map[string]string) + } + var subCompTblName, subCompTblKey string + var subCompDb db.DBNum + if cType == CompTypeIC { + /* The integrated-circuit subtree is backed by multiple DB tables but + * our compTblMap only captures one. The special handling here is to + * cover all tables. + * For wildcard expansion we use the NODE_CFG_TBL in ConfigDB but for + * sample cases on the specific counter paths we need the counter + * tables. */ + tblDb = db.ConfigDB + } + if result.dbDataMap[tblDb] == nil { + result.dbDataMap[tblDb] = make(map[string]map[string]map[string]string) + } + if result.dbDataMap[tblDb][tblName] == nil { + result.dbDataMap[tblDb][tblName] = make(map[string]map[string]string) + } + if subCompTblName != "" { + if result.dbDataMap[subCompDb] == nil { + result.dbDataMap[subCompDb] = make(map[string]map[string]map[string]string) + } + if result.dbDataMap[subCompDb][subCompTblName] == nil { + result.dbDataMap[subCompDb][subCompTblName] = make(map[string]map[string]string) + } + result.dbDataMap[subCompDb][subCompTblName][subCompTblKey] = map[string]string{} + } + if result.dbDataMap[tblDb][tblName][tblKey] == nil { + result.dbDataMap[tblDb][tblName][tblKey] = map[string]string{} + } + } + if log.V(3) { + for db, _ := range result.dbDataMap { + for tbl, _ := range result.dbDataMap[db] { + for k, v := range result.dbDataMap[db][tbl] { + log.Infof("+++ Subscribe_pfm_components_xfmr result: DB=%d, Table=%s, Key=%s, Flds=%v +++", db, tbl, k, v) + } + } + } + } + return result, nil +} + +/* Get a list of all table entries available */ +func getAllTableEntries(d *db.DB, tblName string, key string) ([]string, error) { + if tblName == "" || key == "" { + return nil, errors.New("getAllTableEntries: empty table name or key.") + } + keyList, err := d.GetKeysPattern(&(db.TableSpec{Name: tblName}), db.Key{Comp: []string{key}}) + if err != nil { + return nil, err + } + var ret []string + for _, v := range keyList { + if len(v.Comp) == 0 { + continue + } + ret = append(ret, strings.Join(v.Comp, d.Opts.KeySeparator)) + } + return ret, nil +} + +/* Filling in the config and state info for integrated circuits available in Redis DB */ +func fillICInfo(comp *ocbinds.OpenconfigPlatform_Components_Component, + name string, targetUriPath string, dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct) error { + /* Integrated-circuits have the following subtrees to populate: + * ...component/config + * ...component/state + * ...component/integrated-circuit + * ...component/integrated-circuit/config + * ...component/integrated-circuit/state + * Decide now which subtrees to fill based on the request. */ + var all, compSt bool + if targetUriPath == COMP { + all = true + } else if strings.HasPrefix(targetUriPath, COMP_ST) { + compSt = true + } + log.V(3).Infof("dbToYangIC: name %s targetUriPath %s", name, targetUriPath) + ygot.BuildEmptyTree(comp.IntegratedCircuit) + ygot.BuildEmptyTree(comp.IntegratedCircuit.State) + /* Handle component state paths: name, type, parent, fully-qualified-name */ + if all || compSt { + var stName, stType bool + switch targetUriPath { + case COMP_ST: + stName, stType = true, true + default: + /* Unsupported path or /components/component */ + } + if all || stName { + comp.State.Name = &name + } + if all || stType { + comp.State.Type, _ = comp.State.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_INTEGRATED_CIRCUIT) + } + } + return nil +} + +/* Helper to go from a component type to the type specific helper which reads + * the DB data and populates the ocbinds structs. + * createCompAndFuncCall - when fetching /components/component + * getSysComponents - when fetching the following paths: + * /components/component[name=] + * /components/component[name=]/config + * /components/component[name=]/state + */ +func compTypeToFuncCall(cType componentType, compName, subKey string, pfComp *ocbinds.OpenconfigPlatform_Components_Component, targetUriPath string, dbs [db.MaxDB]*db.DB, pType PathType, ygRoot *ygot.GoStruct) error { + log.V(3).Infof("compTypeToFuncCall with name=%s type=%v pType=%v", compName, cType, pType) + ygot.BuildEmptyTree(pfComp) + switch cType { + case CompTypeIC: + return fillICInfo(pfComp, compName, targetUriPath, dbs, ygRoot) + } + return errors.New("Invalid component type") +} + +func createCompAndFuncCall(pfCpts *ocbinds.OpenconfigPlatform_Components, targetUriPath string, compType componentType, inParams XfmrParams, tblName string, tblKey string) { + var compNames []string + var err error + dbs := inParams.dbs + d := dbs[db.StateDB] + cfgdb := dbs[db.ConfigDB] + switch compType { + case CompTypeIC: + compNames, err = getAllTableEntries(cfgdb, tblName, tblKey) + default: + compNames, err = getAllTableEntries(d, tblName, tblKey) + } + if err != nil { + log.V(3).Info(err) + } + + for _, compAndKey := range compNames { + compKeys := strings.Split(compAndKey, d.Opts.KeySeparator) + if len(compKeys) == 0 { + continue + } + comp := compKeys[0] + derivedCompType := getCompType(comp, d) + if derivedCompType != compType { + continue + } + pfComp := pfCpts.Component[comp] + if pfComp == nil { + pfComp, err = pfCpts.NewComponent(comp) + if err != nil { + log.V(3).Infof("Component creation failed with NewComponent for comp %v; err = %v", comp, err) + continue + } + ygot.BuildEmptyTree(pfComp) + } + + if err = compTypeToFuncCall(compType, comp, "", pfComp, targetUriPath, dbs, AllPaths, inParams.ygRoot); err != nil { + log.V(3).Info(err) + } + } +} + +/* Main workhorse of the DbToYang transformer. The get is either for the entire + * component list (/components/component) or for a specific component name; no + * wildcards are handled here. */ +func getSysComponents(pf_cpts *ocbinds.OpenconfigPlatform_Components, targetUriPath string, inParams XfmrParams, compName, subKey string) error { + + log.V(3).Infof("Preparing dB for system components") + + dbs := inParams.dbs + ygRoot := inParams.ygRoot + + var err error + d := dbs[db.StateDB] + log.V(3).Infof("getSysComponents: compName: %s targetUriPath: %s", compName, targetUriPath) + switch targetUriPath { + case COMP: + log.V(3).Infof("compName: %v", compName) + subCompName := "" /* Get all subcomponents */ + if compName == "" { + /* Handle all component types except for ports, they will be handled just below. */ + for cType, tbl := range compTblMap { + tblName := tbl[0] + createCompAndFuncCall(pf_cpts, targetUriPath, cType, inParams, tblName, tbl[1]) + } + } else { + compType := getCompType(compName, d) + if compType == CompTypeInvalid { + return nil + } + pf_comp, ok := pf_cpts.Component[compName] + if !ok || pf_comp == nil { + return fmt.Errorf("invalid input component name: %s", compName) + } + ygot.BuildEmptyTree(pf_comp) + if err = compTypeToFuncCall(compType, compName, subCompName, pf_comp, targetUriPath, dbs, AllPaths, ygRoot); err != nil { + log.V(3).Info(err) + } + } + case COMP_ST: + compType := getCompType(compName, d) + if compType == CompTypeInvalid { + return nil + } + pf_comp, ok := pf_cpts.Component[compName] + if !ok || pf_comp == nil { + return fmt.Errorf("invalid input component name for state path: %s", compName) + } + ygot.BuildEmptyTree(pf_comp) + ygot.BuildEmptyTree(pf_comp.State) + ygot.BuildEmptyTree(pf_comp.State.Temperature) + if err = compTypeToFuncCall(compType, compName, subKey, pf_comp, targetUriPath, dbs, StatePaths, ygRoot); err != nil { + log.V(3).Info(err) + } + default: + /* The following cases are handled above: + * /components/component + * /components/component[name=] + * /components/component[name=]/config + * /components/component[name=]/state + * /components/component[name=]/healthz + * /components/component[name=]/healthz/faults + * so the request must be for a specific component's leaf or subtree, + * e.g. /components/component[name=integrated_circuit]/integrated-circuit */ + // TODO - Can we de-dup this code with compTypeToFuncCall? No good way to set pathType... + compType := getCompType(compName, d) + if compType == CompTypeInvalid { + return nil + } + pf_comp, ok := pf_cpts.Component[compName] + if !ok || pf_comp == nil { + return fmt.Errorf("invalid input component name: %s", compName) + } + ygot.BuildEmptyTree(pf_comp) + switch compType { + case CompTypeIC: + return fillICInfo(pf_comp, compName, targetUriPath, inParams.dbs, inParams.ygRoot) + default: + return fmt.Errorf("Unhandled Component: %s", compName) + } + } + return err +} + +var DbToYang_pfm_components_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { + pathInfo := NewPathInfo(inParams.uri) + log.V(3).Infof("DbToYang_pfm_components_xfmr: %s, path: %s, vars: %v", + pathInfo.Template, pathInfo.Path, pathInfo.Vars) + + if !strings.Contains(inParams.requestUri, "/openconfig-platform:components") { + return errors.New("Component not supported") + } + log.V(3).Info("inParams.Uri:", inParams.requestUri) + targetUriPath, err := getYangPathFromUri(pathInfo.Path) + if err != nil { + return err + } + + /* Extract the component name (key), it may be empty ("") if the get is for + * the entire list/container (/components/component) */ + compName := pathInfo.Var("name") + /* Rails and subcomponents may have a second level key */ + subKey := pathInfo.Var("name#2") + return getSysComponents(getPfmRootObject(inParams.ygRoot), targetUriPath, inParams, compName, subKey) +} diff --git a/translib/transformer/xfmr_platform_test.go b/translib/transformer/xfmr_platform_test.go new file mode 100644 index 000000000..57e45ebaf --- /dev/null +++ b/translib/transformer/xfmr_platform_test.go @@ -0,0 +1,696 @@ +package transformer + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/openconfig/ygot/ygot" +) + +func TestCompRoot_ComponentTypeString(t *testing.T) { + cases := []struct { + in componentType + want string + }{ + {CompTypeInvalid, "CompTypeInvalid"}, + {CompTypeIC, "CompTypeIC"}, + } + for _, c := range cases { + got := c.in.String() + if got != c.want { + t.Errorf("componentType.String() == %q, want %q", got, c.want) + } + } +} +func TestCompRoot_ValidICName(t *testing.T) { + strPtr := func(s string) *string { return &s } + + tests := []struct { + name string + input *string + expected bool + }{ + { + name: "Fails HasPrefix check", + input: strPtr("invalid-prefix-123"), + expected: false, + }, + { + name: "Fails len(sp) < 2 check (Prefix matches exactly, but nothing follows)", + input: strPtr("integrated_circuit"), + expected: false, + }, + { + name: "Fails Atoi check (Suffix is not a valid integer)", + input: strPtr("integrated_circuitABC"), + expected: false, + }, + { + name: "Fails Atoi check (Suffix is not a valid integer)", + input: strPtr("integrated_circuitf"), + expected: false, + }, + { + name: "Passes all checks (Valid IC name)", + input: strPtr("integrated_circuit42"), + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validICName(tt.input) + if got != tt.expected { + t.Errorf("validICName() = %v, want %v for input %v", got, tt.expected, *tt.input) + } + }) + } +} + +func TestCompRoot_GetCompTypeByName(t *testing.T) { + inputName := "integrated_circuit45" + expectedType := CompTypeIC + + actualType, err := getCompTypeByName(inputName) + + if err != nil { + t.Fatalf("Expected no error, but got: %v", err) + } + + if actualType != expectedType { + t.Errorf("Expected component type %d, but got %d", expectedType, actualType) + } +} + +func TestCompRoot_GetCompType_ErrorCases(t *testing.T) { + testName := "integrated_circuit" + compTypeCache.Delete(testName) + + result := getCompType(testName, nil) + + if result != CompTypeInvalid { + t.Errorf("Expected CompTypeInvalid when an error occurs, but got %v", result) + } + + if _, cached := compTypeCache.Load(testName); cached { + t.Errorf("Expected error case NOT to be cached, but found an entry in compTypeCache") + } +} + +func TestCompRoot_GetPfmRootObject(t *testing.T) { + if r := getPfmRootObject(nil); r != nil { + t.Errorf("Calling getPfmRootObject with nil didn't return nil, %v", r) + } +} + +func TestCompRoot_GetPfmRootObject_Success(t *testing.T) { + expectedComponents := &ocbinds.OpenconfigPlatform_Components{} + + device := &ocbinds.Device{ + Components: expectedComponents, + } + + var gStruct ygot.GoStruct = device + inputPointer := &gStruct + + res := getPfmRootObject(inputPointer) + + if res != expectedComponents { + t.Errorf("Expected components pointer %v, got %v", expectedComponents, res) + } +} +func TestCompRoot_GetSysComponentsWithUnknownComponentType(t *testing.T) { + var inParams XfmrParams + key := "IDONTEXIST" + + dbNum := db.StateDB + d, err := db.NewDB(getDBOptions(dbNum)) + if err != nil { + t.Fatal("NewDB failed") + } + inParams.dbs[dbNum] = d + for _, targetUriPath := range []string{COMP, COMP_ST} { + if err := getSysComponents(nil, targetUriPath, inParams, key, ""); err != nil { + t.Fatal("getSysComponents returned an error with an unknown component type") + } + } +} + +func TestCompRoot_TranslateExists(t *testing.T) { + mockStateDBClient := &db.DB{} + + tests := []struct { + name string + inParams XfmrSubscInParams + key string + expectedOut XfmrSubscOutParams + expectedErr error + expectNilErr bool + }{ + { + name: "Error - Both StateDB and ConfigDB are missing", + inParams: XfmrSubscInParams{ + // Initialize an empty 10-element array of nil pointers + dbs: [10]*db.DB{}, + }, + key: "Ethernet0", + expectedOut: XfmrSubscOutParams{}, + expectedErr: fmt.Errorf("translateExists: No usable DB client in inParams (checked %v and %v)", db.StateDB.Name(), db.ConfigDB.Name()), + expectNilErr: false, + }, + { + name: "Success - Returns empty parameters on Invalid Component Type", + inParams: XfmrSubscInParams{ + dbs: [10]*db.DB{ + db.StateDB: mockStateDBClient, + }, + }, + key: "invalid-key", + expectedOut: XfmrSubscOutParams{}, + expectNilErr: true, + }, + { + name: "Success - Returns valid Component Type", + inParams: XfmrSubscInParams{ + dbs: [10]*db.DB{ + db.StateDB: mockStateDBClient, + }, + }, + key: "integrated_circuit", + expectedOut: XfmrSubscOutParams{}, + expectNilErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotOut, gotErr := translateExists(tt.inParams, tt.key) + + // Check Error States + if tt.expectNilErr { + if gotErr != nil { + t.Fatalf("translateExists() unexpected error: %v", gotErr) + } + } else { + if gotErr == nil || gotErr.Error() != tt.expectedErr.Error() { + t.Fatalf("translateExists() error = %v, expectedErr = %v", gotErr, tt.expectedErr) + } + } + + // Check Result Structs + if !reflect.DeepEqual(gotOut, tt.expectedOut) { + t.Errorf("translateExists() output =\n%v\nExpected =\n%v", gotOut, tt.expectedOut) + } + }) + } +} + +/* +func TestCompRoot_TranslateSubscribe(t *testing.T) { + mockStateDBClient := &db.DB{} + + tests := []struct { + name string + inParams XfmrSubscInParams + key string + expectedOut XfmrSubscOutParams + expectedErr error + expectNilErr bool + }{ + { + name: "Error - Both StateDB and ConfigDB are missing", + inParams: XfmrSubscInParams{ + // Initialize an empty 10-element array of nil pointers + dbs: [10]*db.DB{}, + }, + key: "Ethernet0", + expectedOut: XfmrSubscOutParams{}, + expectedErr: fmt.Errorf("translateSubscribe: No usable DB client in inParams (checked %v and %v)", db.StateDB.Name(), db.ConfigDB.Name()), + expectNilErr: true, + }, + { + name: "Success - Returns empty parameters on Invalid Component Type", + inParams: XfmrSubscInParams{ + dbs: [10]*db.DB{ + db.StateDB: mockStateDBClient, + }, + }, + key: "invalid-key", + expectedOut: XfmrSubscOutParams{}, + expectNilErr: false, + }, + { + name: "Success - Returns valid Component Type", + inParams: XfmrSubscInParams{ + dbs: [10]*db.DB{ + db.StateDB: mockStateDBClient, + }, + }, + key: "integrated_circuit", + expectedOut: XfmrSubscOutParams{}, + expectNilErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotOut, gotErr := translateSubscribe(tt.inParams, tt.key, tt.key) + + // Check Error States + if tt.expectNilErr { + if gotErr != nil { + t.Fatalf("translateSubscribe() unexpected error: %v", gotErr) + } + } else { + if gotErr == nil || gotErr.Error() != tt.expectedErr.Error() { + t.Fatalf("translateSubscribe() error = %v, expectedErr = %v", gotErr, tt.expectedErr) + } + } + + // Check Result Structs + if !reflect.DeepEqual(gotOut, tt.expectedOut) { + t.Errorf("translateExists() output =\n%v\nExpected =\n%v", gotOut, tt.expectedOut) + } + }) + } +} +*/ + +func TestCompRoot_Subscribe_pfm_components_xfmr(t *testing.T) { + mockStateDBClient := &db.DB{} + + oldCompTblMap := compTblMap + defer func() { compTblMap = oldCompTblMap }() + compTblMap = map[componentType][]string{ + CompTypeIC: {"IC_TABLE"}, + } + + const actualConfigDBNum = 4 + + tests := []struct { + name string + inParams XfmrSubscInParams + setupFunc func() // A callback to intercept or set up test-specific preconditions + expectedOut XfmrSubscOutParams + wantErr bool + }{ + { + name: "Early Exit - Key is empty (Requesting ALL components)", + inParams: XfmrSubscInParams{ + uri: "/openconfig-platform:components/component", + requestURI: "/openconfig-platform:components/component", + subscProc: TRANSLATE_EXISTS, + }, + expectedOut: XfmrSubscOutParams{ + isVirtualTbl: true, + }, + wantErr: false, + }, + { + name: "Early Exit - Key contains _sensor", + inParams: XfmrSubscInParams{ + uri: "/openconfig-platform:components/component[name=temp_sensor]", + requestURI: "/openconfig-platform:components/component[name=temp_sensor]", + subscProc: TRANSLATE_EXISTS, + }, + expectedOut: XfmrSubscOutParams{ + isVirtualTbl: true, + }, + wantErr: false, + }, + { + name: "Routing Path - TRANSLATE_SUBSCRIBE branch execution", + inParams: XfmrSubscInParams{ + uri: "/openconfig-platform:components/component[name='ic-key']", + requestURI: "/openconfig-platform:components/component[name='ic-key']/state/temperature", + subscProc: TRANSLATE_SUBSCRIBE, + dbs: [10]*db.DB{ + db.StateDB: mockStateDBClient, + }, + }, + expectedOut: XfmrSubscOutParams{ + dbDataMap: RedisDbSubscribeMap{}, + needCache: true, + onChange: 1, + isVirtualTbl: false, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setupFunc != nil { + tt.setupFunc() + } + + gotOut, gotErr := Subscribe_pfm_components_xfmr(tt.inParams) + + if gotErr != nil { + t.Fatalf("Subscribe_pfm_components_xfmr() unexpected error: %v", gotErr) + } + + if tt.name == "Routing Path - TRANSLATE_SUBSCRIBE branch execution" { + if gotOut.needCache != tt.expectedOut.needCache || gotOut.onChange != tt.expectedOut.onChange { + t.Errorf("Subscribe_pfm_components_xfmr() output fields mismatch.\nGot: needCache=%v, onChange=%v\nExpected: needCache=%v, onChange=%v", + gotOut.needCache, gotOut.onChange, tt.expectedOut.needCache, tt.expectedOut.onChange) + } + return + } + + if !reflect.DeepEqual(gotOut, tt.expectedOut) { + t.Errorf("Subscribe_pfm_components_xfmr() output =\n%+v\nExpected =\n%+v", gotOut, tt.expectedOut) + } + }) + } +} + +func TestCompRoot_DbToYangPath_pfm_components_path_xfmr(t *testing.T) { + tests := []struct { + name string + inParams XfmrDbToYgPathParams + wantErr bool + expectedErrMsg string + wantKeyVal string + }{ + { + name: "Error Case: Empty tblKeyComp slice returns error", + inParams: XfmrDbToYgPathParams{ + tblKeyComp: []string{}, + ygPathKeys: make(map[string]string), + }, + wantErr: true, + expectedErrMsg: "Invalid tblKeyCom for pfm path xmfr:[]", + }, + { + name: "Error Case: Nil tblKeyComp slice returns error", + inParams: XfmrDbToYgPathParams{ + tblKeyComp: nil, + ygPathKeys: make(map[string]string), + }, + wantErr: true, + expectedErrMsg: "Invalid tblKeyCom for pfm path xmfr:[]", + }, + { + name: "Success Case: Single key maps to rootPath + /name", + inParams: XfmrDbToYgPathParams{ + tblKeyComp: []string{"ic-component-1"}, + ygPathKeys: make(map[string]string), + }, + wantErr: false, + wantKeyVal: "ic-component-1", + }, + { + name: "Success Case: Multiple keys extracts only index 0", + inParams: XfmrDbToYgPathParams{ + tblKeyComp: []string{"ic-component-2", "extra-component"}, + ygPathKeys: make(map[string]string), + }, + wantErr: false, + wantKeyVal: "ic-component-2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := DbToYangPath_pfm_components_path_xfmr(tt.inParams) + + if (err != nil) != tt.wantErr { + t.Fatalf("DbToYangPath_pfm_components_path_xfmr() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantErr { + if err.Error() != tt.expectedErrMsg { + t.Errorf("Expected error: %q, got: %q", tt.expectedErrMsg, err.Error()) + } + return + } + + expectedPathKey := COMP + "/name" + actualValue, exists := tt.inParams.ygPathKeys[expectedPathKey] + + if !exists { + t.Errorf("Expected path key %q missing from ygPathKeys map", expectedPathKey) + } + + if actualValue != tt.wantKeyVal { + t.Errorf("ygPathKeys[%q] = %q, expected %q", expectedPathKey, actualValue, tt.wantKeyVal) + } + }) + } +} +func TestCompRoot_GetAllTableEntries(t *testing.T) { + tests := []struct { + name string + tblName string + key string + wantErr bool + wantLen int + mockBehavior func() ([]db.Key, error) + }{ + { + name: "Coverage: Empty table name exits early", + tblName: "", + key: "valid-key", + wantErr: true, + }, + { + name: "Coverage: Empty key exits early", + tblName: "MY_TABLE", + key: "", + wantErr: true, + }, + { + name: "Coverage: DB execution failure bubbles up", + tblName: "MY_TABLE", + key: "key1", + wantErr: true, + mockBehavior: func() ([]db.Key, error) { + return nil, errors.New("mock db failure") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + var testDB *db.DB + + results, err := getAllTableEntries(testDB, tt.tblName, tt.key) + + if (err != nil) != tt.wantErr { + t.Fatalf("getAllTableEntries() unexpected error state = %v, wantErr %v", err, tt.wantErr) + } + + if !tt.wantErr && len(results) != tt.wantLen { + t.Errorf("Expected return array length %d, got %d", tt.wantLen, len(results)) + } + }) + } +} + +func TestCompRoot_FillICInfo(t *testing.T) { + tests := []struct { + name string + targetUriPath string + compName string + }{ + { + name: "Coverage: Path matches COMP exactly", + targetUriPath: COMP, // ensure COMP is defined or use "/components/component" + compName: "ic-1", + }, + { + name: "Coverage: Path matches COMP_ST exactly", + targetUriPath: COMP_ST, // ensure COMP_ST is defined or use "/components/component/state" + compName: "ic-2", + }, + { + name: "Coverage: Path has COMP_ST prefix but hits switch default", + targetUriPath: "/components/component/state/unsupported-leaf", + compName: "ic-3", + }, + { + name: "Coverage: Path doesn't match either condition", + targetUriPath: "/components/component/config", + compName: "ic-4", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + comp := &ocbinds.OpenconfigPlatform_Components_Component{ + State: &ocbinds.OpenconfigPlatform_Components_Component_State{}, + IntegratedCircuit: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit{ + State: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit_State{}, + }, + } + + var dummyDbs [db.MaxDB]*db.DB + var dummyYgRoot *ygot.GoStruct + + err := fillICInfo(comp, tt.compName, tt.targetUriPath, dummyDbs, dummyYgRoot) + if err != nil { + t.Fatalf("fillICInfo returned an unexpected error: %v", err) + } + }) + } +} +func TestCompRoot_DbToYang_pfm_components_xfmr(t *testing.T) { + tests := []struct { + name string + inParams XfmrParams + wantErr bool + expectedErrMsg string + }{ + { + name: "Coverage: Unsupported request URI triggers early exit error", + inParams: XfmrParams{ + requestUri: "/openconfig-system:system/config", + uri: "/some-uri", + }, + wantErr: true, + expectedErrMsg: "Component not supported", + }, + { + name: "Coverage: Successful execution path calls downstream getSysComponents", + inParams: XfmrParams{ + requestUri: "/openconfig-platform:components/component[name=ic-chip-1]", + uri: "valid-path", + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := DbToYang_pfm_components_xfmr(tt.inParams) + + if (err != nil) != tt.wantErr { + t.Fatalf("DbToYang_pfm_components_xfmr() unexpected error state = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantErr && err.Error() != tt.expectedErrMsg { + t.Errorf("Expected error message: %q, got: %q", tt.expectedErrMsg, err.Error()) + } + }) + } +} + +// Mocking or instantiating required types for the test setup +type mockGoStruct struct { + ygot.GoStruct +} + +func TestCompRoot_CompTypeToFuncCall(t *testing.T) { + var mockDbs [db.MaxDB]*db.DB + var mockYgRoot ygot.GoStruct = &mockGoStruct{} + + tests := []struct { + name string + cType componentType + compName string + subKey string + pfComp *ocbinds.OpenconfigPlatform_Components_Component + targetUriPath string + pType PathType + wantErr bool + errMessage string + }{ + { + name: "Invalid Component Type returns error", + cType: CompTypeInvalid, // An invalid/unhandled type + compName: "test-comp", + subKey: "sub-key", + pfComp: &ocbinds.OpenconfigPlatform_Components_Component{}, + targetUriPath: "/root/path", + pType: AllPaths, + wantErr: true, + errMessage: "Invalid component type", + }, + { + name: "Valid CompTypeIC path", + cType: CompTypeIC, + compName: "ic-comp", + subKey: "sub-key", + pfComp: &ocbinds.OpenconfigPlatform_Components_Component{}, + targetUriPath: "/root/path/ic", + pType: AllPaths, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + err := compTypeToFuncCall( + tt.cType, + tt.compName, + tt.subKey, + tt.pfComp, + tt.targetUriPath, + mockDbs, + tt.pType, + &mockYgRoot, + ) + + if (err != nil) != tt.wantErr { + t.Fatalf("compTypeToFuncCall() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantErr && err != nil { + if err.Error() != tt.errMessage { + t.Errorf("compTypeToFuncCall() error message = %q, want %q", err.Error(), tt.errMessage) + } + } + + if tt.pfComp == nil { + t.Errorf("Expected pfComp to be initialized by ygot.BuildEmptyTree, but it was nil") + } + }) + } +} +func TestCompRoot_CreateCompAndFuncCall_Success(t *testing.T) { + + inParams := XfmrParams{ + requestUri: "/openconfig-system:system/config", + uri: "/some-uri", + } + + tests := []struct { + name string + compType componentType + tblName string + tblKey string + targetUriPath string + expectCompName string + }{ + { + name: "Successful component creation and execution for default type", + compType: CompTypeIC, // Use your actual enum value here + tblName: "NODE_CFG_TBL", + tblKey: "Ethernet0", + targetUriPath: "/components/component", + expectCompName: "Ethernet0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + createCompAndFuncCall(getPfmRootObject(inParams.ygRoot), tt.targetUriPath, tt.compType, inParams, tt.tblName, tt.tblKey) + }) + } +} + +func TestCompRoot_YangToDb_pfm_components_xfmr_Simple(t *testing.T) { + params := XfmrParams{ + uri: "/components/component[name=integrated_circuit65]", + oper: DELETE, + } + + YangToDb_pfm_components_xfmr(params) + +}