From 54734f87ced1097184db1db943a9aba8cfc5c0a1 Mon Sep 17 00:00:00 2001 From: Marco Kawajiri Date: Wed, 10 Apr 2024 03:55:57 -0700 Subject: [PATCH] Ensure BMC image is compatible with MB CPLD version Summary: grandteton versions v2024.11.1 require MB CPLD version `20010` or greater. Added `checks_and_remediations` step to fail the upgrade if the MB CPLD version is incompatible with the image version. Test Plan: Unit tests, push flashy/bmc images to grandteton units e.g: ``` $ scripts/run_flashy_remote.sh --device mtd:flash1 --imagepath /tmp/tmp.H8gzWMVfip/openbmc.image.grandteton\:v2024.11.1/flash-grandteton --host twshared39604-oob.02.nao6.facebook.com --dry-run ``` Run step on a BMC with image version = v2024.11.1 and old MB CPLD (should fail): ``` # /run/flashy/flashy --install && /run/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version --imagepath /run/upgrade/image --device mtd:flash1 ... {"message":"S365492: Image version ('grandteton-v2024.11.1') requires MB CPLD version 20010 or greater (current MB CPLD version is 20006)"} ``` Run step on a BMC with image version = v2024.11.1 and new MB CPLD (should succeed): ``` /run/flashy/flashy --install && /run/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version --imagepath /run/upgrade/image --device mtd:flash1 ... 2024/04/09 08:33:48 MB CPLD version 20010 is compatible with image version grandteton-v2024.11.1 ``` Run step on a BMC with image version < v2024.11.1 and old MB CPLD (should succeed): ``` # /run/flashy/flashy --install && /run/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version --imagepath /run/upgrade/image --device mtd:flash1 ... 2024/04/09 08:35:46 MB CPLD version 20006 is compatible with image version grandteton-v2023.39.2 ``` Run step on a BMC with image version < v2024.11.1 and new MB CPLD (should succeed): ``` # /run/flashy/flashy --install && /run/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version --imagepath /run/upgrade/image --device mtd:flash1 ... 2024/04/09 08:40:29 MB CPLD version 20010 is compatible with image version grandteton-v2023.39.2 ``` Reviewed By: doranand Differential Revision: D55921434 fbshipit-source-id: 8672d76c8f4ef18978cda8826f73395b1309b50c --- .../00_ensure_compatible_mb_cpld_version.go | 120 ++++++++++++++++++ ..._ensure_compatible_mb_cpld_version_test.go | 110 ++++++++++++++++ tools/flashy/install/install.go | 1 + tools/flashy/lib/validate/compatibility.go | 6 +- .../flashy/lib/validate/compatibility_test.go | 8 +- 5 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version.go create mode 100644 tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version_test.go diff --git a/tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version.go b/tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version.go new file mode 100644 index 00000000000..60320eed4ff --- /dev/null +++ b/tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version.go @@ -0,0 +1,120 @@ +/** + * Copyright 2020-present Facebook. All Rights Reserved. + * + * This program file is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +package remediations_grandteton + +import ( + "encoding/json" + "log" + "regexp" + "strconv" + "time" + + "github.com/facebook/openbmc/tools/flashy/lib/step" + "github.com/facebook/openbmc/tools/flashy/lib/utils" + "github.com/facebook/openbmc/tools/flashy/lib/validate" + "github.com/pkg/errors" +) + +func init() { + step.RegisterStep(ensureCompatibleMBCPLDVersion) +} + +type FWUtilVersionJson struct { + COMPONENT string + FRU string + PRETTY_COMPONENT string + VERSION string +} + +var versionRegex = regexp.MustCompile("^grandteton-(v[0-9]+[.][0-9]+[.][0-9]+)$") + +var GetOpenBMCVersionFromImageFile = validate.GetOpenBMCVersionFromImageFile + +// ensureCompatibleMBCPLDVersion checks whether the MB CPLD version is compatible with the image being +// flashed +func ensureCompatibleMBCPLDVersion(stepParams step.StepParams) step.StepExitError { + // Fetch current MB CPLD version from fw-util + mbCpldVersion, stepErr := fwutilGetMbCpldVersion() + if stepErr != nil { + return stepErr + } + + // Fetch BMC version from image file + imageFileVer, err := GetOpenBMCVersionFromImageFile(stepParams.ImageFilePath) + if err != nil { + return step.ExitSafeToReboot{ + Err: err, + } + } + + if !versionRegex.MatchString(imageFileVer) { + return step.ExitSafeToReboot{ + Err: errors.Errorf("Image version '%v' does not match expected format %v", imageFileVer, versionRegex), + } + } + + // S365492: Images newer than v2024.11.1 require MB CPLD version 20010 or greater, otherwise sensor data + // becomes unavailable (T175831581). Booting older images with the new CPLD version should be safe. + if imageFileVer >= "grandteton-v2024.11.1" && mbCpldVersion < 20010 { + return step.ExitSafeToReboot{ + Err: errors.Errorf("S365492: Image version ('%v') requires MB CPLD version 20010 or greater (current MB CPLD version is %d)", imageFileVer, mbCpldVersion), + } + } + + log.Printf("MB CPLD version %d is compatible with image version %v", mbCpldVersion, imageFileVer) + + return nil +} + +func fwutilGetMbCpldVersion() (int, step.StepExitError) { + // Run fw-util + fwutilCmd := []string{ + "fw-util", "mb", "--version-json", "mb_cpld", + } + exitCode, err, stdout, stderr := utils.RunCommand(fwutilCmd, 1800*time.Second) + + if err != nil { + return 0, step.ExitSafeToReboot{ + Err: errors.Errorf("`fw-util mb --version-json mb_cpld` failed with exit code %d, stderr: %v, stdout: %v", exitCode, stderr, stdout), + } + } + + var fwutilVersionJson []FWUtilVersionJson + if err := json.Unmarshal([]byte(stdout), &fwutilVersionJson); err != nil { + return 0, step.ExitSafeToReboot{ + Err: errors.Errorf("failed to parse `fw-util mb --version-json mb_cpld` output: %v", err), + } + } + + if len(fwutilVersionJson) != 1 { + return 0, step.ExitSafeToReboot{ + Err: errors.Errorf("`fw-util mb --version-json mb_cpld` returned %v entries", len(fwutilVersionJson)), + } + } + + mbCpldVersion, err := strconv.Atoi(fwutilVersionJson[0].VERSION) + if err != nil { + return 0, step.ExitSafeToReboot{ + Err: errors.Errorf("Cannot parse `fw-util mb --version-json mb_cpld` version as integer: %v", fwutilVersionJson), + } + } + + return mbCpldVersion, nil +} diff --git a/tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version_test.go b/tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version_test.go new file mode 100644 index 00000000000..2595d7352fd --- /dev/null +++ b/tools/flashy/checks_and_remediations/grandteton/00_ensure_compatible_mb_cpld_version_test.go @@ -0,0 +1,110 @@ +package remediations_grandteton + +import ( + "strings" + "testing" + "time" + + "github.com/facebook/openbmc/tools/flashy/lib/step" + "github.com/facebook/openbmc/tools/flashy/lib/utils" + "github.com/pkg/errors" +) + +var fwUtilOutputOldCpldExample = ` +[ + { + "COMPONENT": "mb_cpld", + "FRU": "mb", + "PRETTY_COMPONENT": "MB_CPLD", + "VERSION": "00020008" + } +] +` + +var fwUtilOutputNewCpldExample = ` +[ + { + "COMPONENT": "mb_cpld", + "FRU": "mb", + "PRETTY_COMPONENT": "MB_CPLD", + "VERSION": "00020010" + } +] +` + +func TestEnsureCompatibleMBCPLDVersion(t *testing.T) { + // mock and defer restore RunCommand + runCommandOrig := utils.RunCommand + getOpenBMCVersionFromImageFileOrig := GetOpenBMCVersionFromImageFile + defer func() { + utils.RunCommand = runCommandOrig + GetOpenBMCVersionFromImageFile = getOpenBMCVersionFromImageFileOrig + }() + cases := []struct { + name string + bmcImageVersion string + runCmdStdout string + want step.StepExitError + }{ + { + name: "succeeded as image version == v2023.39.2 and CPLD is old", + bmcImageVersion: "grandteton-v2023.39.2", + runCmdStdout: fwUtilOutputOldCpldExample, + want: nil, + }, + { + name: "succeeded as image version == v2024.11.1 and CPLD is new", + bmcImageVersion: "grandteton-v2024.11.1", + runCmdStdout: fwUtilOutputNewCpldExample, + want: nil, + }, + { + name: "succeeded as image version > v2024.11.1 and CPLD is new", + bmcImageVersion: "grandteton-v2024.12.1", + runCmdStdout: fwUtilOutputNewCpldExample, + want: nil, + }, + { + name: "failed as image version == v2024.11.1 but CPLD is old", + bmcImageVersion: "grandteton-v2024.11.1", + runCmdStdout: fwUtilOutputOldCpldExample, + want: step.ExitSafeToReboot{ + Err: errors.Errorf("S365492: Image version ('grandteton-v2024.11.1') requires MB CPLD version 20010 or greater (current MB CPLD version is 20008)"), + }, + }, + { + name: "failed as image version > v2024.11.1 but CPLD is old", + bmcImageVersion: "grandteton-v2024.12.0", + runCmdStdout: fwUtilOutputOldCpldExample, + want: step.ExitSafeToReboot{ + Err: errors.Errorf("S365492: Image version ('grandteton-v2024.12.0') requires MB CPLD version 20010 or greater (current MB CPLD version is 20008)"), + }, + }, + { + name: "invalid image version", + bmcImageVersion: "fby3-v2024.12.0", + runCmdStdout: fwUtilOutputOldCpldExample, + want: step.ExitSafeToReboot{ + Err: errors.Errorf("Image version 'fby3-v2024.12.0' does not match expected format ^grandteton-(v[0-9]+[.][0-9]+[.][0-9]+)$"), + }, + }, + } + + wantCmd := "fw-util mb --version-json mb_cpld" + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + utils.RunCommand = func(cmdArr []string, timeout time.Duration) (int, error, string, string) { + gotCmd := strings.Join(cmdArr, " ") + if wantCmd != gotCmd { + t.Errorf("cmd: want '%v' got '%v'", wantCmd, gotCmd) + } + return 0, nil, tc.runCmdStdout, "" + } + GetOpenBMCVersionFromImageFile = func(imageFile string) (string, error) { + return tc.bmcImageVersion, nil + } + got := ensureCompatibleMBCPLDVersion(step.StepParams{}) + step.CompareTestExitErrors(tc.want, got, t) + }) + } +} diff --git a/tools/flashy/install/install.go b/tools/flashy/install/install.go index ac7ede289d9..fa9a2eff0ce 100644 --- a/tools/flashy/install/install.go +++ b/tools/flashy/install/install.go @@ -34,6 +34,7 @@ import ( _ "github.com/facebook/openbmc/tools/flashy/checks_and_remediations/galaxy100" _ "github.com/facebook/openbmc/tools/flashy/checks_and_remediations/wedge100" _ "github.com/facebook/openbmc/tools/flashy/checks_and_remediations/yamp" + _ "github.com/facebook/openbmc/tools/flashy/checks_and_remediations/grandteton" _ "github.com/facebook/openbmc/tools/flashy/flash_procedure" _ "github.com/facebook/openbmc/tools/flashy/utilities" ) diff --git a/tools/flashy/lib/validate/compatibility.go b/tools/flashy/lib/validate/compatibility.go index 87b43a9ec38..019bd3c1efd 100644 --- a/tools/flashy/lib/validate/compatibility.go +++ b/tools/flashy/lib/validate/compatibility.go @@ -60,7 +60,7 @@ var CheckImageBuildNameCompatibility = func(imageFilePath string) error { } log.Printf("OpenBMC Version from /etc/issue: '%v'", etcIssueVer) - imageFileVer, err := getOpenBMCVersionFromImageFile(imageFilePath) + imageFileVer, err := GetOpenBMCVersionFromImageFile(imageFilePath) if err != nil { return err } @@ -109,11 +109,11 @@ var getNormalizedBuildNameFromVersion = func(ver string) (string, error) { return verMap[rBuildname], nil } -// getOpenBMCVersionFromImageFile gets OpenBMC version from the image file. +// GetOpenBMCVersionFromImageFile gets OpenBMC version from the image file. // examples: fbtp-v2020.09.1, wedge100-v2020.07.1 // WARNING: This relies on the U-Boot version string on the image // there is no guarantee that this will succeed -var getOpenBMCVersionFromImageFile = func(imageFilePath string) (string, error) { +var GetOpenBMCVersionFromImageFile = func(imageFilePath string) (string, error) { // mmap the first 1MB of the image file imageFileBuf, err := fileutils.MmapFileRange( imageFilePath, 0, 1024*1024, syscall.PROT_READ, syscall.MAP_SHARED, diff --git a/tools/flashy/lib/validate/compatibility_test.go b/tools/flashy/lib/validate/compatibility_test.go index 228e2c60957..dbdb2e3ec82 100644 --- a/tools/flashy/lib/validate/compatibility_test.go +++ b/tools/flashy/lib/validate/compatibility_test.go @@ -41,11 +41,11 @@ func TestCompatibleVersionMapping(t *testing.T) { func TestCheckImageBuildNameCompatibility(t *testing.T) { getOpenBMCVersionFromIssueFileOrig := utils.GetOpenBMCVersionFromIssueFile - getOpenBMCVersionFromImageFileOrig := getOpenBMCVersionFromImageFile + getOpenBMCVersionFromImageFileOrig := GetOpenBMCVersionFromImageFile compatibleVersionMappingOrig := compatibleVersionMapping defer func() { utils.GetOpenBMCVersionFromIssueFile = getOpenBMCVersionFromIssueFileOrig - getOpenBMCVersionFromImageFile = getOpenBMCVersionFromImageFileOrig + GetOpenBMCVersionFromImageFile = getOpenBMCVersionFromImageFileOrig compatibleVersionMapping = compatibleVersionMappingOrig }() @@ -112,7 +112,7 @@ func TestCheckImageBuildNameCompatibility(t *testing.T) { utils.GetOpenBMCVersionFromIssueFile = func() (string, error) { return tc.etcIssueVer, nil } - getOpenBMCVersionFromImageFile = func(imageFilePath string) (string, error) { + GetOpenBMCVersionFromImageFile = func(imageFilePath string) (string, error) { if exampleImageFilePath != imageFilePath { t.Errorf("imageFilePath: want '%v' got '%v'", exampleImageFilePath, imageFilePath) } @@ -241,7 +241,7 @@ func TestGetOpenBMCVersionFromImageFile(t *testing.T) { } return tc.fileBuf, tc.mmapErr } - got, err := getOpenBMCVersionFromImageFile("x") + got, err := GetOpenBMCVersionFromImageFile("x") if tc.want != got { t.Errorf("want '%v' got '%v'", tc.want, got) }