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

Code before:
		})
	}
}

func addMember(ctx context.Context, t *testing.T, epc *e2e.EtcdProcessCluster) {
	_, err := epc.StartNewProc(ctx, nil, t, false /* addAsLearner */)
	require.NoError(t, err)
	require.NoError(t, epc.Procs[len(epc.Procs)-1].Etcdctl().Health(ctx))
}

func addMemberAsLearnerAndPromote(ctx context.Context, t *testing.T, epc *e2e.EtcdProcessCluster) {
	endpoints := epc.EndpointsGRPC()

	id, err := epc.StartNewProc(ctx, nil, t, true /* addAsLearner */)
	require.NoError(t, err)
	_, err = epc.Etcdctl(e2e.WithEndpoints(endpoints)).MemberPromote(ctx, id)
	attempt := 1
	for err != nil && (attempt < 4) && strings.Contains(err.Error(), "can only promote a learner member which is in sync with leader") {
		t.Logf("Learner is not ready yet, retry for the %v time", attempt)
		time.Sleep(100 * time.Duration(attempt) * time.Millisecond)
		_, err = epc.Etcdctl(e2e.WithEndpoints(endpoints)).MemberPromote(ctx, id)
		attempt++
	}
	require.NoError(t, err)

	newLearnerMemberProc := epc.Procs[len(epc.Procs)-1]
	require.NoError(t, newLearnerMemberProc.Etcdctl().Health(ctx))
}

func removeFirstMember(ctx context.Context, t *testing.T, epc *e2e.EtcdProcessCluster) {
	// avoid tearing down the last etcd process
	if len(epc.Procs) == 1 {
		return
	}

	firstProc := epc.Procs[0]
	sts, err := firstProc.Etcdctl().Status(ctx)
	require.NoError(t, err)
	memberIDToRemove := sts[0].Header.MemberId

	epc.Procs = epc.Procs[1:]
	_, err = epc.Etcdctl().MemberRemove(ctx, memberIDToRemove)
	require.NoError(t, err)
	require.NoError(t, firstProc.Stop())
	require.NoError(t, firstProc.Close())
}


Review comment (suggestion):
```suggestion
	attempt := 0
	for attempt < 3 {
		_, err = epc.Etcdctl(e2e.WithEndpoints(endpoints)).MemberPromote(ctx, id)
		if err == nil || !strings.Contains(err.Error(), "can only promote a learner member which is in sync with leader"){
			break
		}
		time.Sleep(100 * time.Millisecond)
		attempt++
	}
```

Code after:
		})
	}
}

func addMember(ctx context.Context, t *testing.T, epc *e2e.EtcdProcessCluster) {
	_, err := epc.StartNewProc(ctx, nil, t, false /* addAsLearner */)
	require.NoError(t, err)
	require.NoError(t, epc.Procs[len(epc.Procs)-1].Etcdctl().Health(ctx))
}

func addMemberAsLearnerAndPromote(ctx context.Context, t *testing.T, epc *e2e.EtcdProcessCluster) {
	endpoints := epc.EndpointsGRPC()

	id, err := epc.StartNewProc(ctx, nil, t, true /* addAsLearner */)
	require.NoError(t, err)
	_, err = epc.Etcdctl(e2e.WithEndpoints(endpoints)).MemberPromote(ctx, id)

	attempt := 0
	for attempt < 3 {
		_, err = epc.Etcdctl(e2e.WithEndpoints(endpoints)).MemberPromote(ctx, id)
		if err == nil || !strings.Contains(err.Error(), "can only promote a learner member which is in sync with leader") {
			break
		}
		time.Sleep(100 * time.Millisecond)
		attempt++
	}
	require.NoError(t, err)

	newLearnerMemberProc := epc.Procs[len(epc.Procs)-1]
	require.NoError(t, newLearnerMemberProc.Etcdctl().Health(ctx))
}

func removeFirstMember(ctx context.Context, t *testing.T, epc *e2e.EtcdProcessCluster) {
	// avoid tearing down the last etcd process
	if len(epc.Procs) == 1 {
		return
	}

	firstProc := epc.Procs[0]
	sts, err := firstProc.Etcdctl().Status(ctx)
	require.NoError(t, err)
	memberIDToRemove := sts[0].Header.MemberId

	epc.Procs = epc.Procs[1:]
	_, err = epc.Etcdctl().MemberRemove(ctx, memberIDToRemove)
	require.NoError(t, err)
	require.NoError(t, firstProc.Stop())
	require.NoError(t, firstProc.Close())
}