mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
139 lines
4.6 KiB
Go
139 lines
4.6 KiB
Go
// Copyright 2021 The etcd Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package schema
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/coreos/go-semver/semver"
|
|
"go.uber.org/zap"
|
|
|
|
"go.etcd.io/etcd/api/v3/version"
|
|
|
|
"go.etcd.io/etcd/server/v3/storage/backend"
|
|
)
|
|
|
|
// Validate checks provided backend to confirm that schema used is supported.
|
|
func Validate(lg *zap.Logger, tx backend.ReadTx) error {
|
|
tx.Lock()
|
|
defer tx.Unlock()
|
|
return unsafeValidate(lg, tx)
|
|
}
|
|
|
|
func unsafeValidate(lg *zap.Logger, tx backend.ReadTx) error {
|
|
current, err := UnsafeDetectSchemaVersion(lg, tx)
|
|
if err != nil {
|
|
// v3.5 requires a wal snapshot to persist its fields, so we can assign it a schema version.
|
|
lg.Warn("Failed to detect storage schema version. Please wait till wal snapshot before upgrading cluster.")
|
|
return nil
|
|
}
|
|
_, err = newPlan(lg, current, localBinaryVersion())
|
|
return err
|
|
}
|
|
|
|
func localBinaryVersion() semver.Version {
|
|
v := semver.New(version.Version)
|
|
return semver.Version{Major: v.Major, Minor: v.Minor}
|
|
}
|
|
|
|
type WALVersion interface {
|
|
// MinimalEtcdVersion returns minimal etcd version able to interpret WAL log.
|
|
MinimalEtcdVersion() *semver.Version
|
|
}
|
|
|
|
// Migrate updates storage schema to provided target version.
|
|
// Downgrading requires that provided WAL doesn't contain unsupported entries.
|
|
func Migrate(lg *zap.Logger, tx backend.BatchTx, w WALVersion, target semver.Version) error {
|
|
tx.LockOutsideApply()
|
|
defer tx.Unlock()
|
|
return UnsafeMigrate(lg, tx, w, target)
|
|
}
|
|
|
|
// UnsafeMigrate is non thread-safe version of Migrate.
|
|
func UnsafeMigrate(lg *zap.Logger, tx backend.BatchTx, w WALVersion, target semver.Version) error {
|
|
current, err := UnsafeDetectSchemaVersion(lg, tx)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot detect storage schema version: %v", err)
|
|
}
|
|
plan, err := newPlan(lg, current, target)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot create migration plan: %v", err)
|
|
}
|
|
if target.LessThan(current) {
|
|
minVersion := w.MinimalEtcdVersion()
|
|
if minVersion != nil && target.LessThan(*minVersion) {
|
|
return fmt.Errorf("cannot downgrade storage, WAL contains newer entries")
|
|
}
|
|
}
|
|
return plan.unsafeExecute(lg, tx)
|
|
}
|
|
|
|
// 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.RLock()
|
|
defer tx.RUnlock()
|
|
return UnsafeDetectSchemaVersion(lg, tx)
|
|
}
|
|
|
|
// UnsafeDetectSchemaVersion non-threadsafe 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 v, fmt.Errorf("missing confstate information")
|
|
}
|
|
_, term := UnsafeReadConsistentIndex(tx)
|
|
if term == 0 {
|
|
return v, fmt.Errorf("missing term information")
|
|
}
|
|
return version.V3_5, nil
|
|
}
|
|
|
|
func schemaChangesForVersion(v semver.Version, isUpgrade bool) ([]schemaChange, error) {
|
|
// changes should be taken from higher version
|
|
var higherV = v
|
|
if isUpgrade {
|
|
higherV = semver.Version{Major: v.Major, Minor: v.Minor + 1}
|
|
}
|
|
|
|
actions, found := schemaChanges[higherV]
|
|
if !found {
|
|
if isUpgrade {
|
|
return nil, fmt.Errorf("version %q is not supported", higherV.String())
|
|
}
|
|
return nil, fmt.Errorf("version %q is not supported", v.String())
|
|
}
|
|
return actions, nil
|
|
}
|
|
|
|
var (
|
|
// 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 = map[semver.Version][]schemaChange{
|
|
version.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 reuse logic to remove it when downgrading to v3.5
|
|
emptyStorageVersion = []byte("")
|
|
)
|