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	"github.com/coreos/etcd/clientv3"
25	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
26	"github.com/coreos/etcd/integration"
27	"github.com/coreos/etcd/pkg/testutil"
28	"github.com/coreos/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.pem",
42		CertFile:       "../../integration/fixtures-expired/server.pem",
43		TrustedCAFile:  "../../integration/fixtures-expired/etcd-root-ca.pem",
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	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
159	defer cancel()
160	if _, err := cli.Get(ctx, "foo"); err != nil {
161		t.Fatal(err)
162	}
163}
164
165func TestRejectOldCluster(t *testing.T) {
166	defer testutil.AfterTest(t)
167	// 2 endpoints to test multi-endpoint Status
168	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2, SkipCreatingClient: true})
169	defer clus.Terminate(t)
170
171	cfg := clientv3.Config{
172		Endpoints:        []string{clus.Members[0].GRPCAddr(), clus.Members[1].GRPCAddr()},
173		DialTimeout:      5 * time.Second,
174		DialOptions:      []grpc.DialOption{grpc.WithBlock()},
175		RejectOldCluster: true,
176	}
177	cli, err := clientv3.New(cfg)
178	if err != nil {
179		t.Fatal(err)
180	}
181	cli.Close()
182}
183
184// TestDialForeignEndpoint checks an endpoint that is not registered
185// with the balancer can be dialed.
186func TestDialForeignEndpoint(t *testing.T) {
187	defer testutil.AfterTest(t)
188	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2})
189	defer clus.Terminate(t)
190
191	conn, err := clus.Client(0).Dial(clus.Client(1).Endpoints()[0])
192	if err != nil {
193		t.Fatal(err)
194	}
195	defer conn.Close()
196
197	// grpc can return a lazy connection that's not connected yet; confirm
198	// that it can communicate with the cluster.
199	kvc := clientv3.NewKVFromKVClient(pb.NewKVClient(conn), clus.Client(0))
200	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
201	defer cancel()
202	if _, gerr := kvc.Get(ctx, "abc"); gerr != nil {
203		t.Fatal(err)
204	}
205}
206
207// TestSetEndpointAndPut checks that a Put following a SetEndpoints
208// to a working endpoint will always succeed.
209func TestSetEndpointAndPut(t *testing.T) {
210	defer testutil.AfterTest(t)
211	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2})
212	defer clus.Terminate(t)
213
214	clus.Client(1).SetEndpoints(clus.Members[0].GRPCAddr())
215	_, err := clus.Client(1).Put(context.TODO(), "foo", "bar")
216	if err != nil && !strings.Contains(err.Error(), "closing") {
217		t.Fatal(err)
218	}
219}
220