-
Notifications
You must be signed in to change notification settings - Fork 222
USHIFT-1086: Refuse to start if versions of data and executable are different, refuse migration if data version > exec version #1900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,7 +65,23 @@ func (pr *PreRun) Perform() error { | |
| klog.InfoS("Previous boot", "health", health.Health, "deploymentID", health.DeploymentID, "bootID", health.BootID) | ||
|
|
||
| if health.IsHealthy() { | ||
| return pr.backup(health) | ||
| if err := pr.backup(health); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| migrationNeeded, err := pr.checkVersions() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| klog.InfoS("Version checks successful", "is-migration-needed?", migrationNeeded) | ||
|
|
||
| if migrationNeeded { | ||
| _ = migrationNeeded | ||
| // TODO: data migration | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| return pr.restore() | ||
|
|
@@ -127,6 +143,31 @@ func (pr *PreRun) restore() error { | |
| return pr.dataManager.Restore(backupsForDeployment[0]) | ||
| } | ||
|
|
||
| // checkVersions compares version of data and executable | ||
| // | ||
| // It returns true if migration should be performed. | ||
| // It returns non-nil error if difference between versions is unsupported. | ||
| func (pr *PreRun) checkVersions() (bool, error) { | ||
| execVer, err := getVersionOfExecutable() | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| dataVer, err := getVersionOfData() | ||
| if err != nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic below this point would be easier to unit test if it was pulled out into its own function so the test suite could pass the execVer and dataVer values for different scenarios.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intended that initially but was discouraged by no protection from passing params in wrong order and doing checks wrong. but the ut argument is valid so i'll change it |
||
| if errors.Is(err, errDataVersionDoesNotExist) { | ||
| klog.InfoS("Version file of data does not exist - assuming data version is 4.13") | ||
| // TODO: 4.13 | ||
| return true, nil | ||
| } | ||
| return false, err | ||
| } | ||
|
|
||
| klog.InfoS("Checking version difference between data and executable", "data", dataVer, "exec", execVer) | ||
|
|
||
| return checkVersionDiff(execVer, dataVer) | ||
| } | ||
|
|
||
| func getCurrentDeploymentID() (string, error) { | ||
| cmd := exec.Command("rpm-ostree", "status", "--jsonpath=$.deployments[0].id", "--booted") | ||
| var stdout, stderr bytes.Buffer | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| package prerun | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/openshift/microshift/pkg/config" | ||
| "github.com/openshift/microshift/pkg/util" | ||
| "github.com/openshift/microshift/pkg/version" | ||
| "k8s.io/klog/v2" | ||
| ) | ||
|
|
||
| var ( | ||
| versionFilePath = filepath.Join(config.DataDir, "version") | ||
|
|
||
| errDataVersionDoesNotExist = errors.New("version file for MicroShift data does not exist") | ||
| ) | ||
|
|
||
| // CreateOrValidateDataVersion creates or compares data version against executable's version | ||
| // | ||
| // Function is intended to be invoked by main MicroShift run procedure, just before starting, | ||
| // to ensure that storage migration (which should update the version file) was performed. | ||
| func CreateOrValidateDataVersion() error { | ||
| execVer, err := getVersionOfExecutable() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| dataVer, err := getVersionOfData() | ||
| if err != nil { | ||
| if errors.Is(err, errDataVersionDoesNotExist) { | ||
| // First run of MicroShift, create version file shortly after creating DataDir | ||
| execVerS := execVer.String() | ||
| klog.InfoS("Version file in data directory does not exist - creating", "version", execVerS) | ||
|
|
||
| if err := os.WriteFile(versionFilePath, []byte(execVerS), 0600); err != nil { | ||
| return fmt.Errorf("writing '%s' to %s failed: %w", execVerS, versionFilePath, err) | ||
| } | ||
| return nil | ||
| } | ||
| return err | ||
| } | ||
| klog.InfoS("Comparing versions of MicroShift data on disk and executable", "data", dataVer, "exec", execVer) | ||
|
|
||
| if execVer != dataVer { | ||
| return fmt.Errorf("data version (%s) does not match binary version (%s) - missing migration?", dataVer, execVer) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| type versionMetadata struct { | ||
| Major, Minor int | ||
| } | ||
|
|
||
| func (v versionMetadata) String() string { | ||
| return fmt.Sprintf("%d.%d", v.Major, v.Minor) | ||
| } | ||
|
|
||
| // versionMetadataFromString creates versionMetadata object from "major.minor" string where X and Y are integers | ||
| func versionMetadataFromString(majorMinor string) (versionMetadata, error) { | ||
| majorMinor = strings.TrimSpace(majorMinor) | ||
| majorMinorSplit := strings.Split(majorMinor, ".") | ||
| if len(majorMinorSplit) != 2 { | ||
| return versionMetadata{}, fmt.Errorf("invalid version string (%s): expected X.Y", majorMinor) | ||
| } | ||
|
|
||
| major, err := strconv.Atoi(majorMinorSplit[0]) | ||
| if err != nil { | ||
| return versionMetadata{}, fmt.Errorf("converting '%s' to an int failed: %w", majorMinorSplit[0], err) | ||
| } | ||
|
|
||
| minor, err := strconv.Atoi(majorMinorSplit[1]) | ||
| if err != nil { | ||
| return versionMetadata{}, fmt.Errorf("converting '%s' to an int failed: %w", majorMinorSplit[1], err) | ||
| } | ||
|
|
||
| return versionMetadata{Major: major, Minor: minor}, nil | ||
| } | ||
|
|
||
| func getVersionOfExecutable() (versionMetadata, error) { | ||
| ver := version.Get() | ||
| return versionMetadataFromString(fmt.Sprintf("%s.%s", ver.Major, ver.Minor)) | ||
| } | ||
|
|
||
| func getVersionOfData() (versionMetadata, error) { | ||
| exists, err := util.PathExists(versionFilePath) | ||
| if err != nil { | ||
| return versionMetadata{}, fmt.Errorf("checking if path exists failed: %w", err) | ||
| } | ||
|
|
||
| if !exists { | ||
| return versionMetadata{}, errDataVersionDoesNotExist | ||
| } | ||
|
|
||
| versionFileContents, err := os.ReadFile(versionFilePath) | ||
| if err != nil { | ||
| return versionMetadata{}, fmt.Errorf("reading %s failed: %w", versionFilePath, err) | ||
| } | ||
|
|
||
| return versionMetadataFromString(string(versionFileContents)) | ||
| } | ||
|
|
||
| func checkVersionDiff(execVer, dataVer versionMetadata) (bool, error) { | ||
| if execVer == dataVer { | ||
| return false, nil | ||
| } | ||
|
|
||
| if execVer.Major != dataVer.Major { | ||
| return false, fmt.Errorf("major versions are different: %d and %d", dataVer.Major, execVer.Major) | ||
| } | ||
|
|
||
| if execVer.Minor < dataVer.Minor { | ||
| return false, fmt.Errorf("executable (%s) is older than existing data (%s): migrating data to older version is not supported", execVer.String(), dataVer.String()) | ||
| } | ||
|
|
||
| return false, nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package prerun | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestCheckVersionDiff(t *testing.T) { | ||
| testData := []struct { | ||
| name string | ||
| execVer versionMetadata | ||
| dataVer versionMetadata | ||
| expectedMigrationRequired bool | ||
| errExpected bool | ||
| }{ | ||
| { | ||
| name: "equal versions: no migration, no error", | ||
| execVer: versionMetadata{Major: 4, Minor: 14}, | ||
| dataVer: versionMetadata{Major: 4, Minor: 14}, | ||
| expectedMigrationRequired: false, | ||
| errExpected: false, | ||
| }, | ||
| { | ||
| name: "X versions must be the same", | ||
| execVer: versionMetadata{Major: 4, Minor: 14}, | ||
| dataVer: versionMetadata{Major: 5, Minor: 14}, | ||
| expectedMigrationRequired: false, | ||
| errExpected: true, | ||
| }, | ||
| { | ||
| name: "binary must not be older than data", | ||
| execVer: versionMetadata{Major: 4, Minor: 14}, | ||
| dataVer: versionMetadata{Major: 4, Minor: 15}, | ||
| expectedMigrationRequired: false, | ||
| errExpected: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, td := range testData { | ||
| td := td | ||
| t.Run(td.name, func(t *testing.T) { | ||
| migrationRequired, err := checkVersionDiff(td.execVer, td.dataVer) | ||
|
|
||
| assert.Equal(t, td.expectedMigrationRequired, migrationRequired) | ||
| if td.errExpected { | ||
| assert.Error(t, err) | ||
| } else { | ||
| assert.NoError(t, err) | ||
| } | ||
| }) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.