// Copyright 2020 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 membership import ( "bytes" "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "strconv" "testing" "github.com/coreos/go-semver/semver" "go.etcd.io/etcd/api/v3/version" "go.uber.org/zap" ) func TestMustDetectDowngrade(t *testing.T) { lv := semver.Must(semver.NewVersion(version.Version)) lv = &semver.Version{Major: lv.Major, Minor: lv.Minor} oneMinorHigher := &semver.Version{Major: lv.Major, Minor: lv.Minor + 1} oneMinorLower := &semver.Version{Major: lv.Major, Minor: lv.Minor - 1} downgradeEnabledHigherVersion := &DowngradeInfo{Enabled: true, TargetVersion: oneMinorHigher.String()} downgradeEnabledEqualVersion := &DowngradeInfo{Enabled: true, TargetVersion: lv.String()} downgradeEnabledLowerVersion := &DowngradeInfo{Enabled: true, TargetVersion: oneMinorLower.String()} downgradeDisabled := &DowngradeInfo{Enabled: false} tests := []struct { name string clusterVersion *semver.Version downgrade *DowngradeInfo success bool message string }{ { "Succeeded when downgrade is disabled and cluster version is nil", nil, downgradeDisabled, true, "", }, { "Succeeded when downgrade is disabled and cluster version is one minor lower", oneMinorLower, downgradeDisabled, true, "", }, { "Succeeded when downgrade is disabled and cluster version is server version", lv, downgradeDisabled, true, "", }, { "Failed when downgrade is disabled and server version is lower than determined cluster version ", oneMinorHigher, downgradeDisabled, false, "invalid downgrade; server version is lower than determined cluster version", }, { "Succeeded when downgrade is enabled and cluster version is nil", nil, downgradeEnabledEqualVersion, true, "", }, { "Failed when downgrade is enabled and server version is target version", lv, downgradeEnabledEqualVersion, true, "cluster is downgrading to target version", }, { "Succeeded when downgrade to lower version and server version is cluster version ", lv, downgradeEnabledLowerVersion, false, "invalid downgrade; server version is not allowed to join when downgrade is enabled", }, { "Failed when downgrade is enabled and local version is out of range and cluster version is nil", nil, downgradeEnabledHigherVersion, false, "invalid downgrade; server version is not allowed to join when downgrade is enabled", }, { "Failed when downgrade is enabled and local version is out of range", lv, downgradeEnabledHigherVersion, false, "invalid downgrade; server version is not allowed to join when downgrade is enabled", }, } if os.Getenv("DETECT_DOWNGRADE") != "" { i := os.Getenv("DETECT_DOWNGRADE") iint, _ := strconv.Atoi(i) logPath := filepath.Join(os.TempDir(), fmt.Sprintf("test-log-must-detect-downgrade-%v", iint)) lcfg := zap.NewProductionConfig() lcfg.OutputPaths = []string{logPath} lcfg.ErrorOutputPaths = []string{logPath} lg, _ := lcfg.Build() mustDetectDowngrade(lg, tests[iint].clusterVersion, tests[iint].downgrade) return } for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { logPath := filepath.Join(os.TempDir(), fmt.Sprintf("test-log-must-detect-downgrade-%d", i)) t.Log(logPath) defer os.RemoveAll(logPath) cmd := exec.Command(os.Args[0], "-test.run=TestMustDetectDowngrade") cmd.Env = append(os.Environ(), fmt.Sprintf("DETECT_DOWNGRADE=%d", i)) if err := cmd.Start(); err != nil { t.Fatal(err) } errCmd := cmd.Wait() data, err := ioutil.ReadFile(logPath) if err == nil { if !bytes.Contains(data, []byte(tt.message)) { t.Errorf("Expected to find %v in log", tt.message) } } else { t.Fatal(err) } if !tt.success { e, ok := errCmd.(*exec.ExitError) if !ok || e.Success() { t.Errorf("Expected exit with status 1; Got %v", err) } } if tt.success && errCmd != nil { t.Errorf("Expected not failure; Got %v", errCmd) } }) } } func TestIsValidDowngrade(t *testing.T) { tests := []struct { name string verFrom string verTo string result bool }{ { "Valid downgrade", "3.5.0", "3.4.0", true, }, { "Invalid downgrade", "3.5.2", "3.3.0", false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { res := isValidDowngrade( semver.Must(semver.NewVersion(tt.verFrom)), semver.Must(semver.NewVersion(tt.verTo))) if res != tt.result { t.Errorf("Expected downgrade valid is %v; Got %v", tt.result, res) } }) } }