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  }