sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/etcd_client_generator_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package internal 18 19 import ( 20 "context" 21 "crypto/tls" 22 "strings" 23 "testing" 24 25 . "github.com/onsi/gomega" 26 "github.com/pkg/errors" 27 "go.etcd.io/etcd/api/v3/etcdserverpb" 28 clientv3 "go.etcd.io/etcd/client/v3" 29 "k8s.io/client-go/rest" 30 31 "sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/etcd" 32 etcdfake "sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/etcd/fake" 33 ) 34 35 var ( 36 subject *EtcdClientGenerator 37 ) 38 39 func TestNewEtcdClientGenerator(t *testing.T) { 40 g := NewWithT(t) 41 subject = NewEtcdClientGenerator(&rest.Config{}, &tls.Config{MinVersion: tls.VersionTLS12}, 0, 0) 42 g.Expect(subject.createClient).To(Not(BeNil())) 43 } 44 45 func TestFirstAvailableNode(t *testing.T) { 46 tests := []struct { 47 name string 48 nodes []string 49 cc clientCreator 50 51 expectedErr string 52 expectedClient etcd.Client 53 }{ 54 { 55 name: "Returns client successfully", 56 nodes: []string{"node-1"}, 57 cc: func(_ context.Context, endpoint string) (*etcd.Client, error) { 58 return &etcd.Client{Endpoint: endpoint}, nil 59 }, 60 expectedClient: etcd.Client{Endpoint: "etcd-node-1"}, 61 }, 62 { 63 name: "Fails when called with an empty node list", 64 nodes: nil, 65 cc: nil, 66 expectedErr: "invalid argument: forLeader can't be called with an empty list of nodes", 67 }, 68 { 69 name: "Returns error from client", 70 nodes: []string{"node-1", "node-2"}, 71 cc: func(context.Context, string) (*etcd.Client, error) { 72 return nil, errors.New("something went wrong") 73 }, 74 expectedErr: "could not establish a connection to any etcd node: something went wrong", 75 }, 76 { 77 name: "Returns client when some of the nodes are down but at least one node is up", 78 nodes: []string{"node-down-1", "node-down-2", "node-up"}, 79 cc: func(_ context.Context, endpoint string) (*etcd.Client, error) { 80 if strings.Contains(endpoint, "node-down") { 81 return nil, errors.New("node down") 82 } 83 84 return &etcd.Client{Endpoint: endpoint}, nil 85 }, 86 expectedClient: etcd.Client{Endpoint: "etcd-node-up"}, 87 }, 88 } 89 90 for _, tt := range tests { 91 t.Run(tt.name, func(t *testing.T) { 92 g := NewWithT(t) 93 subject = NewEtcdClientGenerator(&rest.Config{}, &tls.Config{MinVersion: tls.VersionTLS12}, 0, 0) 94 subject.createClient = tt.cc 95 96 client, err := subject.forFirstAvailableNode(ctx, tt.nodes) 97 98 if tt.expectedErr != "" { 99 g.Expect(err).To(HaveOccurred()) 100 g.Expect(err.Error()).Should(Equal(tt.expectedErr)) 101 } else { 102 g.Expect(*client).Should(BeComparableTo(tt.expectedClient)) 103 } 104 }) 105 } 106 } 107 108 func TestForLeader(t *testing.T) { 109 tests := []struct { 110 name string 111 nodes []string 112 cc clientCreator 113 114 expectedErr string 115 expectedClient etcd.Client 116 }{ 117 { 118 name: "Returns client for leader successfully", 119 nodes: []string{"node-1", "node-leader"}, 120 cc: func(_ context.Context, endpoint string) (*etcd.Client, error) { 121 return &etcd.Client{ 122 Endpoint: endpoint, 123 LeaderID: 1729, 124 EtcdClient: &etcdfake.FakeEtcdClient{ 125 MemberListResponse: &clientv3.MemberListResponse{ 126 Members: []*etcdserverpb.Member{ 127 {ID: 1234, Name: "node-1"}, 128 {ID: 1729, Name: "node-leader"}, 129 }, 130 }, 131 AlarmResponse: &clientv3.AlarmResponse{}, 132 }}, nil 133 }, 134 expectedClient: etcd.Client{ 135 Endpoint: "etcd-node-leader", 136 LeaderID: 1729, EtcdClient: &etcdfake.FakeEtcdClient{ 137 MemberListResponse: &clientv3.MemberListResponse{ 138 Members: []*etcdserverpb.Member{ 139 {ID: 1234, Name: "node-1"}, 140 {ID: 1729, Name: "node-leader"}, 141 }, 142 }, 143 AlarmResponse: &clientv3.AlarmResponse{}, 144 }}, 145 }, 146 { 147 name: "Returns client for leader even when one or more nodes are down", 148 nodes: []string{"node-down-1", "node-down-2", "node-leader"}, 149 cc: func(_ context.Context, endpoint string) (*etcd.Client, error) { 150 if strings.Contains(endpoint, "node-down") { 151 return nil, errors.New("node down") 152 } 153 return &etcd.Client{ 154 Endpoint: endpoint, 155 LeaderID: 1729, 156 EtcdClient: &etcdfake.FakeEtcdClient{ 157 MemberListResponse: &clientv3.MemberListResponse{ 158 Members: []*etcdserverpb.Member{ 159 {ID: 1729, Name: "node-leader"}, 160 }, 161 }, 162 AlarmResponse: &clientv3.AlarmResponse{}, 163 }}, nil 164 }, 165 expectedClient: etcd.Client{ 166 Endpoint: "etcd-node-leader", 167 LeaderID: 1729, EtcdClient: &etcdfake.FakeEtcdClient{ 168 MemberListResponse: &clientv3.MemberListResponse{ 169 Members: []*etcdserverpb.Member{ 170 {ID: 1729, Name: "node-leader"}, 171 }, 172 }, 173 AlarmResponse: &clientv3.AlarmResponse{}, 174 }}, 175 }, 176 { 177 name: "Fails when called with an empty node list", 178 nodes: nil, 179 cc: nil, 180 expectedErr: "invalid argument: forLeader can't be called with an empty list of nodes", 181 }, 182 { 183 name: "Returns error when the leader does not have a corresponding node", 184 nodes: []string{"node-1"}, 185 cc: func(_ context.Context, endpoint string) (*etcd.Client, error) { 186 return &etcd.Client{ 187 Endpoint: endpoint, 188 LeaderID: 1729, 189 EtcdClient: &etcdfake.FakeEtcdClient{ 190 MemberListResponse: &clientv3.MemberListResponse{ 191 Members: []*etcdserverpb.Member{ 192 {ID: 1234, Name: "node-1"}, 193 {ID: 1729, Name: "node-leader"}, 194 }, 195 }, 196 AlarmResponse: &clientv3.AlarmResponse{}, 197 }}, nil 198 }, 199 expectedErr: "etcd leader is reported as 6c1 with name \"node-leader\", but we couldn't find a corresponding Node in the cluster", 200 }, 201 { 202 name: "Returns error when all nodes are down", 203 nodes: []string{"node-down-1", "node-down-2", "node-down-3"}, 204 cc: func(context.Context, string) (*etcd.Client, error) { 205 return nil, errors.New("node down") 206 }, 207 expectedErr: "could not establish a connection to the etcd leader: [could not establish a connection to any etcd node: node down, failed to connect to etcd node]", 208 }, 209 } 210 211 for _, tt := range tests { 212 t.Run(tt.name, func(t *testing.T) { 213 g := NewWithT(t) 214 215 subject = NewEtcdClientGenerator(&rest.Config{}, &tls.Config{MinVersion: tls.VersionTLS12}, 0, 0) 216 subject.createClient = tt.cc 217 218 client, err := subject.forLeader(ctx, tt.nodes) 219 220 if tt.expectedErr != "" { 221 g.Expect(err).To(HaveOccurred()) 222 g.Expect(err.Error()).Should(Equal(tt.expectedErr)) 223 } else { 224 g.Expect(*client).Should(BeComparableTo(tt.expectedClient)) 225 } 226 }) 227 } 228 }