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 integration
16
17import (
18	"context"
19	"math/rand"
20	"strings"
21	"testing"
22	"time"
23
24	"go.etcd.io/etcd/clientv3"
25	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
26	"go.etcd.io/etcd/integration"
27	"go.etcd.io/etcd/pkg/testutil"
28	"go.etcd.io/etcd/pkg/transport"
29	"google.golang.org/grpc"
30)
31
32var (
33	testTLSInfo = transport.TLSInfo{
34		KeyFile:        "../../integration/fixtures/server.key.insecure",
35		CertFile:       "../../integration/fixtures/server.crt",
36		TrustedCAFile:  "../../integration/fixtures/ca.crt",
37		ClientCertAuth: true,
38	}
39
40	testTLSInfoExpired = transport.TLSInfo{
41		KeyFile:        "../../integration/fixtures-expired/server.key.insecure",
42		CertFile:       "../../integration/fixtures-expired/server.crt",
43		TrustedCAFile:  "../../integration/fixtures-expired/ca.crt",
44		ClientCertAuth: true,
45	}
46)
47
48// TestDialTLSExpired tests client with expired certs fails to dial.
49func TestDialTLSExpired(t *testing.T) {
50	defer testutil.AfterTest(t)
51	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1, PeerTLS: &testTLSInfo, ClientTLS: &testTLSInfo, SkipCreatingClient: true})
52	defer clus.Terminate(t)
53
54	tls, err := testTLSInfoExpired.ClientConfig()
55	if err != nil {
56		t.Fatal(err)
57	}
58	// expect remote errors "tls: bad certificate"
59	_, err = clientv3.New(clientv3.Config{
60		Endpoints:   []string{clus.Members[0].GRPCAddr()},
61		DialTimeout: 3 * time.Second,
62		DialOptions: []grpc.DialOption{grpc.WithBlock()},
63		TLS:         tls,
64	})
65	if !isClientTimeout(err) {
66		t.Fatalf("expected dial timeout error, got %v", err)
67	}
68}
69
70// TestDialTLSNoConfig ensures the client fails to dial / times out
71// when TLS endpoints (https, unixs) are given but no tls config.
72func TestDialTLSNoConfig(t *testing.T) {
73	defer testutil.AfterTest(t)
74	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1, ClientTLS: &testTLSInfo, SkipCreatingClient: true})
75	defer clus.Terminate(t)
76	// expect "signed by unknown authority"
77	c, err := clientv3.New(clientv3.Config{
78		Endpoints:   []string{clus.Members[0].GRPCAddr()},
79		DialTimeout: time.Second,
80		DialOptions: []grpc.DialOption{grpc.WithBlock()},
81	})
82	defer func() {
83		if c != nil {
84			c.Close()
85		}
86	}()
87	if !isClientTimeout(err) {
88		t.Fatalf("expected dial timeout error, got %v", err)
89	}
90}
91
92// TestDialSetEndpointsBeforeFail ensures SetEndpoints can replace unavailable
93// endpoints with available ones.
94func TestDialSetEndpointsBeforeFail(t *testing.T) {
95	testDialSetEndpoints(t, true)
96}
97
98func TestDialSetEndpointsAfterFail(t *testing.T) {
99	testDialSetEndpoints(t, false)
100}
101
102// testDialSetEndpoints ensures SetEndpoints can replace unavailable endpoints with available ones.
103func testDialSetEndpoints(t *testing.T, setBefore bool) {
104	defer testutil.AfterTest(t)
105	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3, SkipCreatingClient: true})
106	defer clus.Terminate(t)
107
108	// get endpoint list
109	eps := make([]string, 3)
110	for i := range eps {
111		eps[i] = clus.Members[i].GRPCAddr()
112	}
113	toKill := rand.Intn(len(eps))
114
115	cfg := clientv3.Config{
116		Endpoints:   []string{eps[toKill]},
117		DialTimeout: 1 * time.Second,
118		DialOptions: []grpc.DialOption{grpc.WithBlock()},
119	}
120	cli, err := clientv3.New(cfg)
121	if err != nil {
122		t.Fatal(err)
123	}
124	defer cli.Close()
125
126	if setBefore {
127		cli.SetEndpoints(eps[toKill%3], eps[(toKill+1)%3])
128	}
129	// make a dead node
130	clus.Members[toKill].Stop(t)
131	clus.WaitLeader(t)
132
133	if !setBefore {
134		cli.SetEndpoints(eps[toKill%3], eps[(toKill+1)%3])
135	}
136	time.Sleep(time.Second * 2)
137	ctx, cancel := context.WithTimeout(context.Background(), integration.RequestWaitTimeout)
138	if _, err = cli.Get(ctx, "foo", clientv3.WithSerializable()); err != nil {
139		t.Fatal(err)
140	}
141	cancel()
142}
143
144// TestSwitchSetEndpoints ensures SetEndpoints can switch one endpoint
145// with a new one that doesn't include original endpoint.
146func TestSwitchSetEndpoints(t *testing.T) {
147	defer testutil.AfterTest(t)
148	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
149	defer clus.Terminate(t)
150
151	// get non partitioned members endpoints
152	eps := []string{clus.Members[1].GRPCAddr(), clus.Members[2].GRPCAddr()}
153
154	cli := clus.Client(0)
155	clus.Members[0].InjectPartition(t, clus.Members[1:]...)
156
157	cli.SetEndpoints(eps...)
158
159	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
160	defer cancel()
161	if _, err := cli.Get(ctx, "foo"); err != nil {
162		t.Fatal(err)
163	}
164}
165
166func TestRejectOldCluster(t *testing.T) {
167	defer testutil.AfterTest(t)
168	// 2 endpoints to test multi-endpoint Status
169	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2, SkipCreatingClient: true})
170	defer clus.Terminate(t)
171
172	cfg := clientv3.Config{
173		Endpoints:        []string{clus.Members[0].GRPCAddr(), clus.Members[1].GRPCAddr()},
174		DialTimeout:      5 * time.Second,
175		DialOptions:      []grpc.DialOption{grpc.WithBlock()},
176		RejectOldCluster: true,
177	}
178	cli, err := clientv3.New(cfg)
179	if err != nil {
180		t.Fatal(err)
181	}
182	cli.Close()
183}
184
185// TestDialForeignEndpoint checks an endpoint that is not registered
186// with the balancer can be dialed.
187func TestDialForeignEndpoint(t *testing.T) {
188	defer testutil.AfterTest(t)
189	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2})
190	defer clus.Terminate(t)
191
192	conn, err := clus.Client(0).Dial(clus.Client(1).Endpoints()[0])
193	if err != nil {
194		t.Fatal(err)
195	}
196	defer conn.Close()
197
198	// grpc can return a lazy connection that's not connected yet; confirm
199	// that it can communicate with the cluster.
200	kvc := clientv3.NewKVFromKVClient(pb.NewKVClient(conn), clus.Client(0))
201	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
202	defer cancel()
203	if _, gerr := kvc.Get(ctx, "abc"); gerr != nil {
204		t.Fatal(err)
205	}
206}
207
208// TestSetEndpointAndPut checks that a Put following a SetEndpoints
209// to a working endpoint will always succeed.
210func TestSetEndpointAndPut(t *testing.T) {
211	defer testutil.AfterTest(t)
212	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2})
213	defer clus.Terminate(t)
214
215	clus.Client(1).SetEndpoints(clus.Members[0].GRPCAddr())
216	_, err := clus.Client(1).Put(context.TODO(), "foo", "bar")
217	if err != nil && !strings.Contains(err.Error(), "closing") {
218		t.Fatal(err)
219	}
220}
221