server: Implement schema migrations

This commit is contained in:
Marek Siarkowicz
2021-08-18 17:36:30 +02:00
parent 9d81dde082
commit 0d15ff57e6
10 changed files with 940 additions and 94 deletions

View File

@ -18,49 +18,92 @@ import (
"fmt"
"github.com/coreos/go-semver/semver"
"go.etcd.io/etcd/api/v3/version"
"go.uber.org/zap"
"go.etcd.io/etcd/server/v3/storage/backend"
)
var (
V3_5 = semver.Version{Major: 3, Minor: 5}
V3_6 = semver.Version{Major: 3, Minor: 6}
V3_5 = semver.Version{Major: 3, Minor: 5}
V3_6 = semver.Version{Major: 3, Minor: 6}
currentVersion semver.Version
)
// UpdateStorageSchema updates storage version.
func init() {
v := semver.New(version.Version)
currentVersion = semver.Version{Major: v.Major, Minor: v.Minor}
}
// UpdateStorageSchema updates storage schema to etcd binary version.
func UpdateStorageSchema(lg *zap.Logger, tx backend.BatchTx) error {
return Migrate(lg, tx, currentVersion)
}
// Migrate updates storage schema to provided target version.
func Migrate(lg *zap.Logger, tx backend.BatchTx, target semver.Version) error {
tx.Lock()
defer tx.Unlock()
v, err := DetectSchemaVersion(lg, tx)
current, err := UnsafeDetectSchemaVersion(lg, tx)
if err != nil {
return fmt.Errorf("cannot determine storage version: %w", err)
}
switch *v {
case V3_5:
lg.Warn("setting storage version", zap.String("storage-version", V3_6.String()))
// All meta keys introduced in v3.6 should be filled in here.
UnsafeSetStorageVersion(tx, &V3_6)
case V3_6:
default:
lg.Warn("unknown storage version", zap.String("storage-version", v.String()))
plan, err := newPlan(lg, current, target)
if err != nil {
return fmt.Errorf("cannot create migration plan: %w", err)
}
return nil
return plan.unsafeExecute(lg, tx)
}
func DetectSchemaVersion(lg *zap.Logger, tx backend.ReadTx) (*semver.Version, error) {
v := UnsafeReadStorageVersion(tx)
if v != nil {
return v, nil
// DetectSchemaVersion returns version of storage schema. Returned value depends on etcd version that created the backend. For
// * v3.6 and newer will return storage version.
// * v3.5 will return it's version if it includes all storage fields added in v3.5 (might require a snapshot).
// * v3.4 and older is not supported and will return error.
func DetectSchemaVersion(lg *zap.Logger, tx backend.ReadTx) (v semver.Version, err error) {
tx.Lock()
defer tx.Unlock()
return UnsafeDetectSchemaVersion(lg, tx)
}
// UnsafeDetectSchemaVersion non thread safe version of DetectSchemaVersion.
func UnsafeDetectSchemaVersion(lg *zap.Logger, tx backend.ReadTx) (v semver.Version, err error) {
vp := UnsafeReadStorageVersion(tx)
if vp != nil {
return *vp, nil
}
confstate := UnsafeConfStateFromBackend(lg, tx)
if confstate == nil {
return nil, fmt.Errorf("missing confstate information")
return v, fmt.Errorf("missing confstate information")
}
_, term := UnsafeReadConsistentIndex(tx)
if term == 0 {
return nil, fmt.Errorf("missing term information")
return v, fmt.Errorf("missing term information")
}
copied := V3_5
return &copied, nil
return V3_5, nil
}
func schemaChangesForVersion(v semver.Version, isUpgrade bool) ([]schemaChange, error) {
// changes should be taken from higher version
if isUpgrade {
v = semver.Version{Major: v.Major, Minor: v.Minor + 1}
}
actions, found := schemaChanges[v]
if !found {
return nil, fmt.Errorf("version %q is not supported", v.String())
}
return actions, nil
}
var (
// schemaChanges list changes that were introduced in perticular version.
// schema was introduced in v3.6 as so its changes were not tracked before.
schemaChanges = map[semver.Version][]schemaChange{
V3_6: {
addNewField(Meta, MetaStorageVersionName, emptyStorageVersion),
},
}
// 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 reuselogic to remove it when downgrading to v3.5
emptyStorageVersion = []byte("")
)