diff --git a/etcdctl/README.md b/etcdctl/README.md index 40e93c4d1c0..3589f4d699d 100644 --- a/etcdctl/README.md +++ b/etcdctl/README.md @@ -1119,7 +1119,7 @@ DOWNGRADE ENABLE starts a downgrade action to cluster. Downgrade enable success, cluster version 3.6 ``` -### DOWNGRADE CANCEL \ +### DOWNGRADE CANCEL DOWNGRADE CANCEL cancels the ongoing downgrade action to cluster. diff --git a/tests/e2e/cluster_downgrade_test.go b/tests/e2e/cluster_downgrade_test.go index 086b80894d5..013499a5195 100644 --- a/tests/e2e/cluster_downgrade_test.go +++ b/tests/e2e/cluster_downgrade_test.go @@ -37,23 +37,47 @@ import ( "go.etcd.io/etcd/tests/v3/framework/e2e" ) +type CancellationState int + +const ( + NoCancellation CancellationState = iota + CancelRightBeforeEnable + CancelRightAfterEnable +) + func TestDowngradeUpgradeClusterOf1(t *testing.T) { - testDowngradeUpgrade(t, 1, false) + testDowngradeUpgrade(t, 1, false, NoCancellation) } func TestDowngradeUpgradeClusterOf3(t *testing.T) { - testDowngradeUpgrade(t, 3, false) + testDowngradeUpgrade(t, 3, false, NoCancellation) } func TestDowngradeUpgradeClusterOf1WithSnapshot(t *testing.T) { - testDowngradeUpgrade(t, 1, true) + testDowngradeUpgrade(t, 1, true, NoCancellation) } func TestDowngradeUpgradeClusterOf3WithSnapshot(t *testing.T) { - testDowngradeUpgrade(t, 3, true) + testDowngradeUpgrade(t, 3, true, NoCancellation) +} + +func TestDowngradeCancellationWithoutEnablingClusterOf1(t *testing.T) { + testDowngradeUpgrade(t, 1, false, CancelRightBeforeEnable) +} + +func TestDowngradeCancellationRightAfterEnablingClusterOf1(t *testing.T) { + testDowngradeUpgrade(t, 1, false, CancelRightAfterEnable) } -func testDowngradeUpgrade(t *testing.T, clusterSize int, triggerSnapshot bool) { +func TestDowngradeCancellationWithoutEnablingClusterOf3(t *testing.T) { + testDowngradeUpgrade(t, 3, false, CancelRightBeforeEnable) +} + +func TestDowngradeCancellationRightAfterEnablingClusterOf3(t *testing.T) { + testDowngradeUpgrade(t, 3, false, CancelRightAfterEnable) +} + +func testDowngradeUpgrade(t *testing.T, clusterSize int, triggerSnapshot bool, triggerCancellation CancellationState) { currentEtcdBinary := e2e.BinPath.Etcd lastReleaseBinary := e2e.BinPath.EtcdLastRelease if !fileutil.Exist(lastReleaseBinary) { @@ -107,7 +131,18 @@ func testDowngradeUpgrade(t *testing.T, clusterSize int, triggerSnapshot bool) { require.NoError(t, err) beforeMembers, beforeKV := getMembersAndKeys(t, cc) + if triggerCancellation == CancelRightBeforeEnable { + t.Logf("Cancelling downgrade before enabling") + e2e.DowngradeCancel(t, epc, currentVersion) + return // No need to perform downgrading, end the test here + } e2e.DowngradeEnable(t, epc, lastVersion) + if triggerCancellation == CancelRightAfterEnable { + t.Logf("Cancelling downgrade right after enabling (no node is downgraded yet)") + e2e.DowngradeCancel(t, epc, currentVersion) + return // No need to perform downgrading, end the test here + } + t.Logf("Starting downgrade process to %q", lastVersionStr) e2e.DowngradeUpgradeMembers(t, nil, epc, len(epc.Procs), currentVersion, lastClusterVersion) e2e.AssertProcessLogs(t, leader(t, epc), "the cluster has been downgraded") diff --git a/tests/framework/e2e/downgrade.go b/tests/framework/e2e/downgrade.go index 34c9ca4101c..2be2dcddd66 100644 --- a/tests/framework/e2e/downgrade.go +++ b/tests/framework/e2e/downgrade.go @@ -53,6 +53,26 @@ func DowngradeEnable(t *testing.T, epc *EtcdProcessCluster, ver *semver.Version) t.Log("Cluster is ready for downgrade") } +func DowngradeCancel(t *testing.T, epc *EtcdProcessCluster, ver *semver.Version) { + t.Logf("etcdctl downgrade cancel") + c := epc.Etcdctl() + testutils.ExecuteWithTimeout(t, 20*time.Second, func() { + err := c.DowngradeCancel(context.TODO()) + require.NoError(t, err) + }) + + t.Log("Downgrade cancelled, validating if cluster is in the right state") + for i := 0; i < len(epc.Procs); i++ { + ValidateVersion(t, epc.Cfg, epc.Procs[i], version.Versions{ + Cluster: ver.String(), + Server: ver.String(), + Storage: ver.String(), + }) + } + + t.Log("Cluster downgrade cancellation is completed") +} + func DowngradeUpgradeMembers(t *testing.T, lg *zap.Logger, clus *EtcdProcessCluster, numberOfMembersToChange int, currentVersion, targetVersion *semver.Version) error { if lg == nil { lg = clus.lg diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index d0c8dc14c72..9235ab28d85 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -86,6 +86,11 @@ func (ctl *EtcdctlV3) DowngradeEnable(ctx context.Context, version string) error return err } +func (ctl *EtcdctlV3) DowngradeCancel(ctx context.Context) error { + _, err := SpawnWithExpectLines(ctx, ctl.cmdArgs("downgrade", "cancel"), nil, expect.ExpectedResponse{Value: "Downgrade cancel success"}) + return err +} + func (ctl *EtcdctlV3) Get(ctx context.Context, key string, o config.GetOptions) (*clientv3.GetResponse, error) { resp := clientv3.GetResponse{} var args []string