Repository: etcd-io/etcd
File: tests/e2e/reproduce_20967_util_test.go (Go)

Code before:

		require.NoError(t, restoreSnapshotIntoMemberDir(t, v3439ctl, snapFile, initialCluster, proc),
			"failed to restore snapshot for member %s", serverCfg.Name)
	}

	t.Log("Restarting all members from restored data dirs...")
	require.NoError(t, epc.RollingStart())

	t.Log("Ensuring zombie members exist in v3store...")
	require.NoError(t, epc.Stop())
	for _, proc := range epc.Procs {
		ensureZombieMembers(t, proc.Config().DataDirPath)
	}
	require.NoError(t, epc.RollingStart())
	epc.WaitLeader(t)
	return epc
}

// ensureZombieMembers checks that there are 5 members in the v3store.
func ensureZombieMembers(t *testing.T, dataDir string) {
	dbPath := datadir.ToBackendFileName(dataDir)
	db, err := bbolt.Open(dbPath, 0400, &bbolt.Options{ReadOnly: true})
	require.NoError(t, err)
	defer func() {
		require.NoError(t, db.Close())
	}()

	count := 0
	members := map[string]int{}
	_ = db.View(func(tx *bbolt.Tx) error {
		return tx.Bucket(buckets.Members.Name()).ForEach(func(_, v []byte) error {
			m := membership.Member{}
			err := json.Unmarshal(v, &m)
			require.NoError(t, err)

			key := fmt.Sprintf("name=%s,peerURLs=%v,isLearner=%v", m.Name, m.PeerURLs, m.IsLearner)
			members[key]++

			count++
			return nil
		})
	})
	require.Equal(t, 5, count, "expected 5 members in v3store")
	require.Equal(t, 3, len(members), "expected 3 member names in v3store")
}

// restoreSnapshotIntoMemberDir restores snapshot into the given member's data dir.
func restoreSnapshotIntoMemberDir(t *testing.T, etcdctlBin string, snapshotPath string, initialCluster string, proc e2e.EtcdProcess) error {
	serverCfg := proc.Config()
	require.NoError(t, os.RemoveAll(serverCfg.DataDirPath), "failed to remove data dir %s", serverCfg.DataDirPath)


Review comment (suggestion):
We should add such function into `tests/framework/e2e/etcdctl.go`. I won't insist on it since it's stable releases, as we won't add any new features, so I don't expect any major refatoring.

Code after:
	require.NoError(t, err)
	defer func() {
		require.NoError(t, db.Close())
	}()

	count := 0
	members := map[string]int{}
	_ = db.View(func(tx *bbolt.Tx) error {
		return tx.Bucket(buckets.Members.Name()).ForEach(func(_, v []byte) error {
			m := membership.Member{}
			err := json.Unmarshal(v, &m)
			require.NoError(t, err)

			key := fmt.Sprintf("name=%s,peerURLs=%v,isLearner=%v", m.Name, m.PeerURLs, m.IsLearner)
			members[key]++

			count++
			return nil
		})
	})
	require.Equal(t, 6, count, "expected 6 members in v3store")
	require.Equal(t, 3, len(members), "expected 3 member names in v3store")
}

// restoreSnapshotIntoMemberDir restores snapshot into the given member's data dir.
func restoreSnapshotIntoMemberDir(t *testing.T, etcdctlBin string, snapshotPath string, initialCluster string, proc e2e.EtcdProcess) error {
	serverCfg := proc.Config()
	require.NoError(t, os.RemoveAll(serverCfg.DataDirPath), "failed to remove data dir %s", serverCfg.DataDirPath)

	serverCfg.DataDirPath = filepath.Join(t.TempDir(), "restored-"+serverCfg.Name)
	updateServerCfgArgs(serverCfg, "--data-dir", serverCfg.DataDirPath)

	serverCfg.InitialCluster = initialCluster
	updateServerCfgArgs(serverCfg, "--initial-cluster", initialCluster)

	// We can use AddMember to create unique member ID with same cluter name.
	// However, it takes long time to wait for the cluster to be healthy.
	// So, we simply update the initial cluster configuration with different
	// cluster name here. This achieves the same effect for testing zombie
	// members.
	//
	// NOTE: If we use same cluster name without using AddMember before,
	// etcd will generate same member ID and it won't create zombie members.
	serverCfg.InitialToken = "restored"
	updateServerCfgArgs(serverCfg, "--initial-cluster-token", serverCfg.InitialToken)

	ctl := proc.Etcdctl(e2e.ClientNonTLS, false, false)
	args := ctl.GenerateCmdArgs(
		"snapshot", "restore", snapshotPath,
		"--data-dir", serverCfg.DataDirPath,
		"--name", serverCfg.Name,