k8s.io/kubernetes@v1.29.3/pkg/kubelet/prober/prober_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 package prober 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "reflect" 25 "strings" 26 "testing" 27 28 v1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/util/intstr" 30 "k8s.io/client-go/tools/record" 31 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 32 containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" 33 "k8s.io/kubernetes/pkg/kubelet/prober/results" 34 "k8s.io/kubernetes/pkg/kubelet/util/ioutils" 35 "k8s.io/kubernetes/pkg/probe" 36 execprobe "k8s.io/kubernetes/pkg/probe/exec" 37 ) 38 39 func TestGetURLParts(t *testing.T) { 40 testCases := []struct { 41 probe *v1.HTTPGetAction 42 ok bool 43 host string 44 port int 45 path string 46 }{ 47 {&v1.HTTPGetAction{Host: "", Port: intstr.FromInt32(-1), Path: ""}, false, "", -1, ""}, 48 {&v1.HTTPGetAction{Host: "", Port: intstr.FromString(""), Path: ""}, false, "", -1, ""}, 49 {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("-1"), Path: ""}, false, "", -1, ""}, 50 {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("not-found"), Path: ""}, false, "", -1, ""}, 51 {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("found"), Path: ""}, true, "127.0.0.1", 93, ""}, 52 {&v1.HTTPGetAction{Host: "", Port: intstr.FromInt32(76), Path: ""}, true, "127.0.0.1", 76, ""}, 53 {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("118"), Path: ""}, true, "127.0.0.1", 118, ""}, 54 {&v1.HTTPGetAction{Host: "hostname", Port: intstr.FromInt32(76), Path: "path"}, true, "hostname", 76, "path"}, 55 } 56 57 for _, test := range testCases { 58 state := v1.PodStatus{PodIP: "127.0.0.1"} 59 container := v1.Container{ 60 Ports: []v1.ContainerPort{{Name: "found", ContainerPort: 93}}, 61 LivenessProbe: &v1.Probe{ 62 ProbeHandler: v1.ProbeHandler{ 63 HTTPGet: test.probe, 64 }, 65 }, 66 } 67 68 scheme := test.probe.Scheme 69 if scheme == "" { 70 scheme = v1.URISchemeHTTP 71 } 72 host := test.probe.Host 73 if host == "" { 74 host = state.PodIP 75 } 76 port, err := probe.ResolveContainerPort(test.probe.Port, &container) 77 if test.ok && err != nil { 78 t.Errorf("Unexpected error: %v", err) 79 } 80 path := test.probe.Path 81 82 if !test.ok && err == nil { 83 t.Errorf("Expected error for %+v, got %s%s:%d/%s", test, scheme, host, port, path) 84 } 85 if test.ok { 86 if host != test.host || port != test.port || path != test.path { 87 t.Errorf("Expected %s:%d/%s, got %s:%d/%s", 88 test.host, test.port, test.path, host, port, path) 89 } 90 } 91 } 92 } 93 94 func TestGetTCPAddrParts(t *testing.T) { 95 testCases := []struct { 96 probe *v1.TCPSocketAction 97 ok bool 98 host string 99 port int 100 }{ 101 {&v1.TCPSocketAction{Port: intstr.FromInt32(-1)}, false, "", -1}, 102 {&v1.TCPSocketAction{Port: intstr.FromString("")}, false, "", -1}, 103 {&v1.TCPSocketAction{Port: intstr.FromString("-1")}, false, "", -1}, 104 {&v1.TCPSocketAction{Port: intstr.FromString("not-found")}, false, "", -1}, 105 {&v1.TCPSocketAction{Port: intstr.FromString("found")}, true, "1.2.3.4", 93}, 106 {&v1.TCPSocketAction{Port: intstr.FromInt32(76)}, true, "1.2.3.4", 76}, 107 {&v1.TCPSocketAction{Port: intstr.FromString("118")}, true, "1.2.3.4", 118}, 108 } 109 110 for _, test := range testCases { 111 host := "1.2.3.4" 112 container := v1.Container{ 113 Ports: []v1.ContainerPort{{Name: "found", ContainerPort: 93}}, 114 LivenessProbe: &v1.Probe{ 115 ProbeHandler: v1.ProbeHandler{ 116 TCPSocket: test.probe, 117 }, 118 }, 119 } 120 port, err := probe.ResolveContainerPort(test.probe.Port, &container) 121 if !test.ok && err == nil { 122 t.Errorf("Expected error for %+v, got %s:%d", test, host, port) 123 } 124 if test.ok && err != nil { 125 t.Errorf("Unexpected error: %v", err) 126 } 127 if test.ok { 128 if host != test.host || port != test.port { 129 t.Errorf("Expected %s:%d, got %s:%d", test.host, test.port, host, port) 130 } 131 } 132 } 133 } 134 135 func TestProbe(t *testing.T) { 136 ctx := context.Background() 137 containerID := kubecontainer.ContainerID{Type: "test", ID: "foobar"} 138 139 execProbe := &v1.Probe{ 140 ProbeHandler: v1.ProbeHandler{ 141 Exec: &v1.ExecAction{}, 142 }, 143 } 144 tests := []struct { 145 probe *v1.Probe 146 env []v1.EnvVar 147 execError bool 148 expectError bool 149 execResult probe.Result 150 expectedResult results.Result 151 expectCommand []string 152 }{ 153 { // No probe 154 probe: nil, 155 expectedResult: results.Success, 156 }, 157 { // No handler 158 probe: &v1.Probe{}, 159 expectError: true, 160 expectedResult: results.Failure, 161 }, 162 { // Probe fails 163 probe: execProbe, 164 execResult: probe.Failure, 165 expectedResult: results.Failure, 166 }, 167 { // Probe succeeds 168 probe: execProbe, 169 execResult: probe.Success, 170 expectedResult: results.Success, 171 }, 172 { // Probe result is warning 173 probe: execProbe, 174 execResult: probe.Warning, 175 expectedResult: results.Success, 176 }, 177 { // Probe result is unknown 178 probe: execProbe, 179 execResult: probe.Unknown, 180 expectedResult: results.Failure, 181 }, 182 { // Probe has an error 183 probe: execProbe, 184 execError: true, 185 expectError: true, 186 execResult: probe.Unknown, 187 expectedResult: results.Failure, 188 }, 189 { // Probe arguments are passed through 190 probe: &v1.Probe{ 191 ProbeHandler: v1.ProbeHandler{ 192 Exec: &v1.ExecAction{ 193 Command: []string{"/bin/bash", "-c", "some script"}, 194 }, 195 }, 196 }, 197 expectCommand: []string{"/bin/bash", "-c", "some script"}, 198 execResult: probe.Success, 199 expectedResult: results.Success, 200 }, 201 { // Probe arguments are passed through 202 probe: &v1.Probe{ 203 ProbeHandler: v1.ProbeHandler{ 204 Exec: &v1.ExecAction{ 205 Command: []string{"/bin/bash", "-c", "some $(A) $(B)"}, 206 }, 207 }, 208 }, 209 env: []v1.EnvVar{ 210 {Name: "A", Value: "script"}, 211 }, 212 expectCommand: []string{"/bin/bash", "-c", "some script $(B)"}, 213 execResult: probe.Success, 214 expectedResult: results.Success, 215 }, 216 } 217 218 for i, test := range tests { 219 for _, probeType := range [...]probeType{liveness, readiness, startup} { 220 prober := &prober{ 221 recorder: &record.FakeRecorder{}, 222 } 223 testID := fmt.Sprintf("%d-%s", i, probeType) 224 testContainer := v1.Container{Env: test.env} 225 switch probeType { 226 case liveness: 227 testContainer.LivenessProbe = test.probe 228 case readiness: 229 testContainer.ReadinessProbe = test.probe 230 case startup: 231 testContainer.StartupProbe = test.probe 232 } 233 if test.execError { 234 prober.exec = fakeExecProber{test.execResult, errors.New("exec error")} 235 } else { 236 prober.exec = fakeExecProber{test.execResult, nil} 237 } 238 239 result, err := prober.probe(ctx, probeType, &v1.Pod{}, v1.PodStatus{}, testContainer, containerID) 240 if test.expectError && err == nil { 241 t.Errorf("[%s] Expected probe error but no error was returned.", testID) 242 } 243 if !test.expectError && err != nil { 244 t.Errorf("[%s] Didn't expect probe error but got: %v", testID, err) 245 } 246 if test.expectedResult != result { 247 t.Errorf("[%s] Expected result to be %v but was %v", testID, test.expectedResult, result) 248 } 249 250 if len(test.expectCommand) > 0 { 251 prober.exec = execprobe.New() 252 prober.runner = &containertest.FakeContainerCommandRunner{} 253 _, err := prober.probe(ctx, probeType, &v1.Pod{}, v1.PodStatus{}, testContainer, containerID) 254 if err != nil { 255 t.Errorf("[%s] Didn't expect probe error but got: %v", testID, err) 256 continue 257 } 258 if !reflect.DeepEqual(test.expectCommand, prober.runner.(*containertest.FakeContainerCommandRunner).Cmd) { 259 t.Errorf("[%s] unexpected probe arguments: %v", testID, prober.runner.(*containertest.FakeContainerCommandRunner).Cmd) 260 } 261 } 262 } 263 } 264 } 265 266 func TestNewExecInContainer(t *testing.T) { 267 ctx := context.Background() 268 limit := 1024 269 tenKilobyte := strings.Repeat("logs-123", 128*10) 270 271 tests := []struct { 272 name string 273 stdout string 274 expected string 275 err error 276 }{ 277 { 278 name: "no error", 279 stdout: "foo", 280 expected: "foo", 281 err: nil, 282 }, 283 { 284 name: "no error", 285 stdout: tenKilobyte, 286 expected: tenKilobyte[0:limit], 287 err: nil, 288 }, 289 { 290 name: "error - make sure we get output", 291 stdout: "foo", 292 expected: "foo", 293 err: errors.New("bad"), 294 }, 295 } 296 297 for _, test := range tests { 298 runner := &containertest.FakeContainerCommandRunner{ 299 Stdout: test.stdout, 300 Err: test.err, 301 } 302 prober := &prober{ 303 runner: runner, 304 } 305 306 container := v1.Container{} 307 containerID := kubecontainer.ContainerID{Type: "docker", ID: "containerID"} 308 cmd := []string{"/foo", "bar"} 309 exec := prober.newExecInContainer(ctx, container, containerID, cmd, 0) 310 311 var dataBuffer bytes.Buffer 312 writer := ioutils.LimitWriter(&dataBuffer, int64(limit)) 313 exec.SetStderr(writer) 314 exec.SetStdout(writer) 315 err := exec.Start() 316 if err == nil { 317 err = exec.Wait() 318 } 319 actualOutput := dataBuffer.Bytes() 320 321 if e, a := containerID, runner.ContainerID; e != a { 322 t.Errorf("%s: container id: expected %v, got %v", test.name, e, a) 323 } 324 if e, a := cmd, runner.Cmd; !reflect.DeepEqual(e, a) { 325 t.Errorf("%s: cmd: expected %v, got %v", test.name, e, a) 326 } 327 // this isn't 100% foolproof as a bug in a real CommandRunner where it fails to copy to stdout/stderr wouldn't be caught by this test 328 if e, a := test.expected, string(actualOutput); e != a { 329 t.Errorf("%s: output: expected %q, got %q", test.name, e, a) 330 } 331 if e, a := fmt.Sprintf("%v", test.err), fmt.Sprintf("%v", err); e != a { 332 t.Errorf("%s: error: expected %s, got %s", test.name, e, a) 333 } 334 } 335 }