Skip to content

Commit

Permalink
server: Integrate version validation logic into tests
Browse files Browse the repository at this point in the history
  • Loading branch information
serathius committed Oct 7, 2021
1 parent 1716f5c commit 5e08bdf
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 29 deletions.
6 changes: 4 additions & 2 deletions server/etcdserver/api/membership/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ func (c *RaftCluster) Recover(onSet func(*zap.Logger, *semver.Version)) {
if c.downgradeInfo != nil {
d = &serverversion.DowngradeInfo{Enabled: c.downgradeInfo.Enabled, TargetVersion: c.downgradeInfo.TargetVersion}
}
serverversion.MustDetectDowngrade(c.lg, c.version, d)
sv := semver.Must(semver.NewVersion(version.Version))
serverversion.MustDetectDowngrade(c.lg, sv, c.version, d)
onSet(c.lg, c.version)

for _, m := range c.members {
Expand Down Expand Up @@ -541,7 +542,8 @@ func (c *RaftCluster) SetVersion(ver *semver.Version, onSet func(*zap.Logger, *s
}
oldVer := c.version
c.version = ver
serverversion.MustDetectDowngrade(c.lg, c.version, c.downgradeInfo)
sv := semver.Must(semver.NewVersion(version.Version))
serverversion.MustDetectDowngrade(c.lg, sv, c.version, c.downgradeInfo)
if c.v2store != nil {
mustSaveClusterVersionToStore(c.lg, c.v2store, ver)
}
Expand Down
19 changes: 9 additions & 10 deletions server/etcdserver/version/downgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,47 +34,46 @@ func (d *DowngradeInfo) GetTargetVersion() *semver.Version {

// isValidDowngrade verifies whether the cluster can be downgraded from verFrom to verTo
func isValidDowngrade(verFrom *semver.Version, verTo *semver.Version) bool {
return verTo.Equal(*AllowedDowngradeVersion(verFrom))
return verTo.Equal(*allowedDowngradeVersion(verFrom))
}

// MustDetectDowngrade will detect unexpected downgrade when the local server is recovered.
func MustDetectDowngrade(lg *zap.Logger, cv *semver.Version, d *DowngradeInfo) {
lv := semver.Must(semver.NewVersion(version.Version))
func MustDetectDowngrade(lg *zap.Logger, sv, cv *semver.Version, d *DowngradeInfo) {
// only keep major.minor version for comparison against cluster version
lv = &semver.Version{Major: lv.Major, Minor: lv.Minor}
sv = &semver.Version{Major: sv.Major, Minor: sv.Minor}

// if the cluster enables downgrade, check local version against downgrade target version.
if d != nil && d.Enabled && d.TargetVersion != "" {
if lv.Equal(*d.GetTargetVersion()) {
if sv.Equal(*d.GetTargetVersion()) {
if cv != nil {
lg.Info(
"cluster is downgrading to target version",
zap.String("target-cluster-version", d.TargetVersion),
zap.String("determined-cluster-version", version.Cluster(cv.String())),
zap.String("current-server-version", version.Version),
zap.String("current-server-version", sv.String()),
)
}
return
}
lg.Panic(
"invalid downgrade; server version is not allowed to join when downgrade is enabled",
zap.String("current-server-version", version.Version),
zap.String("current-server-version", sv.String()),
zap.String("target-cluster-version", d.TargetVersion),
)
}

// if the cluster disables downgrade, check local version against determined cluster version.
// the validation passes when local version is not less than cluster version
if cv != nil && lv.LessThan(*cv) {
if cv != nil && sv.LessThan(*cv) {
lg.Panic(
"invalid downgrade; server version is lower than determined cluster version",
zap.String("current-server-version", version.Version),
zap.String("current-server-version", sv.String()),
zap.String("determined-cluster-version", version.Cluster(cv.String())),
)
}
}

func AllowedDowngradeVersion(ver *semver.Version) *semver.Version {
func allowedDowngradeVersion(ver *semver.Version) *semver.Version {
// Todo: handle the case that downgrading from higher major version(e.g. downgrade from v4.0 to v3.x)
return &semver.Version{Major: ver.Major, Minor: ver.Minor - 1}
}
Expand Down
7 changes: 4 additions & 3 deletions server/etcdserver/version/downgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ func TestMustDetectDowngrade(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
lg := zaptest.NewLogger(t)
err := tryMustDetectDowngrade(lg, tt.clusterVersion, tt.downgrade)
sv := semver.Must(semver.NewVersion(version.Version))
err := tryMustDetectDowngrade(lg, sv, tt.clusterVersion, tt.downgrade)

if tt.success != (err == nil) {
t.Errorf("Unexpected status, got %q, wanted: %v", err, tt.success)
Expand All @@ -123,11 +124,11 @@ func TestMustDetectDowngrade(t *testing.T) {
}
}

func tryMustDetectDowngrade(lg *zap.Logger, cv *semver.Version, d *DowngradeInfo) (err interface{}) {
func tryMustDetectDowngrade(lg *zap.Logger, sv, cv *semver.Version, d *DowngradeInfo) (err interface{}) {
defer func() {
err = recover()
}()
MustDetectDowngrade(lg, cv, d)
MustDetectDowngrade(lg, sv, cv, d)
return err
}

Expand Down
2 changes: 1 addition & 1 deletion server/etcdserver/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (m *Manager) DowngradeValidate(ctx context.Context, targetVersion *semver.V
return err
}
cv := m.s.GetClusterVersion()
allowedTargetVersion := AllowedDowngradeVersion(cv)
allowedTargetVersion := allowedDowngradeVersion(cv)
if !targetVersion.Equal(*allowedTargetVersion) {
return ErrInvalidDowngradeTargetVersion
}
Expand Down
32 changes: 19 additions & 13 deletions server/etcdserver/version/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/coreos/go-semver/semver"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"

"go.etcd.io/etcd/api/v3/version"
Expand All @@ -32,21 +33,23 @@ var (
)

func TestUpgradeSingleNode(t *testing.T) {
cluster := newCluster(1, V3_6)
lg := zaptest.NewLogger(t)
cluster := newCluster(lg, 1, V3_6)
stepMonitors(t, cluster)
assert.Equal(t, newCluster(1, V3_6), cluster)
assert.Equal(t, newCluster(lg, 1, V3_6), cluster)

cluster.ReplaceMemberBinary(0, V3_7)
stepMonitors(t, cluster)
stepMonitors(t, cluster)

assert.Equal(t, newCluster(1, V3_7), cluster)
assert.Equal(t, newCluster(lg, 1, V3_7), cluster)
}

func TestUpgradeThreeNodes(t *testing.T) {
cluster := newCluster(3, V3_6)
lg := zaptest.NewLogger(t)
cluster := newCluster(lg, 3, V3_6)
stepMonitors(t, cluster)
assert.Equal(t, newCluster(3, V3_6), cluster)
assert.Equal(t, newCluster(lg, 3, V3_6), cluster)

cluster.ReplaceMemberBinary(0, V3_7)
stepMonitors(t, cluster)
Expand All @@ -56,11 +59,12 @@ func TestUpgradeThreeNodes(t *testing.T) {
stepMonitors(t, cluster)
stepMonitors(t, cluster)

assert.Equal(t, newCluster(3, V3_7), cluster)
assert.Equal(t, newCluster(lg, 3, V3_7), cluster)
}

func newCluster(memberCount int, ver semver.Version) *clusterMock {
func newCluster(lg *zap.Logger, memberCount int, ver semver.Version) *clusterMock {
cluster := &clusterMock{
lg: lg,
clusterVersion: ver,
members: make([]*memberMock, 0, memberCount),
}
Expand Down Expand Up @@ -95,13 +99,14 @@ func stepMonitors(t *testing.T, cluster *clusterMock) {
}

type clusterMock struct {
lg *zap.Logger
clusterVersion semver.Version
downgradeInfo *DowngradeInfo
members []*memberMock
}

func (c *clusterMock) DowngradeEnable(ver semver.Version) {
c.downgradeInfo = &DowngradeInfo{TargetVersion: ver.String(), Enabled: true}
func (c *clusterMock) Version() *Manager {
return NewManager(c.lg, c.members[0])
}

func (c *clusterMock) MembersVersions() map[string]*version.Versions {
Expand All @@ -116,9 +121,7 @@ func (c *clusterMock) MembersVersions() map[string]*version.Versions {
}

func (c *clusterMock) ReplaceMemberBinary(mid int, newServerVersion semver.Version) {
if newServerVersion.LessThan(c.clusterVersion) {
panic("Members cannot join clusters with higher version")
}
MustDetectDowngrade(c.lg, &c.members[mid].serverVersion, &c.clusterVersion, c.downgradeInfo)
c.members[mid].serverVersion = newServerVersion
}

Expand All @@ -141,7 +144,10 @@ func (m *memberMock) LinearizableReadNotify(ctx context.Context) error {
}

func (m *memberMock) DowngradeEnable(ctx context.Context, targetVersion *semver.Version) error {
m.cluster.downgradeInfo = nil
m.cluster.downgradeInfo = &DowngradeInfo{
TargetVersion: targetVersion.String(),
Enabled: true,
}
return nil
}

Expand Down

0 comments on commit 5e08bdf

Please sign in to comment.