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
e2e test: add schema version verification in mix_version_test. #17881
base: main
Are you sure you want to change the base?
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 |
---|---|---|
|
@@ -123,14 +123,37 @@ func schemaChangesForVersion(v semver.Version, isUpgrade bool) ([]schemaChange, | |
return actions, nil | ||
} | ||
|
||
func NewFieldsForVersion(v semver.Version) []NewField { | ||
if newFields, found := newFieldsMapping[v]; found { | ||
return newFields | ||
} | ||
return nil | ||
} | ||
|
||
func newFieldMappingsToSchemaChanges(newFieldMap map[semver.Version][]NewField) map[semver.Version][]schemaChange { | ||
schemaChangeMap := map[semver.Version][]schemaChange{} | ||
for ver, newFields := range newFieldMap { | ||
changes := []schemaChange{} | ||
for _, f := range newFields { | ||
changes = append(changes, f.schemaChange()) | ||
} | ||
schemaChangeMap[ver] = changes | ||
} | ||
return schemaChangeMap | ||
} | ||
|
||
var ( | ||
// schemaChanges list changes that were introduced in a particular version. | ||
// newFieldsMapping list new fields that were introduced in a particular version. | ||
// schema was introduced in v3.6 as so its changes were not tracked before. | ||
schemaChanges = map[semver.Version][]schemaChange{ | ||
newFieldsMapping = map[semver.Version][]NewField{ | ||
version.V3_6: { | ||
addNewField(Meta, MetaStorageVersionName, emptyStorageVersion), | ||
{Meta, MetaStorageVersionName, emptyStorageVersion}, | ||
}, | ||
} | ||
// schemaChanges list changes that were introduced in a particular version. | ||
// schema was introduced in v3.6 as so its changes were not tracked before. | ||
schemaChanges = newFieldMappingsToSchemaChanges(newFieldsMapping) | ||
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. Don't like this change, it limits the possible schema changes to just adding new empty fields. |
||
|
||
// emptyStorageVersion is used for v3.6 Step for the first time, in all other version StoragetVersion should be set by migrator. | ||
// Adding a addNewField for StorageVersion we can reuse logic to remove it when downgrading to v3.5 | ||
emptyStorageVersion = []byte("") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,9 @@ import ( | |
"go.etcd.io/etcd/client/pkg/v3/fileutil" | ||
"go.etcd.io/etcd/pkg/v3/expect" | ||
"go.etcd.io/etcd/pkg/v3/proxy" | ||
"go.etcd.io/etcd/server/v3/storage/backend" | ||
"go.etcd.io/etcd/server/v3/storage/datadir" | ||
"go.etcd.io/etcd/server/v3/storage/schema" | ||
"go.etcd.io/etcd/tests/v3/framework/config" | ||
) | ||
|
||
|
@@ -60,6 +63,8 @@ type EtcdProcess interface { | |
LazyFS() *LazyFS | ||
Logs() LogsExpect | ||
Kill() error | ||
// VerifySchemaVersion verifies the db file schema version is compatible with the binary after the process is closed. | ||
VerifySchemaVersion(lg *zap.Logger) error | ||
} | ||
|
||
type LogsExpect interface { | ||
|
@@ -253,6 +258,43 @@ func (ep *EtcdServerProcess) Close() error { | |
return nil | ||
} | ||
|
||
func (ep *EtcdServerProcess) VerifySchemaVersion(lg *zap.Logger) error { | ||
currentEtcdVer, err := GetVersionFromBinary(ep.cfg.ExecPath) | ||
if err != nil { | ||
return err | ||
} | ||
currentEtcdVer.Patch = 0 | ||
prevEtcdVer := semver.Version{Major: currentEtcdVer.Major, Minor: currentEtcdVer.Minor - 1} | ||
|
||
dbPath := datadir.ToBackendFileName(ep.cfg.DataDirPath) | ||
be := backend.NewDefaultBackend(lg, dbPath) | ||
defer be.Close() | ||
ver, err := schema.UnsafeDetectSchemaVersion(lg, be.BatchTx()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// in a mix version cluster, the storage version would be set to the cluster version, | ||
// which could be lower than the server version by 1 minor version. | ||
if currentEtcdVer.LessThan(ver) || ver.LessThan(prevEtcdVer) { | ||
return fmt.Errorf("expect backend schema version to be between [%s, %s], but got %s", prevEtcdVer.String(), currentEtcdVer.String(), ver.String()) | ||
} | ||
// storage schema is generally backward compatible. No need to check the buckets for higher version. | ||
if ep.cfg.ExecPath == BinPath.Etcd { | ||
return nil | ||
} | ||
lg.Info("verify no new storage schema field is present in the db file of last release process") | ||
nextEtcdVer := semver.Version{Major: currentEtcdVer.Major, Minor: currentEtcdVer.Minor + 1} | ||
newFields := schema.NewFieldsForVersion(nextEtcdVer) | ||
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. Validating consistency of schema should be a feature of schema package. Same as bbolt consistency check, schema should be able to confirm that fields presents and their values are consistent with storage version value. |
||
for _, f := range newFields { | ||
_, vs := be.BatchTx().UnsafeRange(f.Bucket, f.FieldName, nil, 1) | ||
if len(vs) != 0 { | ||
return fmt.Errorf("expect %s not exist in the %s bucket, but got %s", f.Bucket.Name(), f.FieldName, vs[0]) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (ep *EtcdServerProcess) waitReady(ctx context.Context) error { | ||
defer close(ep.donec) | ||
return WaitReadyExpectProc(ctx, ep.proc, EtcdServerReadyLines) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should panic/error if user calls with invalid version.