diff --git a/models/yang/annotations/openconfig-platform-annot.yang b/models/yang/annotations/openconfig-platform-annot.yang new file mode 100644 index 000000000..3f02ae686 --- /dev/null +++ b/models/yang/annotations/openconfig-platform-annot.yang @@ -0,0 +1,15 @@ +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"; + } + } +} diff --git a/translib/transformer/xfmr_platform.go b/translib/transformer/xfmr_platform.go new file mode 100644 index 000000000..62d48b50b --- /dev/null +++ b/translib/transformer/xfmr_platform.go @@ -0,0 +1,487 @@ +package transformer + +import ( + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" +) + +const ( + NODE_CFG_TBL = "NODE_CFG" + + IC_NAME_PREFIX = "integrated_circuit" + CHASSIS_PREFIX = "chassis" + + /** Upper-level URIs **/ + COMP = "/openconfig-platform:components/component" + COMP_ST = "/openconfig-platform:components/component/state" + + /** Supported oc-platform component state URIs **/ + COMP_STATE_EMPTY = "/openconfig-platform:components/component/state/empty" + COMP_STATE_FIRM_VER = "/openconfig-platform:components/component/state/firmware-version" + COMP_STATE_HW_VER = "/openconfig-platform:components/component/state/hardware-version" + COMP_STATE_INSTALL_POSITION = "/openconfig-platform:components/component/state/install-position" + COMP_STATE_INSTALL_COMPONENT = "/openconfig-platform:components/component/state/install-component" + COMP_STATE_MFG_DATE = "/openconfig-platform:components/component/state/mfg-date" + COMP_STATE_MFG_NAME = "/openconfig-platform:components/component/state/mfg-name" + COMP_STATE_NAME = "/openconfig-platform:components/component/state/name" + COMP_STATE_PART_NO = "/openconfig-platform:components/component/state/part-no" + COMP_STATE_SERIAL_NO = "/openconfig-platform:components/component/state/serial-no" + COMP_STATE_TYPE = "/openconfig-platform:components/component/state/type" + COMP_STATE_PARENT = "/openconfig-platform:components/component/state/parent" + COMP_STATE_TEMP_CTR = "/openconfig-platform:components/component/state/temperature" +) + +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("Subscribe_pfm_components_xfmr", Subscribe_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 name == "*" { + return CompTypeInvalid + } + 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 +} + +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 +} + +/* 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 { + 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 || len(tblInfo) == 0 { + return result, errors.New("table not found or is empty.") + } + tblName := tblInfo[0] + tblKey := key + result.dbDataMap = RedisDbSubscribeMap{dbNum: {tblName: {tblKey: {}}}} + log.V(3).Infof("+++ Subscribe_pfm_components_xfmr result: %v %v %v +++", dbNum, tblName, tblKey) + return result, 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 { + if len(tblNames) < 2 { + continue + } + /* 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) + } + 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, stParent bool + switch targetUriPath { + case COMP_ST: + stName, stType = true, true + case COMP_STATE_NAME: + stName = true + case COMP_STATE_TYPE: + stType = true + case COMP_STATE_PARENT: + stParent = 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) + } + if all || stParent { + parentChassis := CHASSIS_PREFIX + comp.State.Parent = &parentChassis + } + } + 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) + 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 + * 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") + /*subkey is second level key set to null which is not used for now*/ + subKey := "" + 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..86bf0f6a3 --- /dev/null +++ b/translib/transformer/xfmr_platform_test.go @@ -0,0 +1,546 @@ +//go:build testapp +// +build testapp + +package transformer + +import ( + "errors" + "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_Subscribe_pfm_components_xfmr(t *testing.T) { + + 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, + }, + } + + 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_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: "integrated_circuit45", + wantErr: true, + }, + { + name: "Coverage: Empty key exits early", + tblName: "MY_TABLE", + key: "", + wantErr: true, + }, + { + name: "Coverage: DB execution failure", + tblName: "MY_TABLE", + key: "key1", + wantErr: true, + mockBehavior: func() ([]db.Key, error) { + return nil, errors.New("mock db failure") + }, + }, + { + name: "Check valid case", + tblName: "NODE_CFG_TBL", + key: "integrated_circuit45", + wantErr: true, + }, + } + + 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_Subscribe_pfm_components_xfmr_TranslateExists(t *testing.T) { + inParams := XfmrSubscInParams{ + uri: "/components/component[name=integrated_circuit78]", + requestURI: "/components/component", + subscProc: TRANSLATE_EXISTS, + } + + _, err := Subscribe_pfm_components_xfmr(inParams) + if err == nil { + t.Errorf("Expectimg error since DB is not filled\n") + } +} +func TestCompRoot_Subscribe_pfm_components_xfmr_TranslateSubscribe(t *testing.T) { + inParams := XfmrSubscInParams{ + uri: "/components/component[name=integrated_circuit78]", + requestURI: "/components/component", + subscProc: TRANSLATE_SUBSCRIBE, + } + + _, err := Subscribe_pfm_components_xfmr(inParams) + if err != nil { + t.Errorf("Expectimg Success for Subscribe_pfm_components_xfmr\n") + } +} + +func TestCompRoot_TranslateExistsInvalidKey(t *testing.T) { + dbNum := db.StateDB + d, err := db.NewDB(getDBOptions(dbNum)) + if err != nil { + t.Fatalf("NewDB failed: %v", err) + } + defer d.DeleteDB() + + var inParams XfmrSubscInParams + inParams.dbs[dbNum] = d + key := "INVALIDKEY" + got, err := translateExists(inParams, key) + if err != nil { + t.Fatalf("translateExists(%q) returned unexpected error: %v", key, err) + } + if got.isVirtualTbl { + t.Errorf("translateExists(%q) returned isVirtualTbl = true, want false", key) + } +} +func TestCompRoot_TranslateExistsValidKey(t *testing.T) { + dbNum := db.StateDB + d, err := db.NewDB(getDBOptions(dbNum)) + if err != nil { + t.Fatalf("NewDB failed: %v", err) + } + defer d.DeleteDB() + + var inParams XfmrSubscInParams + inParams.dbs[dbNum] = d + key := "integrated_circuit32" + got, err := translateExists(inParams, key) + if err != nil { + t.Fatalf("translateExists(%q) returned unexpected error: %v", key, err) + } + if got.isVirtualTbl { + t.Errorf("translateExists(%q) returned isVirtualTbl = true, want false", key) + } +} +func TestCompRoot_TranslateSubscribeValidKey(t *testing.T) { + dbNum := db.StateDB + d, err := db.NewDB(getDBOptions(dbNum)) + if err != nil { + t.Fatalf("NewDB failed: %v", err) + } + defer d.DeleteDB() + + var inParams XfmrSubscInParams + inParams.dbs[dbNum] = d + key := "integrated_circuit32" + targetUriPath := COMP_ST + got, err := translateSubscribe(inParams, key, targetUriPath) + if err != nil { + t.Fatalf("translateSubscribe(%q) returned unexpected error: %v", key, err) + } + if got.isVirtualTbl { + t.Errorf("translateSubsribe(%q) returned isVirtualTbl = true, want false", key) + } +}