diff --git a/config/transformer/models_list b/config/transformer/models_list index 32f43e52e..041ce3652 100644 --- a/config/transformer/models_list +++ b/config/transformer/models_list @@ -12,7 +12,12 @@ openconfig-sampling-sflow-annot.yang openconfig-sampling-sflow.yang openconfig-system-annot.yang openconfig-vlan.yang +sonic-platform-node.yang +openconfig-p4rt.yang +openconfig-platform-annot.yang +openconfig-platform.yang gnsi-authz.yang gnsi-pathz.yang gnsi-certz.yang gnsi-credentialz.yang + diff --git a/models/yang/annotations/openconfig-platform-annot.yang b/models/yang/annotations/openconfig-platform-annot.yang new file mode 100644 index 000000000..6bdbb3bc0 --- /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/models/yang/openconfig-p4rt.yang b/models/yang/openconfig-p4rt.yang new file mode 100644 index 000000000..369ce967d --- /dev/null +++ b/models/yang/openconfig-p4rt.yang @@ -0,0 +1,107 @@ +module openconfig-p4rt { + yang-version "1"; + + prefix "oc-p4rt"; + + namespace "http://openconfig.net/yang/p4rt"; + + import openconfig-extensions { prefix oc-ext; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-platform { prefix oc-platform; } + + organization + "OpenConfig Working Group"; + + contact + "www.openconfig.net"; + + description + "This module defines a set of extensions that provide P4Runtime (P4RT) + specific extensions to the OpenConfig data models. Specifically, these + parameters for configuration and state provide extensions that control + the P4RT service, or allow it to be used alongside other OpenConfig + data models. + + The P4RT protocol specification is linkde from https://p4.org/specs/ + under the P4Runtime heading."; + + oc-ext:openconfig-version "0.1.0"; + + revision 2021-04-06 { + description + "Initial revision."; + reference "0.1.0"; + } + + grouping p4rt-interface-config { + description + "Interface-specific configuration that is applicable to devices that + are running the P4RT service."; + + leaf id { + type uint32; + description + "The numeric identifier used by the controller to address the interface. + This ID is assigned by an external-to-the-device entity (e.g., an SDN + management system) to establish an externally deterministic numeric + reference for the interface. The programming entity must ensure that + the ID is unique within the required context. + + Note that this identifier is used only when a numeric reference to the + interface is required, it does not replace the unique name assigned to + the interface."; + } + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:config" { + description + "Add interface-specific intended configuration for P4RT."; + + uses p4rt-interface-config; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:state" { + description + "Add interface-specific applied configuration for P4RT."; + + uses p4rt-interface-config; + } + + grouping p4rt-ic-config { + description + "Integrated-circuit specific configuration that is applicable to devices + that are running the P4RT service."; + + leaf node-id { + type uint64; + description + "The numeric ID used by the controller to address the integrated circuit, + which may be referred to as a 'device', 'node' or 'target' by the P4RT + specification. + + Each switching ASIC (i.e., node) is addressed by the external entity + based on its numeric identifier. + + The node ID is specified in addition to the string identifier assigned to + the integrated circuit within the /components/component list."; + } + } + + augment "/oc-platform:components/oc-platform:component/" + + "oc-platform:integrated-circuit/oc-platform:config" { + description + "Add integrated circuit specific intended configuration that is required + for P4RT."; + + uses p4rt-ic-config; + } + + augment "/oc-platform:components/oc-platform:component/" + + "oc-platform:integrated-circuit/oc-platform:state" { + description + "Add integrated circuit specific operational state that is required + for P4RT."; + + uses p4rt-ic-config; + } +} diff --git a/translib/transformer/xfmr_platform.go b/translib/transformer/xfmr_platform.go new file mode 100644 index 000000000..ff53e982b --- /dev/null +++ b/translib/transformer/xfmr_platform.go @@ -0,0 +1,714 @@ +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" + CHASSIS_PREFIX = "chassis" + + /** Upper-level URIs **/ + COMP = "/openconfig-platform:components/component" + COMP_ST = "/openconfig-platform:components/component/state" + COMP_CFG = "/openconfig-platform:components/component/config" + + /** 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" + + /** Supported Integrated Circuit URIs **/ + COMP_IC = "/openconfig-platform:components/component/integrated-circuit" + COMP_IC_CFG = "/openconfig-platform:components/component/integrated-circuit/config" + COMP_IC_ST = "/openconfig-platform:components/component/integrated-circuit/state" +) + +type componentType int64 + +const ( + CompTypeInvalid componentType = iota + CompTypeIC +) + +/*ICInfo structure read from State DB*/ +type ICInfo struct { + NodeID string +} + +type PathType int + +const ( + /* Represents all paths under /components/component */ + AllPaths PathType = iota + /* Represents all paths under /components/component/config */ + ConfigPaths + /* Represents all paths under /components/component/state */ + StatePaths +) + +func (pt PathType) String() string { + switch pt { + case AllPaths: + return "AllPaths" + case StatePaths: + return "StatePaths" + } + return strconv.Itoa(int(pt)) +} + +func (ct componentType) String() string { + switch ct { + case CompTypeInvalid: + return "CompTypeInvalid" + case CompTypeIC: + return "CompTypeIC" + } + return strconv.Itoa(int(ct)) +} + +var compTblMap = map[componentType][]string{ + CompTypeIC: {NODE_CFG_TBL, "*"}, +} + +func init() { + XlateFuncBind("DbToYangPath_pfm_components_path_xfmr", DbToYangPath_pfm_components_path_xfmr) + XlateFuncBind("Subscribe_pfm_components_xfmr", Subscribe_pfm_components_xfmr) + XlateFuncBind("DbToYang_pfm_components_xfmr", DbToYang_pfm_components_xfmr) + XlateFuncBind("YangToDb_pfm_components_xfmr", YangToDb_pfm_components_xfmr) +} + +var compTypeCache sync.Map + +func validICName(name string) bool { + suffix := strings.TrimPrefix(name, IC_NAME_PREFIX) + if suffix == name { + return false + } + _, err := strconv.Atoi(suffix) + return err == nil +} + +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 keyInDbTable(tableName, key string, d *db.DB) bool { + if d == nil { + return false + } + log.V(3).Infof("keyInDbTable: tableName=%s, key=%s, db=%s", tableName, key, d.Name()) + keys, err := d.GetKeysPattern(&db.TableSpec{Name: tableName}, db.Key{Comp: []string{key}}) + if err != nil { + return false + } + return len(keys) > 0 +} + +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 + } + if d != nil { + if keyInDbTable(NODE_CFG_TBL, name, d) { + compTypeCache.Store(name, CompTypeIC) + return CompTypeIC + } + } + return CompTypeInvalid +} + +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 tblKeyComp for pfm path xmfr:%v", inParams.tblKeyComp) + } + + tblKey := inParams.tblKeyComp[0] + switch inParams.tblName { + default: + inParams.ygPathKeys[rootPath+"/name"] = tblKey + } + + log.V(3).Info("DbToYangPath_pfm_path_xfmr:- params.ygPathKeys: ", inParams.ygPathKeys) + return 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 +} + +/* Given a URI for a subscription, return a list of component types which apply + * to it. For example a URI of "/components/component/port" would return + * [CompTypePort] while a URI of "/components/component/state/software-version" + * would return a list of all component types which report software version. */ +func compTypesForSubscriptionUri(uri string) []componentType { + cTypes := []componentType{} + if strings.HasPrefix(uri, "/openconfig-platform:components/component/integrated-circuit") { + cTypes = []componentType{CompTypeIC} + } + return cTypes +} + +/* Helper for the main subscribe transformer handling the TRANSLATE_EXISTS case. */ +func translateExists(inParams XfmrSubscInParams, key string) (XfmrSubscOutParams, error) { + var result XfmrSubscOutParams + if key == "*" { + result.isVirtualTbl = true + return result, nil + } + 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 +} + +/* 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{} + if key == "*" { + compTypeFilter = compTypesForSubscriptionUri(targetUriPath) + } else { + 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 + if key != "*" { + /* Generally the DB key will be the component name (yang key), for + * the cases where it is not (e.g. Port components) there will be + * special casing just below to handle it. */ + tblKey = key + } + + 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) + } + + /* Add the DB table and key to the result. */ + 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 +} + +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 && comp.IntegratedCircuit.Config.NodeId != 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] + } + nodeID := *comp.IntegratedCircuit.Config.NodeId + dbVal.Set("node-id", strconv.FormatUint(nodeID, 10)) + } + } + + log.V(3).Infof("YangToDb_pfm_components_xfmr: result %v", memMap) + return memMap, 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 +} + +func getICInfoFromDb(name string, d *db.DB, tblName string) ICInfo { + var nodeInfo ICInfo + + // Only proceed if the table is the one we are interested in + if d == nil || tblName != NODE_CFG_TBL { + return nodeInfo + } + + nodeEntry, err := d.GetEntry(&db.TableSpec{Name: tblName}, db.Key{Comp: []string{name}}) + if err != nil { + log.V(3).Infof("Cant get entry: %s; Error: %v", name, err) + return nodeInfo + } + + nodeInfo.NodeID = nodeEntry.Get("node-id") + + return nodeInfo +} + +/* 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, icCfg, icSt bool + if targetUriPath == COMP { + all = true + } else if strings.HasPrefix(targetUriPath, COMP_ST) { + compSt = true + } else if strings.HasPrefix(targetUriPath, COMP_IC_CFG) { + icCfg = true + } else if strings.HasPrefix(targetUriPath, COMP_IC_ST) { + icSt = true + } + log.V(3).Infof("dbToYangIC: name %s targetUriPath %s", name, targetUriPath) + + // Build the integrated-circuit container and sub-containers when explicitly requested or during a full get + if all || icCfg || icSt { + ygot.BuildEmptyTree(comp.IntegratedCircuit) + if all || icCfg { + ygot.BuildEmptyTree(comp.IntegratedCircuit.Config) + } + if all || icSt { + ygot.BuildEmptyTree(comp.IntegratedCircuit.State) + } + } + cfgDb := dbs[db.ConfigDB] + nodeCfg := getICInfoFromDb(name, cfgDb, NODE_CFG_TBL) + + /* 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 + } + } + + /* Handle component integrated-circuit config and state paths */ + if nodeCfg.NodeID != "" { + nodeID, err := strconv.ParseUint(nodeCfg.NodeID, 10, 64) + if err != nil { + log.Warningf("string conversion failed for IC %s node-id \"%s\": %v", name, nodeCfg.NodeID, err) + } else { + /* Handle component integrated-circuit config paths */ + if all || icCfg { + comp.IntegratedCircuit.Config.NodeId = &nodeID + } + + /* Handle component integrated-circuit state paths */ + if all || icSt { + comp.IntegratedCircuit.State.NodeId = &nodeID + } + } + } + + 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) + 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) + /* The third arument in compTypeToFuncCall() is passed asempty as subcomp is not supported */ + if err = compTypeToFuncCall(compType, compName, "", 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) + } + case COMP_CFG: + compType := getCompType(compName, d) + if compType == CompTypeInvalid { + return nil + } + if compType == CompTypeIC { + pf_comp, ok := pf_cpts.Component[compName] + if !ok || pf_comp == nil { + return fmt.Errorf("Invalid component name: %s", compName) + } + ygot.BuildEmptyTree(pf_comp) + if err = compTypeToFuncCall(compType, compName, subKey, pf_comp, targetUriPath, dbs, ConfigPaths, ygRoot); err != nil { + log.V(3).Info(err) + } + break + } + 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..1d2ac93e5 --- /dev/null +++ b/translib/transformer/xfmr_platform_test.go @@ -0,0 +1,768 @@ +//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) { + tests := []struct { + name string + input string + expected bool + }{ + { + name: "Empty string", + input: "", + expected: false, + }, + { + name: "Fails HasPrefix check", + input: "invalid-prefix-123", + expected: false, + }, + { + name: "Prefix matches exactly, but nothing follows", + input: "integrated_circuit", + expected: false, + }, + { + name: "Suffix is not a valid integer (letters)", + input: "integrated_circuitABC", + expected: false, + }, + { + name: "Suffix is not a valid integer (single letter)", + input: "integrated_circuitf", + expected: false, + }, + { + name: "Valid IC name", + input: "integrated_circuit42", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validICName(tt.input) // Pass tt.input directly as a string + if got != tt.expected { + t.Errorf("validICName() = %v, want %v for input %q", got, tt.expected, tt.input) + } + }) + } +} + +func TestIC_FillICInfo_Coverage(t *testing.T) { + comp := &ocbinds.OpenconfigPlatform_Components_Component{ + IntegratedCircuit: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit{ + Config: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit_Config{}, + State: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit_State{}, + }, + } + var dbs [db.MaxDB]*db.DB + _ = fillICInfo(comp, "integrated_circuit1", COMP_IC_CFG, dbs, nil) + _ = fillICInfo(comp, "integrated_circuit1", COMP_IC_ST, dbs, nil) +} + +func TestIC_FillICInfoErrorPath(t *testing.T) { + comp := &ocbinds.OpenconfigPlatform_Components_Component{ + IntegratedCircuit: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit{ + Config: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit_Config{}, + }, + } + + var dummyDbs [db.MaxDB]*db.DB + + err := fillICInfo(comp, "integrated_circuit1", COMP_IC_CFG, dummyDbs, nil) + if err != nil { + t.Errorf("fillICInfo returned unexpected error: %v", err) + } +} + +func TestIC_YangToDbInvalidComponentType(t *testing.T) { + device := &ocbinds.Device{} + ygot.BuildEmptyTree(device) + device.Components.NewComponent("unknown_comp_type") + + var root ygot.GoStruct = device + params := XfmrParams{ + ygRoot: &root, + uri: "/openconfig-platform:components/component[name=unknown_comp_type]", + } + + _, err := YangToDb_pfm_components_xfmr(params) + if err == nil { + t.Error("Expected error for unidentified component type, got nil") + } +} + +func TestIC_YangToDb__MemMapCoverage(t *testing.T) { + device := &ocbinds.Device{} + ygot.BuildEmptyTree(device) + + // 'integrated_circuit42' passes validICName() + validKey := "integrated_circuit42" + + comp, err := device.Components.NewComponent(validKey) + if err != nil { + t.Fatalf("Failed to create component: %v", err) + } + + comp.Config = &ocbinds.OpenconfigPlatform_Components_Component_Config{ + Name: ygot.String(validKey), + } + comp.IntegratedCircuit = &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit{ + Config: &ocbinds.OpenconfigPlatform_Components_Component_IntegratedCircuit_Config{ + NodeId: ygot.Uint64(100), + }, + } + + var root ygot.GoStruct = device + params := XfmrParams{ + uri: "/components/component[name=" + validKey + "]", + requestUri: "/components/component", + ygRoot: &root, + oper: UPDATE, // Triggers the 'else' block (not DELETE) + key: validKey, + } + + memMap, err := YangToDb_pfm_components_xfmr(params) + + if err != nil { + t.Fatalf("Expected success, but got error: %v", err) + } + if memMap == nil { + t.Fatal("Expected memMap to be populated, but it was nil") + } + if _, ok := memMap[NODE_CFG_TBL]; !ok { + t.Errorf("Expected NODE_CFG_TBL in result, but it was missing") + } +} + +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 TestIC_GetSysComponentsFullCoverage(t *testing.T) { + device := &ocbinds.Device{} + ygot.BuildEmptyTree(device) + inParams := XfmrParams{dbs: [db.MaxDB]*db.DB{}} + + t.Run("CompLoop", func(t *testing.T) { + getSysComponents(device.Components, COMP, inParams, "", "") + }) + + t.Run("CompSpecific", func(t *testing.T) { + device.Components.NewComponent("integrated_circuit1") + getSysComponents(device.Components, COMP, inParams, "integrated_circuit1", "") + }) + + t.Run("CompState", func(t *testing.T) { + device.Components.NewComponent("integrated_circuit1") + getSysComponents(device.Components, COMP_ST, inParams, "integrated_circuit1", "") + }) + + t.Run("CompCfg", func(t *testing.T) { + getSysComponents(device.Components, COMP_CFG, inParams, "integrated_circuit1", "") + }) + + t.Run("DefaultFillIC", func(t *testing.T) { + getSysComponents(device.Components, "/some/other/path", inParams, "integrated_circuit1", "") + }) +} + +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_CreateCompAndFuncCall_Branches(t *testing.T) { + var mockDbs [db.MaxDB]*db.DB + + inParams := XfmrParams{ + dbs: mockDbs, + } + + pfCpts := &ocbinds.OpenconfigPlatform_Components{} + ygot.BuildEmptyTree(pfCpts) + + tests := []struct { + name string + compType componentType + tblName string + }{ + { + name: "Branch: Loop continues on mismatching type", + compType: CompTypeIC, + tblName: "TABLE_WITH_WRONG_TYPE", + }, + { + name: "Branch: Component creation (pfComp == nil)", + compType: CompTypeIC, + tblName: "TABLE_WITH_NEW_COMP", + }, + { + name: "Branch: Error logging when table fetch fails", + compType: CompTypeIC, + tblName: "", // Force an error + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createCompAndFuncCall(pfCpts, "/test", tt.compType, inParams, tt.tblName, "key") + }) + } +} + +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("Expecting 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("Expecting 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("translateSubscribe(%q) returned isVirtualTbl = true, want false", key) + } +} + +func TestCompRoot_PathType_String(t *testing.T) { + if AllPaths.String() != "AllPaths" { + t.Errorf("Expected AllPaths, got %s", AllPaths.String()) + } + invalidPath := PathType(99) + if invalidPath.String() != "99" { + t.Errorf("Expected 99, got %s", invalidPath.String()) + } +} + +func TestCompRoot_CompTypesForSubscriptionUri(t *testing.T) { + uri := "/openconfig-platform:components/component/integrated-circuit/config" + res := compTypesForSubscriptionUri(uri) + if len(res) == 0 { + t.Error("Expected CompTypeIC, got empty slice") + } + + res2 := compTypesForSubscriptionUri("/invalid/path") + if len(res2) != 0 { + t.Errorf("Expected empty slice, got %v", res2) + } +} + +func TestCompRoot_GetICInfoFromDb(t *testing.T) { + info := getICInfoFromDb("test", nil, "WRONG_TABLE") + if info.NodeID != "" { + t.Error("Expected empty NodeID for wrong table") + } + + info2 := getICInfoFromDb("test", nil, NODE_CFG_TBL) + if info2.NodeID != "" { + t.Error("Expected empty NodeID for failed DB lookup") + } +} + +func TestCompRoot_GetICInfoFromDbSuccessPath(t *testing.T) { + dbNum := db.StateDB + d, err := db.NewDB(getDBOptions(dbNum)) + if err != nil { + t.Fatalf("NewDB failed: %v", err) + } + defer d.DeleteDB() + + key := "integrated_circuit1" + tableSpec := &db.TableSpec{Name: NODE_CFG_TBL} + dbKey := db.Key{Comp: []string{key}} + val := db.Value{Field: map[string]string{"node-id": "123"}} + + err = d.SetEntry(tableSpec, dbKey, val) + if err != nil { + t.Fatalf("Failed to seed DB: %v", err) + } + + nodeInfo := getICInfoFromDb(key, d, NODE_CFG_TBL) + + if nodeInfo.NodeID != "123" { + t.Errorf("Expected NodeID '123', got '%s'", nodeInfo.NodeID) + } +}