1// Copyright 2016 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package e2e
16
17import (
18	"os"
19	"strings"
20	"testing"
21	"time"
22
23	"go.etcd.io/etcd/pkg/expect"
24)
25
26func TestCtlV3Elect(t *testing.T) {
27	oldenv := os.Getenv("EXPECT_DEBUG")
28	defer os.Setenv("EXPECT_DEBUG", oldenv)
29	os.Setenv("EXPECT_DEBUG", "1")
30
31	testCtl(t, testElect)
32}
33
34func testElect(cx ctlCtx) {
35	name := "a"
36
37	holder, ch, err := ctlV3Elect(cx, name, "p1")
38	if err != nil {
39		cx.t.Fatal(err)
40	}
41
42	l1 := ""
43	select {
44	case <-time.After(2 * time.Second):
45		cx.t.Fatalf("timed out electing")
46	case l1 = <-ch:
47		if !strings.HasPrefix(l1, name) {
48			cx.t.Errorf("got %q, expected %q prefix", l1, name)
49		}
50	}
51
52	// blocked process that won't win the election
53	blocked, ch, err := ctlV3Elect(cx, name, "p2")
54	if err != nil {
55		cx.t.Fatal(err)
56	}
57	select {
58	case <-time.After(100 * time.Millisecond):
59	case <-ch:
60		cx.t.Fatalf("should block")
61	}
62
63	// overlap with a blocker that will win the election
64	blockAcquire, ch, err := ctlV3Elect(cx, name, "p2")
65	if err != nil {
66		cx.t.Fatal(err)
67	}
68	defer blockAcquire.Stop()
69	select {
70	case <-time.After(100 * time.Millisecond):
71	case <-ch:
72		cx.t.Fatalf("should block")
73	}
74
75	// kill blocked process with clean shutdown
76	if err = blocked.Signal(os.Interrupt); err != nil {
77		cx.t.Fatal(err)
78	}
79	if err = closeWithTimeout(blocked, time.Second); err != nil {
80		cx.t.Fatal(err)
81	}
82
83	// kill the holder with clean shutdown
84	if err = holder.Signal(os.Interrupt); err != nil {
85		cx.t.Fatal(err)
86	}
87	if err = closeWithTimeout(holder, time.Second); err != nil {
88		cx.t.Fatal(err)
89	}
90
91	// blockAcquire should win the election
92	select {
93	case <-time.After(time.Second):
94		cx.t.Fatalf("timed out from waiting to holding")
95	case l2 := <-ch:
96		if l1 == l2 || !strings.HasPrefix(l2, name) {
97			cx.t.Fatalf("expected different elect name, got l1=%q, l2=%q", l1, l2)
98		}
99	}
100}
101
102// ctlV3Elect creates a elect process with a channel listening for when it wins the election.
103func ctlV3Elect(cx ctlCtx, name, proposal string) (*expect.ExpectProcess, <-chan string, error) {
104	cmdArgs := append(cx.PrefixArgs(), "elect", name, proposal)
105	proc, err := spawnCmd(cmdArgs)
106	outc := make(chan string, 1)
107	if err != nil {
108		close(outc)
109		return proc, outc, err
110	}
111	go func() {
112		s, xerr := proc.ExpectFunc(func(string) bool { return true })
113		if xerr != nil {
114			cx.t.Errorf("expect failed (%v)", xerr)
115		}
116		outc <- s
117	}()
118	return proc, outc, err
119}
120