k8s.io/client-go@v0.22.2/plugin/pkg/client/auth/exec/metrics_test.go (about) 1 /* 2 Copyright 2018 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 exec 18 19 import ( 20 "fmt" 21 "io" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "k8s.io/client-go/pkg/apis/clientauthentication" 27 "k8s.io/client-go/tools/clientcmd/api" 28 "k8s.io/client-go/tools/metrics" 29 ) 30 31 type mockExpiryGauge struct { 32 v *time.Time 33 } 34 35 func (m *mockExpiryGauge) Set(t *time.Time) { 36 m.v = t 37 } 38 39 func ptr(t time.Time) *time.Time { 40 return &t 41 } 42 43 func TestCertificateExpirationTracker(t *testing.T) { 44 now := time.Now() 45 mockMetric := &mockExpiryGauge{} 46 47 tracker := &certificateExpirationTracker{ 48 m: map[*Authenticator]time.Time{}, 49 metricSet: mockMetric.Set, 50 } 51 52 firstAuthenticator := &Authenticator{} 53 secondAuthenticator := &Authenticator{} 54 for _, tc := range []struct { 55 desc string 56 auth *Authenticator 57 time time.Time 58 want *time.Time 59 }{ 60 { 61 desc: "ttl for one authenticator", 62 auth: firstAuthenticator, 63 time: now.Add(time.Minute * 10), 64 want: ptr(now.Add(time.Minute * 10)), 65 }, 66 { 67 desc: "second authenticator shorter ttl", 68 auth: secondAuthenticator, 69 time: now.Add(time.Minute * 5), 70 want: ptr(now.Add(time.Minute * 5)), 71 }, 72 { 73 desc: "update shorter to be longer", 74 auth: secondAuthenticator, 75 time: now.Add(time.Minute * 15), 76 want: ptr(now.Add(time.Minute * 10)), 77 }, 78 { 79 desc: "update shorter to be zero time", 80 auth: firstAuthenticator, 81 time: time.Time{}, 82 want: ptr(now.Add(time.Minute * 15)), 83 }, 84 { 85 desc: "update last to be zero time records nil", 86 auth: secondAuthenticator, 87 time: time.Time{}, 88 want: nil, 89 }, 90 } { 91 // Must run in series as the tests build off each other. 92 t.Run(tc.desc, func(t *testing.T) { 93 tracker.set(tc.auth, tc.time) 94 if mockMetric.v != nil && tc.want != nil { 95 if !mockMetric.v.Equal(*tc.want) { 96 t.Errorf("got: %s; want: %s", mockMetric.v, tc.want) 97 } 98 } else if mockMetric.v != tc.want { 99 t.Errorf("got: %s; want: %s", mockMetric.v, tc.want) 100 } 101 }) 102 } 103 } 104 105 type mockCallsMetric struct { 106 exitCode int 107 errorType string 108 } 109 110 type mockCallsMetricCounter struct { 111 calls []mockCallsMetric 112 } 113 114 func (f *mockCallsMetricCounter) Increment(exitCode int, errorType string) { 115 f.calls = append(f.calls, mockCallsMetric{exitCode: exitCode, errorType: errorType}) 116 } 117 118 func TestCallsMetric(t *testing.T) { 119 const ( 120 goodOutput = `{ 121 "kind": "ExecCredential", 122 "apiVersion": "client.authentication.k8s.io/v1beta1", 123 "status": { 124 "token": "foo-bar" 125 } 126 }` 127 ) 128 129 callsMetricCounter := &mockCallsMetricCounter{} 130 originalExecPluginCalls := metrics.ExecPluginCalls 131 t.Cleanup(func() { metrics.ExecPluginCalls = originalExecPluginCalls }) 132 metrics.ExecPluginCalls = callsMetricCounter 133 134 exitCodes := []int{0, 1, 2, 0} 135 var wantCallsMetrics []mockCallsMetric 136 for _, exitCode := range exitCodes { 137 c := api.ExecConfig{ 138 Command: "./testdata/test-plugin.sh", 139 APIVersion: "client.authentication.k8s.io/v1beta1", 140 Env: []api.ExecEnvVar{ 141 {Name: "TEST_EXIT_CODE", Value: fmt.Sprintf("%d", exitCode)}, 142 {Name: "TEST_OUTPUT", Value: goodOutput}, 143 }, 144 InteractiveMode: api.IfAvailableExecInteractiveMode, 145 } 146 147 a, err := newAuthenticator(newCache(), func(_ int) bool { return false }, &c, nil) 148 if err != nil { 149 t.Fatal(err) 150 } 151 a.stderr = io.Discard 152 153 // Run refresh creds twice so that our test validates that the metrics are set correctly twice 154 // in a row with the same authenticator. 155 refreshCreds := func() { 156 if err := a.refreshCredsLocked(&clientauthentication.Response{}); (err == nil) != (exitCode == 0) { 157 if err != nil { 158 t.Fatalf("wanted no error, but got %q", err.Error()) 159 } else { 160 t.Fatal("wanted error, but got nil") 161 } 162 } 163 mockCallsMetric := mockCallsMetric{exitCode: exitCode, errorType: "no_error"} 164 if exitCode != 0 { 165 mockCallsMetric.errorType = "plugin_execution_error" 166 } 167 wantCallsMetrics = append(wantCallsMetrics, mockCallsMetric) 168 } 169 refreshCreds() 170 refreshCreds() 171 } 172 173 // Run some iterations of the authenticator where the exec plugin fails to run to test special 174 // metric values. 175 refreshCreds := func(command string) { 176 c := api.ExecConfig{ 177 Command: command, 178 APIVersion: "client.authentication.k8s.io/v1beta1", 179 InteractiveMode: api.IfAvailableExecInteractiveMode, 180 } 181 a, err := newAuthenticator(newCache(), func(_ int) bool { return false }, &c, nil) 182 if err != nil { 183 t.Fatal(err) 184 } 185 a.stderr = io.Discard 186 if err := a.refreshCredsLocked(&clientauthentication.Response{}); err == nil { 187 t.Fatal("expected the authenticator to fail because the plugin does not exist") 188 } 189 wantCallsMetrics = append(wantCallsMetrics, mockCallsMetric{exitCode: 1, errorType: "plugin_not_found_error"}) 190 } 191 refreshCreds("does not exist without path slashes") 192 refreshCreds("./does/not/exist/with/relative/path") 193 refreshCreds("/does/not/exist/with/absolute/path") 194 195 callsMetricComparer := cmp.Comparer(func(a, b mockCallsMetric) bool { 196 return a.exitCode == b.exitCode && a.errorType == b.errorType 197 }) 198 actuallCallsMetrics := callsMetricCounter.calls 199 if diff := cmp.Diff(wantCallsMetrics, actuallCallsMetrics, callsMetricComparer); diff != "" { 200 t.Fatalf("got unexpected metrics calls; -want, +got:\n%s", diff) 201 } 202 }