Skip to content

Commit

Permalink
server: add fields to migrate from 3.5 to 3.4
Browse files Browse the repository at this point in the history
Signed-off-by: Bogdan Kanivets <bkanivets@apple.com>
  • Loading branch information
Bogdan Kanivets committed Jun 21, 2023
1 parent 5773d94 commit 89a5dbf
Show file tree
Hide file tree
Showing 12 changed files with 392 additions and 146 deletions.
4 changes: 2 additions & 2 deletions etcdutl/etcdutl/migrate_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ func (o *migrateOptions) Config() (*migrateConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse target version: %v", err)
}
if c.targetVersion.LessThan(version.V3_5) {
return nil, fmt.Errorf(`target version %q not supported. Minimal "3.5"`, storageVersionToString(c.targetVersion))
if c.targetVersion.LessThan(version.V3_4) {
return nil, fmt.Errorf(`target version %q not supported. Minimal "3.4"`, storageVersionToString(c.targetVersion))
}

dbPath := datadir.ToBackendFileName(o.dataDir)
Expand Down
38 changes: 28 additions & 10 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -559,22 +559,42 @@ function dep_pass {

function release_pass {
rm -f ./bin/etcd-last-release
mkdir -p ./bin
# to grab latest patch release; bump this up for every minor release
UPGRADE_VER=$(git tag -l --sort=-version:refname "v3.5.*" | head -1 | cut -d- -f1)
if [ -n "${MANUAL_VER:-}" ]; then
UPGRADE_VER_LAST=$(git tag -l --sort=-version:refname "v3.5.*" | head -1 | cut -d- -f1)
if [ -n "${MANUAL_VER_LAST:-}" ]; then
# in case, we need to test against different version
UPGRADE_VER=$MANUAL_VER
UPGRADE_VER_LAST=$MANUAL_VER_LAST
fi
if [[ -z ${UPGRADE_VER} ]]; then
UPGRADE_VER="v3.5.0"
log_warning "fallback to" ${UPGRADE_VER}
if [[ -z ${UPGRADE_VER_LAST} ]]; then
UPGRADE_VER_LAST="v3.5.0"
log_warning "fallback to" ${UPGRADE_VER_LAST}
fi

local file="etcd-$UPGRADE_VER-linux-$GOARCH.tar.gz"
download_etcd_ver_to_tem ${UPGRADE_VER_LAST}
mv /tmp/etcd ./bin/etcd-last-release

UPGRADE_VER_BEFORE_LAST=$(git tag -l --sort=-version:refname "v3.4.*" | head -1 | cut -d- -f1)
if [ -n "${MANUAL_VER_BEFORE_LAST:-}" ]; then
# in case, we need to test against different version
UPGRADE_VER_BEFORE_LAST=$MANUAL_VER_BEFORE_LAST
fi
if [[ -z ${UPGRADE_VER_BEFORE_LAST} ]]; then
UPGRADE_VER_BEFORE_LAST="v3.4.0"
log_warning "fallback to" ${UPGRADE_VER_BEFORE_LAST}
fi

download_etcd_ver_to_tem ${UPGRADE_VER_BEFORE_LAST}
mv /tmp/etcd ./bin/etcd-before-last-release
}

function download_etcd_ver_to_tem {
local version="$1"
local file="etcd-$version-linux-$GOARCH.tar.gz"
log_callout "Downloading $file"

set +e
curl --fail -L "https://github.com/etcd-io/etcd/releases/download/$UPGRADE_VER/$file" -o "/tmp/$file"
curl --fail -L "https://github.com/etcd-io/etcd/releases/download/$version/$file" -o "/tmp/$file"
local result=$?
set -e
case $result in
Expand All @@ -585,8 +605,6 @@ function release_pass {
esac

tar xzvf "/tmp/$file" -C /tmp/ --strip-components=1
mkdir -p ./bin
mv /tmp/etcd ./bin/etcd-last-release
}

function mod_tidy_for_module {
Expand Down
23 changes: 13 additions & 10 deletions server/storage/schema/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,20 @@ func (b bucket) String() string { return string(b.Name()) }
func (b bucket) IsSafeRangeBucket() bool { return b.safeRangeBucket }

var (
// Pre v3.5
ScheduledCompactKeyName = []byte("scheduledCompactRev")
FinishedCompactKeyName = []byte("finishedCompactRev")
MetaConsistentIndexKeyName = []byte("consistent_index")
AuthEnabledKeyName = []byte("authEnabled")
AuthRevisionKeyName = []byte("authRevision")
// Since v3.5
MetaTermKeyName = []byte("term")
MetaConfStateName = []byte("confState")
// Pre v3.4
ScheduledCompactKeyName = []byte("scheduledCompactRev")
FinishedCompactKeyName = []byte("finishedCompactRev")
MetaConsistentIndexKeyName = []byte("consistent_index")
AuthEnabledKeyName = []byte("authEnabled")
AuthRevisionKeyName = []byte("authRevision")
ClusterClusterVersionKeyName = []byte("clusterVersion")
ClusterDowngradeKeyName = []byte("downgrade")

// No new keys were added in v3.4

// Since v3.5
MetaTermKeyName = []byte("term")
MetaConfStateName = []byte("confState")
ClusterDowngradeKeyName = []byte("downgrade")
// Since v3.6
MetaStorageVersionName = []byte("storageVersion")
// Before adding new meta key please update server/etcdserver/version
Expand Down
16 changes: 16 additions & 0 deletions server/storage/schema/confstate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ func TestMustUnsafeSaveConfStateToBackend(t *testing.T) {
assert.Nil(t, UnsafeConfStateFromBackend(lg, tx))
})

emptyConfState := raftpb.ConfState{}
t.Run("save empty", func(t *testing.T) {
tx := be.BatchTx()
tx.Lock()
MustUnsafeSaveConfStateToBackend(lg, tx, &emptyConfState)
tx.Unlock()
tx.Commit()
})

t.Run("read empty", func(t *testing.T) {
tx := be.ReadTx()
tx.Lock()
defer tx.Unlock()
assert.Equal(t, emptyConfState, *UnsafeConfStateFromBackend(lg, tx))
})

confState := raftpb.ConfState{Learners: []uint64{1, 2}, Voters: []uint64{3}, AutoLeave: false}

t.Run("save", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion server/storage/schema/membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func mustParseMemberIDFromBytes(lg *zap.Logger, key []byte) types.ID {
}

// ClusterVersionFromBackend reads cluster version from backend.
// The field is populated since etcd v3.5.
// The field is populated since etcd v3.4.
func (s *membershipBackend) ClusterVersionFromBackend() *semver.Version {
ckey := ClusterClusterVersionKeyName
tx := s.be.ReadTx()
Expand Down
43 changes: 43 additions & 0 deletions server/storage/schema/membership_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 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 (
"github.com/stretchr/testify/assert"
"go.etcd.io/etcd/api/v3/version"
serverversion "go.etcd.io/etcd/server/v3/etcdserver/version"
betesting "go.etcd.io/etcd/server/v3/storage/backend/testing"
"go.uber.org/zap/zaptest"
"testing"
)

func TestDowngradeInfoFromBackend(t *testing.T) {
lg := zaptest.NewLogger(t)
be, _ := betesting.NewDefaultTmpBackend(t)
defer betesting.Close(t, be)

mbe := NewMembershipBackend(lg, be)

mbe.MustCreateBackendBuckets()
mbe.be.ForceCommit()
assert.Nil(t, mbe.DowngradeInfoFromBackend())

dinfo := &serverversion.DowngradeInfo{Enabled: true, TargetVersion: version.V3_5.String()}
mbe.MustSaveDowngradeToBackend(dinfo)

info := mbe.DowngradeInfoFromBackend()

assert.Equal(t, dinfo, info)
}
39 changes: 29 additions & 10 deletions server/storage/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
package schema

import (
"encoding/json"
"fmt"

"github.com/coreos/go-semver/semver"
serverversion "go.etcd.io/etcd/server/v3/etcdserver/version"
"go.etcd.io/raft/v3/raftpb"
"go.uber.org/zap"

"go.etcd.io/etcd/api/v3/version"
Expand Down Expand Up @@ -83,7 +85,7 @@ func UnsafeMigrate(lg *zap.Logger, tx backend.BatchTx, w WALVersion, target semv
// 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.
// * v3.4 will return it's version if it doesn't include all storage fields added in v3.5.
func DetectSchemaVersion(lg *zap.Logger, tx backend.ReadTx) (v semver.Version, err error) {
tx.RLock()
defer tx.RUnlock()
Expand All @@ -96,12 +98,18 @@ func UnsafeDetectSchemaVersion(lg *zap.Logger, tx backend.ReadTx) (v semver.Vers
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 {
if confstate == nil && term == 0 {
// if both confstate and term are missing, assume it's v3.4
return version.V3_4, nil
} else if confstate == nil {
return v, fmt.Errorf("missing confstate information")
} else if len(confstate.Voters) == 0 && term == 0 {
// if confstate is empty and term is missing, assume it's v3.5 that was migrated from v3.4 and never started
return version.V3_5, nil
} else if term == 0 {
return v, fmt.Errorf("missing term information")
}
return version.V3_5, nil
Expand Down Expand Up @@ -129,10 +137,21 @@ var (
// 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),
// emptyValue is used for v3.6 Step for the first time, in all other version StoragetVersion should be set by migrator.
addNewField(Meta, MetaStorageVersionName, emptyValue),
},
version.V3_5: {
// UnsafeReadConsistentIndex will fail on []byte(""), use 0 as default
addNewField(Meta, MetaTermKeyName, emptyTerm),
// UnsafeConfStateFromBackend will fail on []byte(""), use empty struct as default
addNewField(Meta, MetaConfStateName, emptyConfState),
// DowngradeInfoFromBackend will fail on []byte(""), false is better default
addNewField(Cluster, ClusterDowngradeKeyName, falseDowngradeInfo),
},
}
// 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("")

emptyValue = []byte("")
emptyTerm = make([]byte, 8)
emptyConfState, _ = json.Marshal(raftpb.ConfState{})
falseDowngradeInfo, _ = json.Marshal(serverversion.DowngradeInfo{Enabled: false, TargetVersion: ""})
)
Loading

0 comments on commit 89a5dbf

Please sign in to comment.