// Copyright 2016 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package integration import ( "math/rand" "strings" "testing" "time" "github.com/coreos/etcd/clientv3" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/integration" "github.com/coreos/etcd/pkg/testutil" "github.com/coreos/etcd/pkg/transport" "golang.org/x/net/context" ) var ( testTLSInfo = transport.TLSInfo{ KeyFile: "../../integration/fixtures/server.key.insecure", CertFile: "../../integration/fixtures/server.crt", TrustedCAFile: "../../integration/fixtures/ca.crt", ClientCertAuth: true, } testTLSInfoExpired = transport.TLSInfo{ KeyFile: "../../integration/fixtures-expired/server-key.pem", CertFile: "../../integration/fixtures-expired/server.pem", TrustedCAFile: "../../integration/fixtures-expired/etcd-root-ca.pem", ClientCertAuth: true, } ) // TestDialTLSExpired tests client with expired certs fails to dial. func TestDialTLSExpired(t *testing.T) { defer testutil.AfterTest(t) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1, PeerTLS: &testTLSInfo, ClientTLS: &testTLSInfo, SkipCreatingClient: true}) defer clus.Terminate(t) tls, err := testTLSInfoExpired.ClientConfig() if err != nil { t.Fatal(err) } // expect remote errors "tls: bad certificate" _, err = clientv3.New(clientv3.Config{ Endpoints: []string{clus.Members[0].GRPCAddr()}, DialTimeout: 3 * time.Second, TLS: tls, }) if err != context.DeadlineExceeded { t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err) } } // TestDialTLSNoConfig ensures the client fails to dial / times out // when TLS endpoints (https, unixs) are given but no tls config. func TestDialTLSNoConfig(t *testing.T) { defer testutil.AfterTest(t) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1, ClientTLS: &testTLSInfo, SkipCreatingClient: true}) defer clus.Terminate(t) // expect "signed by unknown authority" _, err := clientv3.New(clientv3.Config{ Endpoints: []string{clus.Members[0].GRPCAddr()}, DialTimeout: time.Second, }) if err != context.DeadlineExceeded { t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err) } } // TestDialSetEndpointsBeforeFail ensures SetEndpoints can replace unavailable // endpoints with available ones. func TestDialSetEndpointsBeforeFail(t *testing.T) { testDialSetEndpoints(t, true) } func TestDialSetEndpointsAfterFail(t *testing.T) { testDialSetEndpoints(t, false) } // testDialSetEndpoints ensures SetEndpoints can replace unavailable endpoints with available ones. func testDialSetEndpoints(t *testing.T, setBefore bool) { defer testutil.AfterTest(t) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3, SkipCreatingClient: true}) defer clus.Terminate(t) // get endpoint list eps := make([]string, 3) for i := range eps { eps[i] = clus.Members[i].GRPCAddr() } toKill := rand.Intn(len(eps)) cfg := clientv3.Config{Endpoints: []string{eps[toKill]}, DialTimeout: 1 * time.Second} cli, err := clientv3.New(cfg) if err != nil { t.Fatal(err) } defer cli.Close() if setBefore { cli.SetEndpoints(eps[toKill%3], eps[(toKill+1)%3]) } // make a dead node clus.Members[toKill].Stop(t) clus.WaitLeader(t) if !setBefore { cli.SetEndpoints(eps[toKill%3], eps[(toKill+1)%3]) } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) if _, err = cli.Get(ctx, "foo", clientv3.WithSerializable()); err != nil { t.Fatal(err) } cancel() } // TestSwitchSetEndpoints ensures SetEndpoints can switch one endpoint // with a new one that doesn't include original endpoint. func TestSwitchSetEndpoints(t *testing.T) { defer testutil.AfterTest(t) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) defer clus.Terminate(t) // get non partitioned members endpoints eps := []string{clus.Members[1].GRPCAddr(), clus.Members[2].GRPCAddr()} cli := clus.Client(0) clus.Members[0].InjectPartition(t, clus.Members[1:]...) cli.SetEndpoints(eps...) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if _, err := cli.Get(ctx, "foo"); err != nil { t.Fatal(err) } } func TestRejectOldCluster(t *testing.T) { defer testutil.AfterTest(t) // 2 endpoints to test multi-endpoint Status clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2, SkipCreatingClient: true}) defer clus.Terminate(t) cfg := clientv3.Config{ Endpoints: []string{clus.Members[0].GRPCAddr(), clus.Members[1].GRPCAddr()}, DialTimeout: 5 * time.Second, RejectOldCluster: true, } cli, err := clientv3.New(cfg) if err != nil { t.Fatal(err) } cli.Close() } // TestDialForeignEndpoint checks an endpoint that is not registered // with the balancer can be dialed. func TestDialForeignEndpoint(t *testing.T) { defer testutil.AfterTest(t) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2}) defer clus.Terminate(t) conn, err := clus.Client(0).Dial(clus.Client(1).Endpoints()[0]) if err != nil { t.Fatal(err) } defer conn.Close() // grpc can return a lazy connection that's not connected yet; confirm // that it can communicate with the cluster. kvc := clientv3.NewKVFromKVClient(pb.NewKVClient(conn), clus.Client(0)) ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) defer cancel() if _, gerr := kvc.Get(ctx, "abc"); gerr != nil { t.Fatal(err) } } // TestSetEndpointAndPut checks that a Put following a SetEndpoints // to a working endpoint will always succeed. func TestSetEndpointAndPut(t *testing.T) { defer testutil.AfterTest(t) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2}) defer clus.Terminate(t) clus.Client(1).SetEndpoints(clus.Members[0].GRPCAddr()) _, err := clus.Client(1).Put(context.TODO(), "foo", "bar") if err != nil && !strings.Contains(err.Error(), "closing") { t.Fatal(err) } }