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/models/yang/openconfig-platform-boot-loader.yang b/models/yang/openconfig-platform-boot-loader.yang new file mode 100644 index 000000000..81d92eb63 --- /dev/null +++ b/models/yang/openconfig-platform-boot-loader.yang @@ -0,0 +1,120 @@ +module openconfig-platform-boot-loader { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/platform/boot-loader"; + + prefix "oc-boot-loader"; + + import openconfig-platform { prefix oc-platform; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines data related to BOOT_LOADER components in the + OpenConfig platform model."; + + oc-ext:openconfig-version "0.2.0"; + + revision "2026-02-27" { + description + "Added uboot, linuxboot, and systemd-boot types"; + reference "0.2.0"; + } + + revision "2025-07-10" { + description + "Initial revision"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + identity BOOT_LOADER_BASE { + description + "Base identity for boot-loader types"; + } + + identity BOOT_LOADER_GRUB { + base BOOT_LOADER_BASE; + description + "GRUB is a boot-loader and boot manager for Linux and other Unix-based + OSes."; + reference "https://www.gnu.org/software/grub/"; + } + + identity BOOT_LOADER_ONIE { + base BOOT_LOADER_BASE; + description + "Open Network Install Environment (ONIE) is the combination of a boot + loader and a small operating system for network switches. + + https://opencomputeproject.github.io/onie/overview/"; + } + + identity BOOT_LOADER_UBOOT { + base BOOT_LOADER_BASE; + description + "Das U-Boot, or the Universal Boot Loader."; + reference "https://u-boot.org/about/"; + } + + identity BOOT_LOADER_LINUXBOOT { + base BOOT_LOADER_BASE; + description + "LinuxBoot is the concept of using a Linux kernel and initramfs as a + bootloader."; + reference "https://www.linuxboot.org/"; + } + + identity BOOT_LOADER_SYSTEMD_BOOT { + base BOOT_LOADER_BASE; + description + "systemd-boot, previously called gummiboot (German for 'rubber dinghy') + and sometimes referred to as sd-boot, is an easy-to-configure UEFI boot + manager. It provides a textual menu to select the boot entry and an editor + for the kernel command line."; + reference "https://systemd.io/"; + } + + // typedef statements + + // grouping statements + + grouping boot-loader-state { + description + "Operational state data for boot-loader components"; + + leaf type { + type identityref { + base BOOT_LOADER_BASE; + } + description + "Specifies the type of boot-loader"; + } + } + + // data definition statements + + // augment statements + + augment "/oc-platform:components/oc-platform:component/" + + "oc-platform:boot-loader/oc-platform:state" { + description + "Adding boot-loader data to component model"; + + uses boot-loader-state; + } + +} diff --git a/models/yang/openconfig-platform.yang b/models/yang/openconfig-platform.yang index d4ba13862..984aab13a 100644 --- a/models/yang/openconfig-platform.yang +++ b/models/yang/openconfig-platform.yang @@ -1283,6 +1283,21 @@ revision "2024-05-29" { "Operational state data for controller card components"; } } + container boot-loader { + description + "Data for boot-loader components"; + + container config { + description + "Configuration data for boot-loader components"; + } + + container state { + config false; + description + "Operational state data for boot-loader components"; + } + } } grouping platform-component-top { diff --git a/translib/transformer/xfmr_platform.go b/translib/transformer/xfmr_platform.go new file mode 100644 index 000000000..a73259e07 --- /dev/null +++ b/translib/transformer/xfmr_platform.go @@ -0,0 +1,804 @@ +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" + SW_COMP_TBL = "SW_COMP_INFO" + BOOTL_TYPE = "BOOT_LOADER" + OS_TYPE = "OPERATING_SYSTEM" + NW_STACK_TYPE = "SOFTWARE_MODULE" + BIOS_TYPE = "BIOS" + + IC_NAME_PREFIX = "integrated_circuit" + CHASSIS_PREFIX = "chassis" + NW_STACK_PREFIX = "network_stack" + OS_PREFIX = "os" + BOOTL_PREFIX = "boot_loader" + + /** 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_OPER_STATUS = "/openconfig-platform:components/component/state/oper-status" + 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" + COMP_STATE_SW_VER = "/openconfig-platform:components/component/state/software-version" + + /** Supported Software Module URIs **/ + COMP_SW_MOD = "/openconfig-platform:components/component/software-module" + COMP_SW_MOD_ST = "/openconfig-platform:components/component/software-module/state" + SW_MODULE_STATE_MODULE_TYPE = "/openconfig-platform:components/component/software-module/state/openconfig-platform-software:module-type" + + SW_BOOT_LOADER_STATE_TYPE = "/openconfig-platform:components/component/boot-loader/state/openconfig-platform-boot-loader:type" +) + +type componentType int64 + +const ( + CompTypeInvalid componentType = iota + CompTypeIC + CompTypeNWStack + CompTypeOS + CompTypeBootLoader + CompTypeBios +) + +/*SWCompInfo structure read from State DB*/ +type SWCompInfo struct { + Name string + SoftwareVersion string + Parent string + OperStatus string + Type string + BootLoaderType string +} + +var dbToYangBootLoaderTypeMap = map[string]ocbinds.E_OpenconfigPlatformBootLoader_BOOT_LOADER_BASE{ + "GRUB": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_GRUB, + "ONIE": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_ONIE, + //"SONIE": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_SONIE, + //"NETLO": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_NETLO, + "UBOOT": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_UBOOT, + "SYSTEMD_BOOT": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_SYSTEMD_BOOT, + "LINUXBOOT": ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_LINUXBOOT, +} + +func operStatusFromString(status string) (ocbinds.E_OpenconfigPlatformTypes_COMPONENT_OPER_STATUS, error) { + switch strings.ToLower(status) { + case "active": + return ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, nil + case "inactive": + return ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_INACTIVE, nil + case "disabled": + return ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_DISABLED, nil + default: + return ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_DISABLED, fmt.Errorf("unknown oper-status: %s", status) + } +} + +type PathType int + +const ( + /* Represents all paths under /components/component */ + AllPaths PathType = iota + /* Represents all paths under /components/component/state */ + StatePaths + /* Represents all paths under a component subtree, e.g. + * /components/component/port or /components/component/fan */ + AllCompPaths + /* Represents a path to a specific leaf */ + SingularPath +) + +func (pt PathType) String() string { + switch pt { + case AllPaths: + return "AllPaths" + case AllCompPaths: + return "AllComponentPaths" + case StatePaths: + return "StatePaths" + case SingularPath: + return "SingularPath" + } + return fmt.Sprintf("%s", pt) +} + +func (ct componentType) String() string { + switch ct { + case CompTypeInvalid: + return "CompTypeInvalid" + case CompTypeIC: + return "CompTypeIC" + case CompTypeNWStack: + return "CompTypeNWStack" + case CompTypeOS: + return "CompTypeOS" + case CompTypeBootLoader: + return "CompTypeBootLoader" + case CompTypeBios: + return "CompTypeBios" + } + return fmt.Sprintf("%s", ct) +} + +var compTblMap = map[componentType][]string{ + CompTypeIC: {NODE_CFG_TBL, "*"}, + CompTypeNWStack: {SW_COMP_TBL, "*"}, + CompTypeOS: {SW_COMP_TBL, "*"}, + CompTypeBootLoader: {SW_COMP_TBL, "*"}, + CompTypeBios: {SW_COMP_TBL, "*"}, +} + +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 + case validSWCompName(&compName, NW_STACK_PREFIX): + return CompTypeNWStack, nil + case validSWCompName(&compName, OS_PREFIX): + return CompTypeOS, nil + case strings.HasPrefix(compName, BOOTL_PREFIX): + return CompTypeBootLoader, nil + + default: + return CompTypeInvalid, fmt.Errorf("component name %s did not match with supported types.", compName) + } +} +func getCompType(name string, stdb, cfgdb *db.DB) componentType { + if name == "*" { + return CompTypeInvalid + } + if val, ok := compTypeCache.Load(name); ok { + return val.(componentType) + } + if stdb != nil { + if swcEntry, err := stdb.GetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{name}}); err == nil { + switch swcEntry.Get("type") { + case BOOTL_TYPE: + compTypeCache.Store(name, CompTypeBootLoader) + return CompTypeBootLoader + case BIOS_TYPE: + compTypeCache.Store(name, CompTypeBios) + return CompTypeBios + case NW_STACK_TYPE: + // Need to check if actuall NW_STACK_TYPE or older boot loader + if swcEntry.Get("module-type") == BOOTL_TYPE { + compTypeCache.Store(name, CompTypeBootLoader) + return CompTypeBootLoader + } + compTypeCache.Store(name, CompTypeNWStack) + return CompTypeNWStack + case OS_TYPE: + compTypeCache.Store(name, CompTypeOS) + return CompTypeOS + } + } + } + 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 +} + +/* 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/software-module") { + cTypes = []componentType{CompTypeNWStack, CompTypeOS} + } else if strings.HasPrefix(uri, "/openconfig-platform:components/component/state/oper-status") { + cTypes = []componentType{CompTypeNWStack, CompTypeOS} + } else if strings.HasPrefix(uri, "/openconfig-platform:components/component/google-pins-platform:boot-loader") { + cTypes = []componentType{CompTypeBootLoader} + } else if strings.HasPrefix(uri, "/openconfig-platform:components/component/google-pins-platform:bios") { + cTypes = []componentType{CompTypeBios} + } else if strings.HasPrefix(uri, COMP_STATE_PARENT) { + cTypes = []componentType{CompTypeNWStack, CompTypeOS, CompTypeBootLoader, CompTypeBios} + } else if strings.HasPrefix(uri, "/openconfig-platform:components/component/state/software-version") { + cTypes = []componentType{CompTypeNWStack, CompTypeOS, CompTypeBootLoader, CompTypeBios} + } + return cTypes +} + +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 { + 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, inParams.dbs[db.StateDB], inParams.dbs[db.ConfigDB]) + 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], inParams.dbs[db.ConfigDB]) + 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) + d := dbs[db.StateDB] + ygot.BuildEmptyTree(pfComp) + switch cType { + case CompTypeIC: + return fillICInfo(pfComp, compName, targetUriPath, dbs, ygRoot) + case CompTypeNWStack, CompTypeOS, CompTypeBootLoader, CompTypeBios: + return fillSWCompInfo(pfComp, compName, pType, targetUriPath, d, cType) + } + 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, cfgdb) + 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] + cfgdb := dbs[db.ConfigDB] + 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, cfgdb) + 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, cfgdb) + 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, cfgdb) + 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) + case CompTypeNWStack: + fallthrough + case CompTypeOS: + fallthrough + case CompTypeBootLoader: + fallthrough + case CompTypeBios: + ygot.BuildEmptyTree(pf_comp.SoftwareModule) + ygot.BuildEmptyTree(pf_comp.SoftwareModule.State) + switch targetUriPath { + case COMP_SW_MOD: + fallthrough + case COMP_SW_MOD_ST: + return fillSWCompInfo(pf_comp, compName, AllCompPaths, targetUriPath, d, compType) + default: + return fillSWCompInfo(pf_comp, compName, SingularPath, targetUriPath, d, compType) + } + 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) +} + +func validSWCompName(name *string, prefix string) bool { + if name == nil || *name == "" { + return false + } + // Expect node name of form network_stackX or osX, where X is an integer (either 0 or 1) + if !strings.HasPrefix(*name, prefix) { + return false + } + + sp := strings.SplitAfter(*name, prefix) + if len(sp) < 2 { + return false + } + + if val, err := strconv.Atoi(sp[1]); err != nil || val > 1 { + return false + } + return true +} + +func getSWCompInfoFromDb(name string, d *db.DB, tblName string) (SWCompInfo, error) { + if d == nil { + return SWCompInfo{}, errors.New("DB instance is nil") + } + + swcEntry, err := d.GetEntry(&db.TableSpec{Name: tblName}, db.Key{Comp: []string{name}}) + if err != nil { + log.Info("Cannot get entry: ", name, "; Error: ", err) + return SWCompInfo{}, err + } + + swcInfo := SWCompInfo{ + Name: swcEntry.Get("name"), + SoftwareVersion: swcEntry.Get("software-version"), + Parent: swcEntry.Get("parent"), + OperStatus: swcEntry.Get("oper-status"), + Type: swcEntry.Get("type"), + BootLoaderType: swcEntry.Get("boot-loader-type"), + } + + return swcInfo, nil +} + +func fillBootLoaderContainer(info SWCompInfo, comp *ocbinds.OpenconfigPlatform_Components_Component) { + if info.BootLoaderType == "" { + return + } + ygot.BuildEmptyTree(comp.BootLoader) + ygot.BuildEmptyTree(comp.BootLoader.State) + if et, ok := dbToYangBootLoaderTypeMap[info.BootLoaderType]; ok { + comp.BootLoader.State.Type = et + } +} + +/* Filling in the state info for software components available in Redis DB */ +func fillSWCompInfo(comp *ocbinds.OpenconfigPlatform_Components_Component, + name string, pType PathType, targetUriPath string, stdb *db.DB, cType componentType) error { + swcInfo, err := getSWCompInfoFromDb(name, stdb, SW_COMP_TBL) + if err != nil { + log.V(3).Info("Error Getting SW Comp info from State DB: ", err.Error()) + return err + } + compState := comp.State + swModuleState := comp.SoftwareModule.State + defaultVal := "" + /*getChassis function is not used here . Parent name used as chassis*/ + defaultParentVal := CHASSIS_PREFIX + + if pType == AllPaths || pType == AllCompPaths || pType == StatePaths { + // Filling in state values + // State Name + compState.Name = &name + // State Software Version + compState.SoftwareVersion = &defaultVal + if swcInfo.SoftwareVersion != "" { + compState.SoftwareVersion = &swcInfo.SoftwareVersion + } + // State Parent + compState.Parent = &defaultParentVal + if swcInfo.Parent != "" { + compState.Parent = &swcInfo.Parent + } + // State Type + switch cType { + case CompTypeOS: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_OPERATING_SYSTEM) + case CompTypeBootLoader: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_BOOT_LOADER) + fillBootLoaderContainer(swcInfo, comp) + return nil + case CompTypeBios: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_BIOS) + case CompTypeNWStack: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_SOFTWARE_MODULE) + } + // State Oper Status + if operStatus, err := operStatusFromString(swcInfo.OperStatus); err == nil { + compState.OperStatus = operStatus + } else { + compState.OperStatus = ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_DISABLED + } + if pType == StatePaths { + return nil + } + // SW Module State Module Type + if cType == CompTypeNWStack { + swModuleState.ModuleType = ocbinds.OpenconfigPlatformSoftware_SOFTWARE_MODULE_TYPE_USERSPACE_PACKAGE_BUNDLE + } + return nil + } + + switch targetUriPath { + case COMP_STATE_NAME: + compState.Name = &name + case COMP_STATE_SW_VER: + if swcInfo.SoftwareVersion == "" { + return errors.New("software_version field not present in State DB") + } + compState.SoftwareVersion = &swcInfo.SoftwareVersion + case COMP_STATE_PARENT: + compState.Parent = &defaultParentVal + if swcInfo.Parent != "" { + compState.Parent = &swcInfo.Parent + } + case COMP_STATE_TYPE: + switch cType { + case CompTypeOS: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_OPERATING_SYSTEM) + case CompTypeBootLoader: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_BOOT_LOADER) + case CompTypeBios: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_BIOS) + case CompTypeNWStack: + compState.Type, _ = compState.To_OpenconfigPlatform_Components_Component_State_Type_Union( + ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_SOFTWARE_MODULE) + default: + return errors.New("invalid component type for software component") + } + case COMP_STATE_OPER_STATUS: + if cType == CompTypeBootLoader || cType == CompTypeBios { + return errors.New("invalid path for this component type.") + } + if operStatus, err := operStatusFromString(swcInfo.OperStatus); err == nil { + compState.OperStatus = operStatus + } else { + return errors.New("oper_status is missing/invalid field value in State DB: " + swcInfo.OperStatus) + } + case SW_MODULE_STATE_MODULE_TYPE: + if cType != CompTypeNWStack { + return errors.New("invalid component for software-module/state/module-type path.") + } + swModuleState.ModuleType = ocbinds.OpenconfigPlatformSoftware_SOFTWARE_MODULE_TYPE_USERSPACE_PACKAGE_BUNDLE + case SW_BOOT_LOADER_STATE_TYPE: + fillBootLoaderContainer(swcInfo, comp) + } + return nil +} diff --git a/translib/transformer/xfmr_platform_test.go b/translib/transformer/xfmr_platform_test.go new file mode 100644 index 000000000..ae87af784 --- /dev/null +++ b/translib/transformer/xfmr_platform_test.go @@ -0,0 +1,984 @@ +//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"}, + {CompTypeNWStack, "CompTypeNWStack"}, + {CompTypeOS, "CompTypeOS"}, + {CompTypeBootLoader, "CompTypeBootLoader"}, + {CompTypeBios, "CompTypeBios"}, + } + 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, 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 bubbles up", + 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) + } +} + +func TestSWGetSysCompUnknownCompTyp(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 TestSWOperStatusFromString(t *testing.T) { + cases := []struct { + in string + want ocbinds.E_OpenconfigPlatformTypes_COMPONENT_OPER_STATUS + wantErr bool + }{ + {"active", ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, false}, + {"INACTIVE", ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_INACTIVE, false}, + {"disabled", ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_DISABLED, false}, + {"unknown", ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_DISABLED, true}, + } + for _, c := range cases { + got, err := operStatusFromString(c.in) + if (err != nil) != c.wantErr { + t.Errorf("operStatusFromString(%q) error = %v, wantErr %v", c.in, err, c.wantErr) + } + if got != c.want { + t.Errorf("operStatusFromString(%q) = %v, want %v", c.in, got, c.want) + } + } +} + +func TestSWGetCompTypeByName_Patterns(t *testing.T) { + cases := []struct { + in string + want componentType + }{ + {"integrated_circuit0", CompTypeIC}, + {"network_stack0", CompTypeNWStack}, + {"os1", CompTypeOS}, + {"boot_loader_primary", CompTypeBootLoader}, + {"unknown_comp", CompTypeInvalid}, + } + for _, c := range cases { + got, _ := getCompTypeByName(c.in) + if got != c.want { + t.Errorf("getCompTypeByName(%q) = %v, want %v", c.in, got, c.want) + } + } +} + +func TestSWGetCompType_DbAndCache(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "sw_module_test" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "type": "SOFTWARE_MODULE", + }}) + + got := getCompType(compName, sdb, nil) + if got != CompTypeNWStack { + t.Errorf("getCompType DB lookup failed, got %v", got) + } + + gotCache := getCompType(compName, nil, nil) + if gotCache != CompTypeNWStack { + t.Errorf("getCompType cache lookup failed") + } +} + +func TestSWCompTypesForSubscriptionUri(t *testing.T) { + cases := []struct { + uri string + want []componentType + }{ + {"/openconfig-platform:components/component/software-module", []componentType{CompTypeNWStack, CompTypeOS}}, + {"/openconfig-platform:components/component/state/software-version", []componentType{CompTypeNWStack, CompTypeOS, CompTypeBootLoader, CompTypeBios}}, + {"/other/path", []componentType{}}, + } + for _, c := range cases { + got := compTypesForSubscriptionUri(c.uri) + if len(got) != len(c.want) { + t.Errorf("compTypesForSubscriptionUri(%q) length mismatch", c.uri) + } + } +} + +func TestSWValidSWCompName(t *testing.T) { + cases := []struct { + name string + prefix string + want bool + }{ + {"os0", "os", true}, + {"network_stack1", "network_stack", true}, + {"os2", "os", false}, + {"wrong_prefix0", "os", false}, + {"", "os", false}, + } + for _, c := range cases { + got := validSWCompName(&c.name, c.prefix) + if got != c.want { + t.Errorf("validSWCompName(%q, %q) = %v, want %v", c.name, c.prefix, got, c.want) + } + } +} + +func TestSWFillSWCompInfo_Comp(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "os0" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "name": "SONiC", + "software-version": "202405", + "oper-status": "ACTIVE", + "type": "OPERATING_SYSTEM", + }}) + + comp := &ocbinds.OpenconfigPlatform_Components_Component{} + ygot.BuildEmptyTree(comp) + + err := fillSWCompInfo(comp, compName, AllPaths, COMP_ST, sdb, CompTypeOS) + if err != nil || *comp.State.SoftwareVersion != "202405" { + t.Errorf("fillSWCompInfo AllPaths failed") + } + + ygot.BuildEmptyTree(comp) + err = fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_SW_VER, sdb, CompTypeOS) + if err != nil || *comp.State.SoftwareVersion != "202405" { + t.Errorf("fillSWCompInfo SingularPath SW_VER failed") + } + + ygot.BuildEmptyTree(comp) + err = fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_OPER_STATUS, sdb, CompTypeOS) + if comp.State.OperStatus != ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_ACTIVE { + t.Errorf("fillSWCompInfo OperStatus failed") + } +} + +func TestSWGetSysComponents_Subtree(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "os0" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "type": "OPERATING_SYSTEM", + }}) + + pf_cpts := &ocbinds.OpenconfigPlatform_Components{ + Component: make(map[string]*ocbinds.OpenconfigPlatform_Components_Component), + } + pf_cpts.NewComponent(compName) + + inParams := XfmrParams{ + dbs: [db.MaxDB]*db.DB{db.StateDB: sdb}, + } + + err := getSysComponents(pf_cpts, COMP_SW_MOD, inParams, compName, "") + if err != nil { + t.Errorf("getSysComponents subtree fetch failed: %v", err) + } + if pf_cpts.Component[compName].SoftwareModule == nil { + t.Errorf("SoftwareModule container not initialized") + } +} +func TestSWFillSWCompInfo_SWTypes(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + tests := []struct { + name string + compType componentType + dbType string + wantType ocbinds.E_OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT + }{ + {"boot_loader0", CompTypeBootLoader, "BOOT_LOADER", ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_BOOT_LOADER}, + {"bios0", CompTypeBios, "BIOS", ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_BIOS}, + {"network_stack0", CompTypeNWStack, "SOFTWARE_MODULE", ocbinds.OpenconfigPlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT_SOFTWARE_MODULE}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{tt.name}}, db.Value{Field: map[string]string{ + "type": tt.dbType, + "oper-status": "ACTIVE", + "boot-loader-type": "GRUB", + }}) + + comp := &ocbinds.OpenconfigPlatform_Components_Component{} + ygot.BuildEmptyTree(comp) + + err := fillSWCompInfo(comp, tt.name, AllPaths, COMP, sdb, tt.compType) + if err != nil { + t.Fatalf("fillSWCompInfo failed for %s: %v", tt.name, err) + } + + if tt.compType == CompTypeBootLoader { + if comp.BootLoader == nil || comp.BootLoader.State.Type == ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_UNSET { + t.Errorf("BootLoader container or type not populated") + } + } + }) + } +} +func TestSWFillSWCompInfo_SingPath(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "sw_comp_test" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "parent": "chassis_parent", + "type": "SOFTWARE_MODULE", + "boot-loader-type": "UBOOT", + "oper-status": "ACTIVE", + }}) + + comp := &ocbinds.OpenconfigPlatform_Components_Component{} + ygot.BuildEmptyTree(comp) + + fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_PARENT, sdb, CompTypeNWStack) + if comp.State.Parent == nil || *comp.State.Parent != "chassis_parent" { + t.Errorf("Expected parent chassis_parent, got %v", comp.State.Parent) + } + + fillSWCompInfo(comp, compName, SingularPath, SW_MODULE_STATE_MODULE_TYPE, sdb, CompTypeNWStack) + if comp.SoftwareModule.State.ModuleType != ocbinds.OpenconfigPlatformSoftware_SOFTWARE_MODULE_TYPE_USERSPACE_PACKAGE_BUNDLE { + t.Errorf("Unexpected ModuleType") + } + + fillSWCompInfo(comp, compName, SingularPath, SW_BOOT_LOADER_STATE_TYPE, sdb, CompTypeBootLoader) + if comp.BootLoader.State.Type != ocbinds.OpenconfigPlatformBootLoader_BOOT_LOADER_BASE_BOOT_LOADER_UBOOT { + t.Errorf("Unexpected BootLoader Type") + } +} +func TestSWFillSWCompInfo_ValidnErr(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "os0" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "type": "OPERATING_SYSTEM", + }}) + + comp := &ocbinds.OpenconfigPlatform_Components_Component{} + ygot.BuildEmptyTree(comp) + + err := fillSWCompInfo(comp, compName, SingularPath, SW_MODULE_STATE_MODULE_TYPE, sdb, CompTypeOS) + if err == nil { + t.Error("Expected error for SW_MODULE_STATE_MODULE_TYPE on OS component") + } + + err = fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_OPER_STATUS, sdb, CompTypeBootLoader) + if err == nil { + t.Error("Expected error for COMP_STATE_OPER_STATUS on BootLoader") + } +} +func TestSWGetCompType_BL_BiosDb(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + tests := []struct { + name string + dbType string + wantType componentType + }{ + {"boot_comp_db", BOOTL_TYPE, CompTypeBootLoader}, + {"bios_comp_db", BIOS_TYPE, CompTypeBios}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{tt.name}}, db.Value{Field: map[string]string{ + "type": tt.dbType, + }}) + + compTypeCache.Delete(tt.name) + + got := getCompType(tt.name, sdb, nil) + if got != tt.wantType { + t.Errorf("getCompType(%q) for DB type %s = %v, want %v", tt.name, tt.dbType, got, tt.wantType) + } + + if val, ok := compTypeCache.Load(tt.name); !ok || val.(componentType) != tt.wantType { + t.Errorf("getCompType(%q) failed to cache the result", tt.name) + } + }) + } +} +func TestSWFillSWCompInfo_StateUriType(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "type_test_comp" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "type": "SOFTWARE_MODULE", + }}) + + tests := []struct { + name string + cType componentType + wantErr bool + }{ + {"OS", CompTypeOS, false}, + {"BootLoader", CompTypeBootLoader, false}, + {"Bios", CompTypeBios, false}, + {"NWStack", CompTypeNWStack, false}, + {"Invalid", CompTypeIC, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + comp := &ocbinds.OpenconfigPlatform_Components_Component{} + ygot.BuildEmptyTree(comp) + ygot.BuildEmptyTree(comp.State) + + err := fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_TYPE, sdb, tt.cType) + + if (err != nil) != tt.wantErr { + t.Fatalf("fillSWCompInfo(COMP_STATE_TYPE) error = %v, wantErr %v", err, tt.wantErr) + } + + if !tt.wantErr { + if comp.State.Type == nil { + t.Fatal("comp.State.Type was not populated") + } + } + }) + } +} +func TestSWFillSWCompInfo_NameVerErr(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + compName := "os0" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{compName}}, db.Value{Field: map[string]string{ + "name": "SONiC", + "type": "OPERATING_SYSTEM", + }}) + + comp := &ocbinds.OpenconfigPlatform_Components_Component{} + ygot.BuildEmptyTree(comp) + + err := fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_NAME, sdb, CompTypeOS) + if err != nil || comp.State.Name == nil || *comp.State.Name != compName { + t.Errorf("fillSWCompInfo COMP_STATE_NAME failed") + } + + err = fillSWCompInfo(comp, compName, SingularPath, COMP_STATE_SW_VER, sdb, CompTypeOS) + if err == nil || err.Error() != "software_version field not present in State DB" { + t.Errorf("Expected error for missing software_version, got: %v", err) + } +} + +func TestSWGetSysComponents_SWIC(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + inParams := XfmrParams{ + dbs: [db.MaxDB]*db.DB{db.StateDB: sdb}, + } + + pf_cpts := &ocbinds.OpenconfigPlatform_Components{ + Component: make(map[string]*ocbinds.OpenconfigPlatform_Components_Component), + } + + nwName := "network_stack0" + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{nwName}}, db.Value{Field: map[string]string{ + "type": "SOFTWARE_MODULE", + "software-version": "1.0", + "oper-status": "active", + }}) + + pf_cpts.NewComponent(nwName) + + _ = getSysComponents(pf_cpts, COMP_SW_MOD, inParams, nwName, "") + + icName := "integrated_circuit0" + pf_cpts.NewComponent(icName) + + _ = getSysComponents(pf_cpts, COMP_ST, inParams, icName, "") +} +func TestSWGetCompType_NWStackSplCase(t *testing.T) { + sdb, _ := db.NewDB(getDBOptions(db.StateDB)) + defer sdb.DeleteDB() + + tests := []struct { + name string + dbType string + moduleType string + wantType componentType + }{ + { + name: "legacy_bootloader_in_nw_stack_table", + dbType: NW_STACK_TYPE, + moduleType: BOOTL_TYPE, + wantType: CompTypeBootLoader, + }, + { + name: "standard_network_stack", + dbType: NW_STACK_TYPE, + moduleType: "USERSPACE_PACKAGE_BUNDLE", + wantType: CompTypeNWStack, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + compTypeCache.Delete(tt.name) + + sdb.SetEntry(&db.TableSpec{Name: SW_COMP_TBL}, db.Key{Comp: []string{tt.name}}, db.Value{Field: map[string]string{ + "type": tt.dbType, + "module-type": tt.moduleType, + }}) + + got := getCompType(tt.name, sdb, nil) + if got != tt.wantType { + t.Errorf("getCompType(%q) with module-type %q = %v, want %v", tt.name, tt.moduleType, got, tt.wantType) + } + + if val, ok := compTypeCache.Load(tt.name); !ok || val.(componentType) != tt.wantType { + t.Errorf("getCompType(%q) failed to cache the correct type", tt.name) + } + }) + } +}