istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/leaderelection/k8sleaderelection/healthzadaptor_test.go (about)

     1  /*
     2  Copyright 2015 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  // nolint
    18  package k8sleaderelection
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"net/http"
    24  	"testing"
    25  	"time"
    26  
    27  	clock "k8s.io/utils/clock/testing"
    28  
    29  	rl "istio.io/istio/pilot/pkg/leaderelection/k8sleaderelection/k8sresourcelock"
    30  )
    31  
    32  type fakeLock struct {
    33  	identity string
    34  	key      string
    35  }
    36  
    37  // Get is a dummy to allow us to have a fakeLock for testing.
    38  func (fl *fakeLock) Get(ctx context.Context) (ler *rl.LeaderElectionRecord, rawRecord []byte, err error) {
    39  	return nil, nil, nil
    40  }
    41  
    42  // Create is a dummy to allow us to have a fakeLock for testing.
    43  func (fl *fakeLock) Create(ctx context.Context, ler rl.LeaderElectionRecord) error {
    44  	return nil
    45  }
    46  
    47  // Update is a dummy to allow us to have a fakeLock for testing.
    48  func (fl *fakeLock) Update(ctx context.Context, ler rl.LeaderElectionRecord) error {
    49  	return nil
    50  }
    51  
    52  // RecordEvent is a dummy to allow us to have a fakeLock for testing.
    53  func (fl *fakeLock) RecordEvent(string) {}
    54  
    55  // Identity is a dummy to allow us to have a fakeLock for testing.
    56  func (fl *fakeLock) Identity() string {
    57  	return fl.identity
    58  }
    59  
    60  // Key is a dummy to allow us to have a fakeLock for testing.
    61  func (fl *fakeLock) Key() string {
    62  	return fl.key
    63  }
    64  
    65  // Describe is a dummy to allow us to have a fakeLock for testing.
    66  func (fl *fakeLock) Describe() string {
    67  	return "Dummy implementation of lock for testing"
    68  }
    69  
    70  // TestLeaderElectionHealthChecker tests that the healthcheck for leader election handles its edge cases.
    71  func TestLeaderElectionHealthChecker(t *testing.T) {
    72  	current := time.Now()
    73  	req := &http.Request{}
    74  
    75  	tests := []struct {
    76  		description    string
    77  		expected       error
    78  		adaptorTimeout time.Duration
    79  		elector        *LeaderElector
    80  	}{
    81  		{
    82  			description:    "call check before leader elector initialized",
    83  			expected:       nil,
    84  			adaptorTimeout: time.Second * 20,
    85  			elector:        nil,
    86  		},
    87  		{
    88  			description:    "call check when the lease is far expired",
    89  			expected:       fmt.Errorf("failed election to renew leadership on lease %s", "foo"),
    90  			adaptorTimeout: time.Second * 20,
    91  			elector: &LeaderElector{
    92  				config: LeaderElectionConfig{
    93  					Lock:          &fakeLock{identity: "healthTest"},
    94  					LeaseDuration: time.Minute,
    95  					Name:          "foo",
    96  				},
    97  				observedRecord: rl.LeaderElectionRecord{
    98  					HolderIdentity: "healthTest",
    99  				},
   100  				observedTime: current,
   101  				clock:        clock.NewFakeClock(current.Add(time.Hour)),
   102  			},
   103  		},
   104  		{
   105  			description:    "call check when the lease is far expired but held by another server",
   106  			expected:       nil,
   107  			adaptorTimeout: time.Second * 20,
   108  			elector: &LeaderElector{
   109  				config: LeaderElectionConfig{
   110  					Lock:          &fakeLock{identity: "healthTest"},
   111  					LeaseDuration: time.Minute,
   112  					Name:          "foo",
   113  				},
   114  				observedRecord: rl.LeaderElectionRecord{
   115  					HolderIdentity: "otherServer",
   116  				},
   117  				observedTime: current,
   118  				clock:        clock.NewFakeClock(current.Add(time.Hour)),
   119  			},
   120  		},
   121  		{
   122  			description:    "call check when the lease is not expired",
   123  			expected:       nil,
   124  			adaptorTimeout: time.Second * 20,
   125  			elector: &LeaderElector{
   126  				config: LeaderElectionConfig{
   127  					Lock:          &fakeLock{identity: "healthTest"},
   128  					LeaseDuration: time.Minute,
   129  					Name:          "foo",
   130  				},
   131  				observedRecord: rl.LeaderElectionRecord{
   132  					HolderIdentity: "healthTest",
   133  				},
   134  				observedTime: current,
   135  				clock:        clock.NewFakeClock(current),
   136  			},
   137  		},
   138  		{
   139  			description:    "call check when the lease is expired but inside the timeout",
   140  			expected:       nil,
   141  			adaptorTimeout: time.Second * 20,
   142  			elector: &LeaderElector{
   143  				config: LeaderElectionConfig{
   144  					Lock:          &fakeLock{identity: "healthTest"},
   145  					LeaseDuration: time.Minute,
   146  					Name:          "foo",
   147  				},
   148  				observedRecord: rl.LeaderElectionRecord{
   149  					HolderIdentity: "healthTest",
   150  				},
   151  				observedTime: current,
   152  				clock:        clock.NewFakeClock(current.Add(time.Minute).Add(time.Second)),
   153  			},
   154  		},
   155  	}
   156  
   157  	for _, test := range tests {
   158  		adaptor := NewLeaderHealthzAdaptor(test.adaptorTimeout)
   159  		if adaptor.le != nil {
   160  			t.Errorf("[%s] leaderChecker started with a LeaderElector %v", test.description, adaptor.le)
   161  		}
   162  		if test.elector != nil {
   163  			test.elector.config.WatchDog = adaptor
   164  			adaptor.SetLeaderElection(test.elector)
   165  			if adaptor.le == nil {
   166  				t.Errorf("[%s] adaptor failed to set the LeaderElector", test.description)
   167  			}
   168  		}
   169  		err := adaptor.Check(req)
   170  		if test.expected == nil {
   171  			if err == nil {
   172  				continue
   173  			}
   174  			t.Errorf("[%s] called check, expected no error but received \"%v\"", test.description, err)
   175  		} else {
   176  			if err == nil {
   177  				t.Errorf("[%s] called check and failed to received the expected error \"%v\"", test.description, test.expected)
   178  			}
   179  			if err.Error() != test.expected.Error() {
   180  				t.Errorf("[%s] called check, expected %v, received %v", test.description, test.expected, err)
   181  			}
   182  		}
   183  	}
   184  }