k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/server/server_test.go (about) 1 /* 2 Copyright 2014 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 server 18 19 import ( 20 "context" 21 "crypto/tls" 22 "errors" 23 "fmt" 24 "io" 25 "net" 26 "net/http" 27 "net/http/httptest" 28 "net/http/httputil" 29 "net/url" 30 "reflect" 31 "strconv" 32 "strings" 33 "testing" 34 "time" 35 36 cadvisorapi "github.com/google/cadvisor/info/v1" 37 cadvisorapiv2 "github.com/google/cadvisor/info/v2" 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40 v1 "k8s.io/api/core/v1" 41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 "k8s.io/apimachinery/pkg/types" 43 "k8s.io/apimachinery/pkg/util/httpstream" 44 "k8s.io/apimachinery/pkg/util/httpstream/spdy" 45 "k8s.io/apiserver/pkg/authentication/authenticator" 46 "k8s.io/apiserver/pkg/authentication/user" 47 "k8s.io/apiserver/pkg/authorization/authorizer" 48 "k8s.io/client-go/tools/record" 49 "k8s.io/client-go/tools/remotecommand" 50 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 51 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 52 api "k8s.io/kubernetes/pkg/apis/core" 53 "k8s.io/utils/pointer" 54 55 // Do some initialization to decode the query parameters correctly. 56 utilfeature "k8s.io/apiserver/pkg/util/feature" 57 featuregatetesting "k8s.io/component-base/featuregate/testing" 58 "k8s.io/kubelet/pkg/cri/streaming" 59 "k8s.io/kubelet/pkg/cri/streaming/portforward" 60 remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand" 61 _ "k8s.io/kubernetes/pkg/apis/core/install" 62 "k8s.io/kubernetes/pkg/features" 63 kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" 64 "k8s.io/kubernetes/pkg/kubelet/cm" 65 "k8s.io/kubernetes/pkg/kubelet/server/stats" 66 "k8s.io/kubernetes/pkg/volume" 67 ) 68 69 const ( 70 testUID = "9b01b80f-8fb4-11e4-95ab-4200af06647" 71 testContainerID = "container789" 72 testPodSandboxID = "pod0987" 73 ) 74 75 type fakeKubelet struct { 76 podByNameFunc func(namespace, name string) (*v1.Pod, bool) 77 machineInfoFunc func() (*cadvisorapi.MachineInfo, error) 78 podsFunc func() []*v1.Pod 79 runningPodsFunc func(ctx context.Context) ([]*v1.Pod, error) 80 logFunc func(w http.ResponseWriter, req *http.Request) 81 runFunc func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) 82 getExecCheck func(string, types.UID, string, []string, remotecommandserver.Options) 83 getAttachCheck func(string, types.UID, string, remotecommandserver.Options) 84 getPortForwardCheck func(string, string, types.UID, portforward.V4Options) 85 86 containerLogsFunc func(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error 87 hostnameFunc func() string 88 resyncInterval time.Duration 89 loopEntryTime time.Time 90 plegHealth bool 91 streamingRuntime streaming.Server 92 } 93 94 func (fk *fakeKubelet) ResyncInterval() time.Duration { 95 return fk.resyncInterval 96 } 97 98 func (fk *fakeKubelet) LatestLoopEntryTime() time.Time { 99 return fk.loopEntryTime 100 } 101 102 func (fk *fakeKubelet) GetPodByName(namespace, name string) (*v1.Pod, bool) { 103 return fk.podByNameFunc(namespace, name) 104 } 105 106 func (fk *fakeKubelet) GetRequestedContainersInfo(containerName string, options cadvisorapiv2.RequestOptions) (map[string]*cadvisorapi.ContainerInfo, error) { 107 return map[string]*cadvisorapi.ContainerInfo{}, nil 108 } 109 110 func (fk *fakeKubelet) GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) { 111 return fk.machineInfoFunc() 112 } 113 114 func (*fakeKubelet) GetVersionInfo() (*cadvisorapi.VersionInfo, error) { 115 return &cadvisorapi.VersionInfo{}, nil 116 } 117 118 func (fk *fakeKubelet) GetPods() []*v1.Pod { 119 return fk.podsFunc() 120 } 121 122 func (fk *fakeKubelet) GetRunningPods(ctx context.Context) ([]*v1.Pod, error) { 123 return fk.runningPodsFunc(ctx) 124 } 125 126 func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { 127 fk.logFunc(w, req) 128 } 129 130 func (fk *fakeKubelet) GetKubeletContainerLogs(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error { 131 return fk.containerLogsFunc(ctx, podFullName, containerName, logOptions, stdout, stderr) 132 } 133 134 func (fk *fakeKubelet) GetHostname() string { 135 return fk.hostnameFunc() 136 } 137 138 func (fk *fakeKubelet) RunInContainer(_ context.Context, podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) { 139 return fk.runFunc(podFullName, uid, containerName, cmd) 140 } 141 142 func (fk *fakeKubelet) CheckpointContainer(_ context.Context, podUID types.UID, podFullName, containerName string, options *runtimeapi.CheckpointContainerRequest) error { 143 if containerName == "checkpointingFailure" { 144 return fmt.Errorf("Returning error for test") 145 } 146 return nil 147 } 148 149 func (fk *fakeKubelet) ListMetricDescriptors(ctx context.Context) ([]*runtimeapi.MetricDescriptor, error) { 150 return nil, nil 151 } 152 153 func (fk *fakeKubelet) ListPodSandboxMetrics(ctx context.Context) ([]*runtimeapi.PodSandboxMetrics, error) { 154 return nil, nil 155 } 156 157 type fakeRuntime struct { 158 execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error 159 attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error 160 portForwardFunc func(string, int32, io.ReadWriteCloser) error 161 } 162 163 func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { 164 return f.execFunc(containerID, cmd, stdin, stdout, stderr, tty, resize) 165 } 166 167 func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { 168 return f.attachFunc(containerID, stdin, stdout, stderr, tty, resize) 169 } 170 171 func (f *fakeRuntime) PortForward(_ context.Context, podSandboxID string, port int32, stream io.ReadWriteCloser) error { 172 return f.portForwardFunc(podSandboxID, port, stream) 173 } 174 175 type testStreamingServer struct { 176 streaming.Server 177 fakeRuntime *fakeRuntime 178 testHTTPServer *httptest.Server 179 } 180 181 func newTestStreamingServer(streamIdleTimeout time.Duration) (s *testStreamingServer, err error) { 182 s = &testStreamingServer{} 183 s.testHTTPServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 184 s.ServeHTTP(w, r) 185 })) 186 defer func() { 187 if err != nil { 188 s.testHTTPServer.Close() 189 } 190 }() 191 192 testURL, err := url.Parse(s.testHTTPServer.URL) 193 if err != nil { 194 return nil, err 195 } 196 197 s.fakeRuntime = &fakeRuntime{} 198 config := streaming.DefaultConfig 199 config.BaseURL = testURL 200 if streamIdleTimeout != 0 { 201 config.StreamIdleTimeout = streamIdleTimeout 202 } 203 s.Server, err = streaming.NewServer(config, s.fakeRuntime) 204 if err != nil { 205 return nil, err 206 } 207 return s, nil 208 } 209 210 func (fk *fakeKubelet) GetExec(_ context.Context, podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error) { 211 if fk.getExecCheck != nil { 212 fk.getExecCheck(podFullName, podUID, containerName, cmd, streamOpts) 213 } 214 // Always use testContainerID 215 resp, err := fk.streamingRuntime.GetExec(&runtimeapi.ExecRequest{ 216 ContainerId: testContainerID, 217 Cmd: cmd, 218 Tty: streamOpts.TTY, 219 Stdin: streamOpts.Stdin, 220 Stdout: streamOpts.Stdout, 221 Stderr: streamOpts.Stderr, 222 }) 223 if err != nil { 224 return nil, err 225 } 226 return url.Parse(resp.GetUrl()) 227 } 228 229 func (fk *fakeKubelet) GetAttach(_ context.Context, podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error) { 230 if fk.getAttachCheck != nil { 231 fk.getAttachCheck(podFullName, podUID, containerName, streamOpts) 232 } 233 // Always use testContainerID 234 resp, err := fk.streamingRuntime.GetAttach(&runtimeapi.AttachRequest{ 235 ContainerId: testContainerID, 236 Tty: streamOpts.TTY, 237 Stdin: streamOpts.Stdin, 238 Stdout: streamOpts.Stdout, 239 Stderr: streamOpts.Stderr, 240 }) 241 if err != nil { 242 return nil, err 243 } 244 return url.Parse(resp.GetUrl()) 245 } 246 247 func (fk *fakeKubelet) GetPortForward(ctx context.Context, podName, podNamespace string, podUID types.UID, portForwardOpts portforward.V4Options) (*url.URL, error) { 248 if fk.getPortForwardCheck != nil { 249 fk.getPortForwardCheck(podName, podNamespace, podUID, portForwardOpts) 250 } 251 // Always use testPodSandboxID 252 resp, err := fk.streamingRuntime.GetPortForward(&runtimeapi.PortForwardRequest{ 253 PodSandboxId: testPodSandboxID, 254 Port: portForwardOpts.Ports, 255 }) 256 if err != nil { 257 return nil, err 258 } 259 return url.Parse(resp.GetUrl()) 260 } 261 262 // Unused functions 263 func (*fakeKubelet) GetNode() (*v1.Node, error) { return nil, nil } 264 func (*fakeKubelet) GetNodeConfig() cm.NodeConfig { return cm.NodeConfig{} } 265 func (*fakeKubelet) GetPodCgroupRoot() string { return "" } 266 func (*fakeKubelet) GetPodByCgroupfs(cgroupfs string) (*v1.Pod, bool) { return nil, false } 267 func (fk *fakeKubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) { 268 return map[string]volume.Volume{}, true 269 } 270 func (*fakeKubelet) ListBlockVolumesForPod(podUID types.UID) (map[string]volume.BlockVolume, bool) { 271 return map[string]volume.BlockVolume{}, true 272 } 273 func (*fakeKubelet) RootFsStats() (*statsapi.FsStats, error) { return nil, nil } 274 func (*fakeKubelet) ListPodStats(_ context.Context) ([]statsapi.PodStats, error) { return nil, nil } 275 func (*fakeKubelet) ListPodStatsAndUpdateCPUNanoCoreUsage(_ context.Context) ([]statsapi.PodStats, error) { 276 return nil, nil 277 } 278 func (*fakeKubelet) ListPodCPUAndMemoryStats(_ context.Context) ([]statsapi.PodStats, error) { 279 return nil, nil 280 } 281 func (*fakeKubelet) ImageFsStats(_ context.Context) (*statsapi.FsStats, *statsapi.FsStats, error) { 282 return nil, nil, nil 283 } 284 func (*fakeKubelet) RlimitStats() (*statsapi.RlimitStats, error) { return nil, nil } 285 func (*fakeKubelet) GetCgroupStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, *statsapi.NetworkStats, error) { 286 return nil, nil, nil 287 } 288 func (*fakeKubelet) GetCgroupCPUAndMemoryStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, error) { 289 return nil, nil 290 } 291 292 type fakeAuth struct { 293 authenticateFunc func(*http.Request) (*authenticator.Response, bool, error) 294 attributesFunc func(user.Info, *http.Request) authorizer.Attributes 295 authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) 296 } 297 298 func (f *fakeAuth) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { 299 return f.authenticateFunc(req) 300 } 301 func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes { 302 return f.attributesFunc(u, req) 303 } 304 func (f *fakeAuth) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { 305 return f.authorizeFunc(a) 306 } 307 308 type serverTestFramework struct { 309 serverUnderTest *Server 310 fakeKubelet *fakeKubelet 311 fakeAuth *fakeAuth 312 testHTTPServer *httptest.Server 313 } 314 315 func newServerTest() *serverTestFramework { 316 return newServerTestWithDebug(true, nil) 317 } 318 319 func newServerTestWithDebug(enableDebugging bool, streamingServer streaming.Server) *serverTestFramework { 320 kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ 321 EnableDebuggingHandlers: enableDebugging, 322 EnableSystemLogHandler: enableDebugging, 323 EnableProfilingHandler: enableDebugging, 324 EnableDebugFlagsHandler: enableDebugging, 325 } 326 return newServerTestWithDebuggingHandlers(kubeCfg, streamingServer) 327 } 328 329 func newServerTestWithDebuggingHandlers(kubeCfg *kubeletconfiginternal.KubeletConfiguration, streamingServer streaming.Server) *serverTestFramework { 330 331 fw := &serverTestFramework{} 332 fw.fakeKubelet = &fakeKubelet{ 333 hostnameFunc: func() string { 334 return "127.0.0.1" 335 }, 336 podByNameFunc: func(namespace, name string) (*v1.Pod, bool) { 337 return &v1.Pod{ 338 ObjectMeta: metav1.ObjectMeta{ 339 Namespace: namespace, 340 Name: name, 341 UID: testUID, 342 }, 343 }, true 344 }, 345 plegHealth: true, 346 streamingRuntime: streamingServer, 347 } 348 fw.fakeAuth = &fakeAuth{ 349 authenticateFunc: func(req *http.Request) (*authenticator.Response, bool, error) { 350 return &authenticator.Response{User: &user.DefaultInfo{Name: "test"}}, true, nil 351 }, 352 attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes { 353 return &authorizer.AttributesRecord{User: u} 354 }, 355 authorizeFunc: func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { 356 return authorizer.DecisionAllow, "", nil 357 }, 358 } 359 server := NewServer( 360 fw.fakeKubelet, 361 stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute, &record.FakeRecorder{}), 362 fw.fakeAuth, 363 kubeCfg, 364 ) 365 fw.serverUnderTest = &server 366 fw.testHTTPServer = httptest.NewServer(fw.serverUnderTest) 367 return fw 368 } 369 370 // A helper function to return the correct pod name. 371 func getPodName(name, namespace string) string { 372 if namespace == "" { 373 namespace = metav1.NamespaceDefault 374 } 375 return name + "_" + namespace 376 } 377 378 func TestServeLogs(t *testing.T) { 379 fw := newServerTest() 380 defer fw.testHTTPServer.Close() 381 382 content := string(`<pre><a href="kubelet.log">kubelet.log</a><a href="google.log">google.log</a></pre>`) 383 384 fw.fakeKubelet.logFunc = func(w http.ResponseWriter, req *http.Request) { 385 w.WriteHeader(http.StatusOK) 386 w.Header().Add("Content-Type", "text/html") 387 w.Write([]byte(content)) 388 } 389 390 resp, err := http.Get(fw.testHTTPServer.URL + "/logs/") 391 if err != nil { 392 t.Fatalf("Got error GETing: %v", err) 393 } 394 defer resp.Body.Close() 395 396 body, err := httputil.DumpResponse(resp, true) 397 if err != nil { 398 // copying the response body did not work 399 t.Errorf("Cannot copy resp: %#v", err) 400 } 401 result := string(body) 402 if !strings.Contains(result, "kubelet.log") || !strings.Contains(result, "google.log") { 403 t.Errorf("Received wrong data: %s", result) 404 } 405 } 406 407 func TestServeRunInContainer(t *testing.T) { 408 fw := newServerTest() 409 defer fw.testHTTPServer.Close() 410 output := "foo bar" 411 podNamespace := "other" 412 podName := "foo" 413 expectedPodName := getPodName(podName, podNamespace) 414 expectedContainerName := "baz" 415 expectedCommand := "ls -a" 416 fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) { 417 if podFullName != expectedPodName { 418 t.Errorf("expected %s, got %s", expectedPodName, podFullName) 419 } 420 if containerName != expectedContainerName { 421 t.Errorf("expected %s, got %s", expectedContainerName, containerName) 422 } 423 if strings.Join(cmd, " ") != expectedCommand { 424 t.Errorf("expected: %s, got %v", expectedCommand, cmd) 425 } 426 427 return []byte(output), nil 428 } 429 430 resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil) 431 432 if err != nil { 433 t.Fatalf("Got error POSTing: %v", err) 434 } 435 defer resp.Body.Close() 436 437 body, err := io.ReadAll(resp.Body) 438 if err != nil { 439 // copying the response body did not work 440 t.Errorf("Cannot copy resp: %#v", err) 441 } 442 result := string(body) 443 if result != output { 444 t.Errorf("expected %s, got %s", output, result) 445 } 446 } 447 448 func TestServeRunInContainerWithUID(t *testing.T) { 449 fw := newServerTest() 450 defer fw.testHTTPServer.Close() 451 output := "foo bar" 452 podNamespace := "other" 453 podName := "foo" 454 expectedPodName := getPodName(podName, podNamespace) 455 expectedContainerName := "baz" 456 expectedCommand := "ls -a" 457 fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) { 458 if podFullName != expectedPodName { 459 t.Errorf("expected %s, got %s", expectedPodName, podFullName) 460 } 461 if string(uid) != testUID { 462 t.Errorf("expected %s, got %s", testUID, uid) 463 } 464 if containerName != expectedContainerName { 465 t.Errorf("expected %s, got %s", expectedContainerName, containerName) 466 } 467 if strings.Join(cmd, " ") != expectedCommand { 468 t.Errorf("expected: %s, got %v", expectedCommand, cmd) 469 } 470 471 return []byte(output), nil 472 } 473 474 resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+testUID+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil) 475 if err != nil { 476 t.Fatalf("Got error POSTing: %v", err) 477 } 478 defer resp.Body.Close() 479 480 body, err := io.ReadAll(resp.Body) 481 if err != nil { 482 // copying the response body did not work 483 t.Errorf("Cannot copy resp: %#v", err) 484 } 485 result := string(body) 486 if result != output { 487 t.Errorf("expected %s, got %s", output, result) 488 } 489 } 490 491 func TestHealthCheck(t *testing.T) { 492 fw := newServerTest() 493 defer fw.testHTTPServer.Close() 494 fw.fakeKubelet.hostnameFunc = func() string { 495 return "127.0.0.1" 496 } 497 498 // Test with correct hostname, Docker version 499 assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") 500 501 // Test with incorrect hostname 502 fw.fakeKubelet.hostnameFunc = func() string { 503 return "fake" 504 } 505 assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") 506 } 507 508 func assertHealthFails(t *testing.T, httpURL string, expectedErrorCode int) { 509 resp, err := http.Get(httpURL) 510 if err != nil { 511 t.Fatalf("Got error GETing: %v", err) 512 } 513 defer resp.Body.Close() 514 if resp.StatusCode != expectedErrorCode { 515 t.Errorf("expected status code %d, got %d", expectedErrorCode, resp.StatusCode) 516 } 517 } 518 519 // Ensure all registered handlers & services have an associated testcase. 520 func TestAuthzCoverage(t *testing.T) { 521 fw := newServerTest() 522 defer fw.testHTTPServer.Close() 523 524 // method:path -> has coverage 525 expectedCases := map[string]bool{} 526 527 // Test all the non-web-service handlers 528 for _, path := range fw.serverUnderTest.restfulCont.RegisteredHandlePaths() { 529 expectedCases["GET:"+path] = false 530 expectedCases["POST:"+path] = false 531 } 532 533 // Test all the generated web-service paths 534 for _, ws := range fw.serverUnderTest.restfulCont.RegisteredWebServices() { 535 for _, r := range ws.Routes() { 536 expectedCases[r.Method+":"+r.Path] = false 537 } 538 } 539 540 // This is a sanity check that the Handle->HandleWithFilter() delegation is working 541 // Ideally, these would move to registered web services and this list would get shorter 542 expectedPaths := []string{"/healthz", "/metrics", "/metrics/cadvisor"} 543 for _, expectedPath := range expectedPaths { 544 if _, expected := expectedCases["GET:"+expectedPath]; !expected { 545 t.Errorf("Expected registered handle path %s was missing", expectedPath) 546 } 547 } 548 549 for _, tc := range AuthzTestCases() { 550 expectedCases[tc.Method+":"+tc.Path] = true 551 } 552 553 for tc, found := range expectedCases { 554 if !found { 555 t.Errorf("Missing authz test case for %s", tc) 556 } 557 } 558 } 559 560 func TestAuthFilters(t *testing.T) { 561 // Enable features.ContainerCheckpoint during test 562 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ContainerCheckpoint, true) 563 564 fw := newServerTest() 565 defer fw.testHTTPServer.Close() 566 567 attributesGetter := NewNodeAuthorizerAttributesGetter(authzTestNodeName) 568 569 for _, tc := range AuthzTestCases() { 570 t.Run(tc.Method+":"+tc.Path, func(t *testing.T) { 571 var ( 572 expectedUser = AuthzTestUser() 573 574 calledAuthenticate = false 575 calledAuthorize = false 576 calledAttributes = false 577 ) 578 579 fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) { 580 calledAuthenticate = true 581 return &authenticator.Response{User: expectedUser}, true, nil 582 } 583 fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { 584 calledAttributes = true 585 require.Equal(t, expectedUser, u) 586 return attributesGetter.GetRequestAttributes(u, req) 587 } 588 fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { 589 calledAuthorize = true 590 tc.AssertAttributes(t, a) 591 return authorizer.DecisionNoOpinion, "", nil 592 } 593 594 req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil) 595 require.NoError(t, err) 596 597 resp, err := http.DefaultClient.Do(req) 598 require.NoError(t, err) 599 defer resp.Body.Close() 600 601 assert.Equal(t, http.StatusForbidden, resp.StatusCode) 602 assert.True(t, calledAuthenticate, "Authenticate was not called") 603 assert.True(t, calledAttributes, "Attributes were not called") 604 assert.True(t, calledAuthorize, "Authorize was not called") 605 }) 606 } 607 } 608 609 func TestAuthenticationError(t *testing.T) { 610 var ( 611 expectedUser = &user.DefaultInfo{Name: "test"} 612 expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} 613 614 calledAuthenticate = false 615 calledAuthorize = false 616 calledAttributes = false 617 ) 618 619 fw := newServerTest() 620 defer fw.testHTTPServer.Close() 621 fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) { 622 calledAuthenticate = true 623 return &authenticator.Response{User: expectedUser}, true, nil 624 } 625 fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { 626 calledAttributes = true 627 return expectedAttributes 628 } 629 fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { 630 calledAuthorize = true 631 return authorizer.DecisionNoOpinion, "", errors.New("Failed") 632 } 633 634 assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError) 635 636 if !calledAuthenticate { 637 t.Fatalf("Authenticate was not called") 638 } 639 if !calledAttributes { 640 t.Fatalf("Attributes was not called") 641 } 642 if !calledAuthorize { 643 t.Fatalf("Authorize was not called") 644 } 645 } 646 647 func TestAuthenticationFailure(t *testing.T) { 648 var ( 649 expectedUser = &user.DefaultInfo{Name: "test"} 650 expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} 651 652 calledAuthenticate = false 653 calledAuthorize = false 654 calledAttributes = false 655 ) 656 657 fw := newServerTest() 658 defer fw.testHTTPServer.Close() 659 fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) { 660 calledAuthenticate = true 661 return nil, false, nil 662 } 663 fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { 664 calledAttributes = true 665 return expectedAttributes 666 } 667 fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { 668 calledAuthorize = true 669 return authorizer.DecisionNoOpinion, "", nil 670 } 671 672 assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized) 673 674 if !calledAuthenticate { 675 t.Fatalf("Authenticate was not called") 676 } 677 if calledAttributes { 678 t.Fatalf("Attributes was called unexpectedly") 679 } 680 if calledAuthorize { 681 t.Fatalf("Authorize was called unexpectedly") 682 } 683 } 684 685 func TestAuthorizationSuccess(t *testing.T) { 686 var ( 687 expectedUser = &user.DefaultInfo{Name: "test"} 688 expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} 689 690 calledAuthenticate = false 691 calledAuthorize = false 692 calledAttributes = false 693 ) 694 695 fw := newServerTest() 696 defer fw.testHTTPServer.Close() 697 fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) { 698 calledAuthenticate = true 699 return &authenticator.Response{User: expectedUser}, true, nil 700 } 701 fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { 702 calledAttributes = true 703 return expectedAttributes 704 } 705 fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { 706 calledAuthorize = true 707 return authorizer.DecisionAllow, "", nil 708 } 709 710 assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") 711 712 if !calledAuthenticate { 713 t.Fatalf("Authenticate was not called") 714 } 715 if !calledAttributes { 716 t.Fatalf("Attributes were not called") 717 } 718 if !calledAuthorize { 719 t.Fatalf("Authorize was not called") 720 } 721 } 722 723 func TestSyncLoopCheck(t *testing.T) { 724 fw := newServerTest() 725 defer fw.testHTTPServer.Close() 726 fw.fakeKubelet.hostnameFunc = func() string { 727 return "127.0.0.1" 728 } 729 730 fw.fakeKubelet.resyncInterval = time.Minute 731 fw.fakeKubelet.loopEntryTime = time.Now() 732 733 // Test with correct hostname, Docker version 734 assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") 735 736 fw.fakeKubelet.loopEntryTime = time.Now().Add(time.Minute * -10) 737 assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError) 738 } 739 740 // returns http response status code from the HTTP GET 741 func assertHealthIsOk(t *testing.T, httpURL string) { 742 resp, err := http.Get(httpURL) 743 if err != nil { 744 t.Fatalf("Got error GETing: %v", err) 745 } 746 defer resp.Body.Close() 747 if resp.StatusCode != http.StatusOK { 748 t.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode) 749 } 750 body, readErr := io.ReadAll(resp.Body) 751 if readErr != nil { 752 // copying the response body did not work 753 t.Fatalf("Cannot copy resp: %#v", readErr) 754 } 755 result := string(body) 756 if !strings.Contains(result, "ok") { 757 t.Errorf("expected body contains ok, got %s", result) 758 } 759 } 760 761 func setPodByNameFunc(fw *serverTestFramework, namespace, pod, container string) { 762 fw.fakeKubelet.podByNameFunc = func(namespace, name string) (*v1.Pod, bool) { 763 return &v1.Pod{ 764 ObjectMeta: metav1.ObjectMeta{ 765 Namespace: namespace, 766 Name: pod, 767 }, 768 Spec: v1.PodSpec{ 769 Containers: []v1.Container{ 770 { 771 Name: container, 772 }, 773 }, 774 }, 775 }, true 776 } 777 } 778 779 func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodName, expectedContainerName string, expectedLogOptions *v1.PodLogOptions, output string) { 780 fw.fakeKubelet.containerLogsFunc = func(_ context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error { 781 if podFullName != expectedPodName { 782 t.Errorf("expected %s, got %s", expectedPodName, podFullName) 783 } 784 if containerName != expectedContainerName { 785 t.Errorf("expected %s, got %s", expectedContainerName, containerName) 786 } 787 if !reflect.DeepEqual(expectedLogOptions, logOptions) { 788 t.Errorf("expected %#v, got %#v", expectedLogOptions, logOptions) 789 } 790 791 io.WriteString(stdout, output) 792 return nil 793 } 794 } 795 796 func TestContainerLogs(t *testing.T) { 797 fw := newServerTest() 798 defer fw.testHTTPServer.Close() 799 800 tests := map[string]struct { 801 query string 802 podLogOption *v1.PodLogOptions 803 }{ 804 "without tail": {"", &v1.PodLogOptions{}}, 805 "with tail": {"?tailLines=5", &v1.PodLogOptions{TailLines: pointer.Int64(5)}}, 806 "with legacy tail": {"?tail=5", &v1.PodLogOptions{TailLines: pointer.Int64(5)}}, 807 "with tail all": {"?tail=all", &v1.PodLogOptions{}}, 808 "with follow": {"?follow=1", &v1.PodLogOptions{Follow: true}}, 809 } 810 811 for desc, test := range tests { 812 t.Run(desc, func(t *testing.T) { 813 output := "foo bar" 814 podNamespace := "other" 815 podName := "foo" 816 expectedPodName := getPodName(podName, podNamespace) 817 expectedContainerName := "baz" 818 setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) 819 setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, test.podLogOption, output) 820 resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + test.query) 821 if err != nil { 822 t.Errorf("Got error GETing: %v", err) 823 } 824 defer resp.Body.Close() 825 826 body, err := io.ReadAll(resp.Body) 827 if err != nil { 828 t.Errorf("Error reading container logs: %v", err) 829 } 830 result := string(body) 831 if result != output { 832 t.Errorf("Expected: '%v', got: '%v'", output, result) 833 } 834 }) 835 } 836 } 837 838 func TestContainerLogsWithInvalidTail(t *testing.T) { 839 fw := newServerTest() 840 defer fw.testHTTPServer.Close() 841 output := "foo bar" 842 podNamespace := "other" 843 podName := "foo" 844 expectedPodName := getPodName(podName, podNamespace) 845 expectedContainerName := "baz" 846 setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) 847 setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{}, output) 848 resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=-1") 849 if err != nil { 850 t.Errorf("Got error GETing: %v", err) 851 } 852 defer resp.Body.Close() 853 if resp.StatusCode != http.StatusUnprocessableEntity { 854 t.Errorf("Unexpected non-error reading container logs: %#v", resp) 855 } 856 } 857 858 func TestCheckpointContainer(t *testing.T) { 859 podNamespace := "other" 860 podName := "foo" 861 expectedContainerName := "baz" 862 863 setupTest := func(featureGate bool) *serverTestFramework { 864 // Enable features.ContainerCheckpoint during test 865 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ContainerCheckpoint, featureGate) 866 867 fw := newServerTest() 868 // GetPodByName() should always fail 869 fw.fakeKubelet.podByNameFunc = func(namespace, name string) (*v1.Pod, bool) { 870 return nil, false 871 } 872 return fw 873 } 874 fw := setupTest(true) 875 defer fw.testHTTPServer.Close() 876 877 t.Run("wrong pod namespace", func(t *testing.T) { 878 resp, err := http.Post(fw.testHTTPServer.URL+"/checkpoint/"+podNamespace+"/"+podName+"/"+expectedContainerName, "", nil) 879 if err != nil { 880 t.Errorf("Got error POSTing: %v", err) 881 } 882 defer resp.Body.Close() 883 if resp.StatusCode != http.StatusNotFound { 884 t.Errorf("Unexpected non-error checkpointing container: %#v", resp) 885 } 886 }) 887 // let GetPodByName() return a result, but our container "wrongContainerName" is not part of the Pod 888 setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) 889 t.Run("wrong container name", func(t *testing.T) { 890 resp, err := http.Post(fw.testHTTPServer.URL+"/checkpoint/"+podNamespace+"/"+podName+"/wrongContainerName", "", nil) 891 if err != nil { 892 t.Errorf("Got error POSTing: %v", err) 893 } 894 defer resp.Body.Close() 895 if resp.StatusCode != http.StatusNotFound { 896 t.Errorf("Unexpected non-error checkpointing container: %#v", resp) 897 } 898 }) 899 // Now the checkpointing of the container fails 900 fw.fakeKubelet.podByNameFunc = func(namespace, name string) (*v1.Pod, bool) { 901 return &v1.Pod{ 902 ObjectMeta: metav1.ObjectMeta{ 903 Namespace: podNamespace, 904 Name: podName, 905 }, 906 Spec: v1.PodSpec{ 907 Containers: []v1.Container{ 908 { 909 Name: "checkpointingFailure", 910 }, 911 }, 912 }, 913 }, true 914 } 915 t.Run("checkpointing fails", func(t *testing.T) { 916 resp, err := http.Post(fw.testHTTPServer.URL+"/checkpoint/"+podNamespace+"/"+podName+"/checkpointingFailure", "", nil) 917 if err != nil { 918 t.Errorf("Got error POSTing: %v", err) 919 } 920 defer resp.Body.Close() 921 assert.Equal(t, resp.StatusCode, 500) 922 body, _ := io.ReadAll(resp.Body) 923 assert.Equal(t, string(body), "checkpointing of other/foo/checkpointingFailure failed (Returning error for test)") 924 }) 925 // Now test a successful checkpoint succeeds 926 setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) 927 t.Run("checkpointing succeeds", func(t *testing.T) { 928 resp, err := http.Post(fw.testHTTPServer.URL+"/checkpoint/"+podNamespace+"/"+podName+"/"+expectedContainerName, "", nil) 929 if err != nil { 930 t.Errorf("Got error POSTing: %v", err) 931 } 932 assert.Equal(t, resp.StatusCode, 200) 933 }) 934 935 // Now test for 404 if checkpointing support is explicitly disabled. 936 fw.testHTTPServer.Close() 937 fw = setupTest(false) 938 defer fw.testHTTPServer.Close() 939 setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) 940 t.Run("checkpointing fails because disabled", func(t *testing.T) { 941 resp, err := http.Post(fw.testHTTPServer.URL+"/checkpoint/"+podNamespace+"/"+podName+"/"+expectedContainerName, "", nil) 942 if err != nil { 943 t.Errorf("Got error POSTing: %v", err) 944 } 945 assert.Equal(t, 404, resp.StatusCode) 946 }) 947 } 948 949 func makeReq(t *testing.T, method, url, clientProtocol string) *http.Request { 950 req, err := http.NewRequest(method, url, nil) 951 if err != nil { 952 t.Fatalf("error creating request: %v", err) 953 } 954 req.Header.Set("Content-Type", "") 955 req.Header.Add("X-Stream-Protocol-Version", clientProtocol) 956 return req 957 } 958 959 func TestServeExecInContainerIdleTimeout(t *testing.T) { 960 ss, err := newTestStreamingServer(100 * time.Millisecond) 961 require.NoError(t, err) 962 defer ss.testHTTPServer.Close() 963 fw := newServerTestWithDebug(true, ss) 964 defer fw.testHTTPServer.Close() 965 966 podNamespace := "other" 967 podName := "foo" 968 expectedContainerName := "baz" 969 970 url := fw.testHTTPServer.URL + "/exec/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?c=ls&c=-a&" + api.ExecStdinParam + "=1" 971 972 upgradeRoundTripper, err := spdy.NewRoundTripper(&tls.Config{}) 973 if err != nil { 974 t.Fatalf("Error creating SpdyRoundTripper: %v", err) 975 } 976 c := &http.Client{Transport: upgradeRoundTripper} 977 978 resp, err := c.Do(makeReq(t, "POST", url, "v4.channel.k8s.io")) 979 if err != nil { 980 t.Fatalf("Got error POSTing: %v", err) 981 } 982 defer resp.Body.Close() 983 984 upgradeRoundTripper.Dialer = &net.Dialer{ 985 Deadline: time.Now().Add(60 * time.Second), 986 Timeout: 60 * time.Second, 987 } 988 conn, err := upgradeRoundTripper.NewConnection(resp) 989 if err != nil { 990 t.Fatalf("Unexpected error creating streaming connection: %s", err) 991 } 992 if conn == nil { 993 t.Fatal("Unexpected nil connection") 994 } 995 996 <-conn.CloseChan() 997 } 998 999 func testExecAttach(t *testing.T, verb string) { 1000 tests := map[string]struct { 1001 stdin bool 1002 stdout bool 1003 stderr bool 1004 tty bool 1005 responseStatusCode int 1006 uid bool 1007 }{ 1008 "no input or output": {responseStatusCode: http.StatusBadRequest}, 1009 "stdin": {stdin: true, responseStatusCode: http.StatusSwitchingProtocols}, 1010 "stdout": {stdout: true, responseStatusCode: http.StatusSwitchingProtocols}, 1011 "stderr": {stderr: true, responseStatusCode: http.StatusSwitchingProtocols}, 1012 "stdout and stderr": {stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols}, 1013 "stdin stdout and stderr": {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols}, 1014 "stdin stdout stderr with uid": {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols, uid: true}, 1015 } 1016 1017 for desc := range tests { 1018 test := tests[desc] 1019 t.Run(desc, func(t *testing.T) { 1020 ss, err := newTestStreamingServer(0) 1021 require.NoError(t, err) 1022 defer ss.testHTTPServer.Close() 1023 fw := newServerTestWithDebug(true, ss) 1024 defer fw.testHTTPServer.Close() 1025 fmt.Println(desc) 1026 1027 podNamespace := "other" 1028 podName := "foo" 1029 expectedPodName := getPodName(podName, podNamespace) 1030 expectedContainerName := "baz" 1031 expectedCommand := "ls -a" 1032 expectedStdin := "stdin" 1033 expectedStdout := "stdout" 1034 expectedStderr := "stderr" 1035 done := make(chan struct{}) 1036 clientStdoutReadDone := make(chan struct{}) 1037 clientStderrReadDone := make(chan struct{}) 1038 execInvoked := false 1039 attachInvoked := false 1040 1041 checkStream := func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) { 1042 assert.Equal(t, expectedPodName, podFullName, "podFullName") 1043 if test.uid { 1044 assert.Equal(t, testUID, string(uid), "uid") 1045 } 1046 assert.Equal(t, expectedContainerName, containerName, "containerName") 1047 assert.Equal(t, test.stdin, streamOpts.Stdin, "stdin") 1048 assert.Equal(t, test.stdout, streamOpts.Stdout, "stdout") 1049 assert.Equal(t, test.tty, streamOpts.TTY, "tty") 1050 assert.Equal(t, !test.tty && test.stderr, streamOpts.Stderr, "stderr") 1051 } 1052 1053 fw.fakeKubelet.getExecCheck = func(podFullName string, uid types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) { 1054 execInvoked = true 1055 assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd") 1056 checkStream(podFullName, uid, containerName, streamOpts) 1057 } 1058 1059 fw.fakeKubelet.getAttachCheck = func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) { 1060 attachInvoked = true 1061 checkStream(podFullName, uid, containerName, streamOpts) 1062 } 1063 1064 testStream := func(containerID string, in io.Reader, out, stderr io.WriteCloser, tty bool, done chan struct{}) error { 1065 close(done) 1066 assert.Equal(t, testContainerID, containerID, "containerID") 1067 assert.Equal(t, test.tty, tty, "tty") 1068 require.Equal(t, test.stdin, in != nil, "in") 1069 require.Equal(t, test.stdout, out != nil, "out") 1070 require.Equal(t, !test.tty && test.stderr, stderr != nil, "err") 1071 1072 if test.stdin { 1073 b := make([]byte, 10) 1074 n, err := in.Read(b) 1075 assert.NoError(t, err, "reading from stdin") 1076 assert.Equal(t, expectedStdin, string(b[0:n]), "content from stdin") 1077 } 1078 1079 if test.stdout { 1080 _, err := out.Write([]byte(expectedStdout)) 1081 assert.NoError(t, err, "writing to stdout") 1082 out.Close() 1083 <-clientStdoutReadDone 1084 } 1085 1086 if !test.tty && test.stderr { 1087 _, err := stderr.Write([]byte(expectedStderr)) 1088 assert.NoError(t, err, "writing to stderr") 1089 stderr.Close() 1090 <-clientStderrReadDone 1091 } 1092 return nil 1093 } 1094 1095 ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { 1096 assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd") 1097 return testStream(containerID, stdin, stdout, stderr, tty, done) 1098 } 1099 1100 ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { 1101 return testStream(containerID, stdin, stdout, stderr, tty, done) 1102 } 1103 1104 var url string 1105 if test.uid { 1106 url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + testUID + "/" + expectedContainerName + "?ignore=1" 1107 } else { 1108 url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?ignore=1" 1109 } 1110 if verb == "exec" { 1111 url += "&command=ls&command=-a" 1112 } 1113 if test.stdin { 1114 url += "&" + api.ExecStdinParam + "=1" 1115 } 1116 if test.stdout { 1117 url += "&" + api.ExecStdoutParam + "=1" 1118 } 1119 if test.stderr && !test.tty { 1120 url += "&" + api.ExecStderrParam + "=1" 1121 } 1122 if test.tty { 1123 url += "&" + api.ExecTTYParam + "=1" 1124 } 1125 1126 var ( 1127 resp *http.Response 1128 upgradeRoundTripper httpstream.UpgradeRoundTripper 1129 c *http.Client 1130 ) 1131 upgradeRoundTripper, err = spdy.NewRoundTripper(&tls.Config{}) 1132 if err != nil { 1133 t.Fatalf("Error creating SpdyRoundTripper: %v", err) 1134 } 1135 c = &http.Client{Transport: upgradeRoundTripper} 1136 1137 resp, err = c.Do(makeReq(t, "POST", url, "v4.channel.k8s.io")) 1138 require.NoError(t, err, "POSTing") 1139 defer resp.Body.Close() 1140 1141 _, err = io.ReadAll(resp.Body) 1142 assert.NoError(t, err, "reading response body") 1143 1144 require.Equal(t, test.responseStatusCode, resp.StatusCode, "response status") 1145 if test.responseStatusCode != http.StatusSwitchingProtocols { 1146 return 1147 } 1148 1149 conn, err := upgradeRoundTripper.NewConnection(resp) 1150 require.NoError(t, err, "creating streaming connection") 1151 defer conn.Close() 1152 1153 h := http.Header{} 1154 h.Set(api.StreamType, api.StreamTypeError) 1155 _, err = conn.CreateStream(h) 1156 require.NoError(t, err, "creating error stream") 1157 1158 if test.stdin { 1159 h.Set(api.StreamType, api.StreamTypeStdin) 1160 stream, err := conn.CreateStream(h) 1161 require.NoError(t, err, "creating stdin stream") 1162 _, err = stream.Write([]byte(expectedStdin)) 1163 require.NoError(t, err, "writing to stdin stream") 1164 } 1165 1166 var stdoutStream httpstream.Stream 1167 if test.stdout { 1168 h.Set(api.StreamType, api.StreamTypeStdout) 1169 stdoutStream, err = conn.CreateStream(h) 1170 require.NoError(t, err, "creating stdout stream") 1171 } 1172 1173 var stderrStream httpstream.Stream 1174 if test.stderr && !test.tty { 1175 h.Set(api.StreamType, api.StreamTypeStderr) 1176 stderrStream, err = conn.CreateStream(h) 1177 require.NoError(t, err, "creating stderr stream") 1178 } 1179 1180 if test.stdout { 1181 output := make([]byte, 10) 1182 n, err := stdoutStream.Read(output) 1183 close(clientStdoutReadDone) 1184 assert.NoError(t, err, "reading from stdout stream") 1185 assert.Equal(t, expectedStdout, string(output[0:n]), "stdout") 1186 } 1187 1188 if test.stderr && !test.tty { 1189 output := make([]byte, 10) 1190 n, err := stderrStream.Read(output) 1191 close(clientStderrReadDone) 1192 assert.NoError(t, err, "reading from stderr stream") 1193 assert.Equal(t, expectedStderr, string(output[0:n]), "stderr") 1194 } 1195 1196 // wait for the server to finish before checking if the attach/exec funcs were invoked 1197 <-done 1198 1199 if verb == "exec" { 1200 assert.True(t, execInvoked, "exec should be invoked") 1201 assert.False(t, attachInvoked, "attach should not be invoked") 1202 } else { 1203 assert.True(t, attachInvoked, "attach should be invoked") 1204 assert.False(t, execInvoked, "exec should not be invoked") 1205 } 1206 }) 1207 } 1208 } 1209 1210 func TestServeExecInContainer(t *testing.T) { 1211 testExecAttach(t, "exec") 1212 } 1213 1214 func TestServeAttachContainer(t *testing.T) { 1215 testExecAttach(t, "attach") 1216 } 1217 1218 func TestServePortForwardIdleTimeout(t *testing.T) { 1219 ss, err := newTestStreamingServer(100 * time.Millisecond) 1220 require.NoError(t, err) 1221 defer ss.testHTTPServer.Close() 1222 fw := newServerTestWithDebug(true, ss) 1223 defer fw.testHTTPServer.Close() 1224 1225 podNamespace := "other" 1226 podName := "foo" 1227 1228 url := fw.testHTTPServer.URL + "/portForward/" + podNamespace + "/" + podName 1229 1230 upgradeRoundTripper, err := spdy.NewRoundTripper(&tls.Config{}) 1231 if err != nil { 1232 t.Fatalf("Error creating SpdyRoundTripper: %v", err) 1233 } 1234 c := &http.Client{Transport: upgradeRoundTripper} 1235 1236 req := makeReq(t, "POST", url, "portforward.k8s.io") 1237 resp, err := c.Do(req) 1238 if err != nil { 1239 t.Fatalf("Got error POSTing: %v", err) 1240 } 1241 defer resp.Body.Close() 1242 1243 conn, err := upgradeRoundTripper.NewConnection(resp) 1244 if err != nil { 1245 t.Fatalf("Unexpected error creating streaming connection: %s", err) 1246 } 1247 if conn == nil { 1248 t.Fatal("Unexpected nil connection") 1249 } 1250 defer conn.Close() 1251 1252 <-conn.CloseChan() 1253 } 1254 1255 func TestServePortForward(t *testing.T) { 1256 tests := map[string]struct { 1257 port string 1258 uid bool 1259 clientData string 1260 containerData string 1261 shouldError bool 1262 }{ 1263 "no port": {port: "", shouldError: true}, 1264 "none number port": {port: "abc", shouldError: true}, 1265 "negative port": {port: "-1", shouldError: true}, 1266 "too large port": {port: "65536", shouldError: true}, 1267 "0 port": {port: "0", shouldError: true}, 1268 "min port": {port: "1", shouldError: false}, 1269 "normal port": {port: "8000", shouldError: false}, 1270 "normal port with data forward": {port: "8000", clientData: "client data", containerData: "container data", shouldError: false}, 1271 "max port": {port: "65535", shouldError: false}, 1272 "normal port with uid": {port: "8000", uid: true, shouldError: false}, 1273 } 1274 1275 podNamespace := "other" 1276 podName := "foo" 1277 1278 for desc := range tests { 1279 test := tests[desc] 1280 t.Run(desc, func(t *testing.T) { 1281 ss, err := newTestStreamingServer(0) 1282 require.NoError(t, err) 1283 defer ss.testHTTPServer.Close() 1284 fw := newServerTestWithDebug(true, ss) 1285 defer fw.testHTTPServer.Close() 1286 1287 portForwardFuncDone := make(chan struct{}) 1288 1289 fw.fakeKubelet.getPortForwardCheck = func(name, namespace string, uid types.UID, opts portforward.V4Options) { 1290 assert.Equal(t, podName, name, "pod name") 1291 assert.Equal(t, podNamespace, namespace, "pod namespace") 1292 if test.uid { 1293 assert.Equal(t, testUID, string(uid), "uid") 1294 } 1295 } 1296 1297 ss.fakeRuntime.portForwardFunc = func(podSandboxID string, port int32, stream io.ReadWriteCloser) error { 1298 defer close(portForwardFuncDone) 1299 assert.Equal(t, testPodSandboxID, podSandboxID, "pod sandbox id") 1300 // The port should be valid if it reaches here. 1301 testPort, err := strconv.ParseInt(test.port, 10, 32) 1302 require.NoError(t, err, "parse port") 1303 assert.Equal(t, int32(testPort), port, "port") 1304 1305 if test.clientData != "" { 1306 fromClient := make([]byte, 32) 1307 n, err := stream.Read(fromClient) 1308 assert.NoError(t, err, "reading client data") 1309 assert.Equal(t, test.clientData, string(fromClient[0:n]), "client data") 1310 } 1311 1312 if test.containerData != "" { 1313 _, err := stream.Write([]byte(test.containerData)) 1314 assert.NoError(t, err, "writing container data") 1315 } 1316 1317 return nil 1318 } 1319 1320 var url string 1321 if test.uid { 1322 url = fmt.Sprintf("%s/portForward/%s/%s/%s", fw.testHTTPServer.URL, podNamespace, podName, testUID) 1323 } else { 1324 url = fmt.Sprintf("%s/portForward/%s/%s", fw.testHTTPServer.URL, podNamespace, podName) 1325 } 1326 1327 var ( 1328 upgradeRoundTripper httpstream.UpgradeRoundTripper 1329 c *http.Client 1330 ) 1331 1332 upgradeRoundTripper, err = spdy.NewRoundTripper(&tls.Config{}) 1333 if err != nil { 1334 t.Fatalf("Error creating SpdyRoundTripper: %v", err) 1335 } 1336 c = &http.Client{Transport: upgradeRoundTripper} 1337 1338 req := makeReq(t, "POST", url, "portforward.k8s.io") 1339 resp, err := c.Do(req) 1340 require.NoError(t, err, "POSTing") 1341 defer resp.Body.Close() 1342 1343 assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode, "status code") 1344 1345 conn, err := upgradeRoundTripper.NewConnection(resp) 1346 require.NoError(t, err, "creating streaming connection") 1347 defer conn.Close() 1348 1349 headers := http.Header{} 1350 headers.Set("streamType", "error") 1351 headers.Set("port", test.port) 1352 _, err = conn.CreateStream(headers) 1353 assert.Equal(t, test.shouldError, err != nil, "expect error") 1354 1355 if test.shouldError { 1356 return 1357 } 1358 1359 headers.Set("streamType", "data") 1360 headers.Set("port", test.port) 1361 dataStream, err := conn.CreateStream(headers) 1362 require.NoError(t, err, "create stream") 1363 1364 if test.clientData != "" { 1365 _, err := dataStream.Write([]byte(test.clientData)) 1366 assert.NoError(t, err, "writing client data") 1367 } 1368 1369 if test.containerData != "" { 1370 fromContainer := make([]byte, 32) 1371 n, err := dataStream.Read(fromContainer) 1372 assert.NoError(t, err, "reading container data") 1373 assert.Equal(t, test.containerData, string(fromContainer[0:n]), "container data") 1374 } 1375 1376 <-portForwardFuncDone 1377 }) 1378 } 1379 } 1380 1381 func TestMetricBuckets(t *testing.T) { 1382 tests := map[string]struct { 1383 url string 1384 bucket string 1385 }{ 1386 "healthz endpoint": {url: "/healthz", bucket: "healthz"}, 1387 "attach": {url: "/attach/podNamespace/podID/containerName", bucket: "attach"}, 1388 "attach with uid": {url: "/attach/podNamespace/podID/uid/containerName", bucket: "attach"}, 1389 "configz": {url: "/configz", bucket: "configz"}, 1390 "containerLogs": {url: "/containerLogs/podNamespace/podID/containerName", bucket: "containerLogs"}, 1391 "debug v flags": {url: "/debug/flags/v", bucket: "debug"}, 1392 "pprof with sub": {url: "/debug/pprof/subpath", bucket: "debug"}, 1393 "exec": {url: "/exec/podNamespace/podID/containerName", bucket: "exec"}, 1394 "exec with uid": {url: "/exec/podNamespace/podID/uid/containerName", bucket: "exec"}, 1395 "healthz": {url: "/healthz/", bucket: "healthz"}, 1396 "healthz log sub": {url: "/healthz/log", bucket: "healthz"}, 1397 "healthz ping": {url: "/healthz/ping", bucket: "healthz"}, 1398 "healthz sync loop": {url: "/healthz/syncloop", bucket: "healthz"}, 1399 "logs": {url: "/logs/", bucket: "logs"}, 1400 "logs with path": {url: "/logs/logpath", bucket: "logs"}, 1401 "metrics": {url: "/metrics", bucket: "metrics"}, 1402 "metrics cadvisor sub": {url: "/metrics/cadvisor", bucket: "metrics/cadvisor"}, 1403 "metrics probes sub": {url: "/metrics/probes", bucket: "metrics/probes"}, 1404 "metrics resource sub": {url: "/metrics/resource", bucket: "metrics/resource"}, 1405 "pods": {url: "/pods/", bucket: "pods"}, 1406 "portForward": {url: "/portForward/podNamespace/podID", bucket: "portForward"}, 1407 "portForward with uid": {url: "/portForward/podNamespace/podID/uid", bucket: "portForward"}, 1408 "run": {url: "/run/podNamespace/podID/containerName", bucket: "run"}, 1409 "run with uid": {url: "/run/podNamespace/podID/uid/containerName", bucket: "run"}, 1410 "runningpods": {url: "/runningpods/", bucket: "runningpods"}, 1411 "stats": {url: "/stats/", bucket: "stats"}, 1412 "stats summary sub": {url: "/stats/summary", bucket: "stats"}, 1413 "invalid path": {url: "/junk", bucket: "other"}, 1414 "invalid path starting with good": {url: "/healthzjunk", bucket: "other"}, 1415 } 1416 fw := newServerTest() 1417 defer fw.testHTTPServer.Close() 1418 1419 for _, test := range tests { 1420 path := test.url 1421 bucket := test.bucket 1422 require.Equal(t, fw.serverUnderTest.getMetricBucket(path), bucket) 1423 } 1424 } 1425 1426 func TestMetricMethodBuckets(t *testing.T) { 1427 tests := map[string]struct { 1428 method string 1429 bucket string 1430 }{ 1431 "normal GET": {method: "GET", bucket: "GET"}, 1432 "normal POST": {method: "POST", bucket: "POST"}, 1433 "invalid method": {method: "WEIRD", bucket: "other"}, 1434 } 1435 1436 fw := newServerTest() 1437 defer fw.testHTTPServer.Close() 1438 1439 for _, test := range tests { 1440 method := test.method 1441 bucket := test.bucket 1442 require.Equal(t, fw.serverUnderTest.getMetricMethodBucket(method), bucket) 1443 } 1444 } 1445 1446 func TestDebuggingDisabledHandlers(t *testing.T) { 1447 // for backward compatibility even if enablesystemLogHandler or enableProfilingHandler is set but not 1448 // enableDebuggingHandler then /logs, /pprof and /flags shouldn't be served. 1449 kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ 1450 EnableDebuggingHandlers: false, 1451 EnableSystemLogHandler: true, 1452 EnableDebugFlagsHandler: true, 1453 EnableProfilingHandler: true, 1454 } 1455 fw := newServerTestWithDebuggingHandlers(kubeCfg, nil) 1456 defer fw.testHTTPServer.Close() 1457 1458 paths := []string{ 1459 "/run", "/exec", "/attach", "/portForward", "/containerLogs", "/runningpods", 1460 "/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/", "/runningpods/", 1461 "/run/xxx", "/exec/xxx", "/attach/xxx", "/debug/pprof/profile", "/logs/kubelet.log", 1462 } 1463 1464 for _, p := range paths { 1465 verifyEndpointResponse(t, fw, p, "Debug endpoints are disabled.\n") 1466 } 1467 } 1468 1469 func TestDisablingLogAndProfilingHandler(t *testing.T) { 1470 kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ 1471 EnableDebuggingHandlers: true, 1472 } 1473 fw := newServerTestWithDebuggingHandlers(kubeCfg, nil) 1474 defer fw.testHTTPServer.Close() 1475 1476 // verify debug endpoints are disabled 1477 verifyEndpointResponse(t, fw, "/logs/kubelet.log", "logs endpoint is disabled.\n") 1478 verifyEndpointResponse(t, fw, "/debug/pprof/profile?seconds=2", "profiling endpoint is disabled.\n") 1479 verifyEndpointResponse(t, fw, "/debug/flags/v", "flags endpoint is disabled.\n") 1480 } 1481 1482 func TestFailedParseParamsSummaryHandler(t *testing.T) { 1483 fw := newServerTest() 1484 defer fw.testHTTPServer.Close() 1485 1486 resp, err := http.Post(fw.testHTTPServer.URL+"/stats/summary", "invalid/content/type", nil) 1487 assert.NoError(t, err) 1488 defer resp.Body.Close() 1489 v, err := io.ReadAll(resp.Body) 1490 assert.NoError(t, err) 1491 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) 1492 assert.Contains(t, string(v), "parse form failed") 1493 } 1494 1495 func verifyEndpointResponse(t *testing.T, fw *serverTestFramework, path string, expectedResponse string) { 1496 resp, err := http.Get(fw.testHTTPServer.URL + path) 1497 require.NoError(t, err) 1498 assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) 1499 body, err := io.ReadAll(resp.Body) 1500 require.NoError(t, err) 1501 assert.Equal(t, expectedResponse, string(body)) 1502 1503 resp, err = http.Post(fw.testHTTPServer.URL+path, "", nil) 1504 require.NoError(t, err) 1505 assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) 1506 body, err = io.ReadAll(resp.Body) 1507 require.NoError(t, err) 1508 assert.Equal(t, expectedResponse, string(body)) 1509 } 1510 1511 func TestTrimURLPath(t *testing.T) { 1512 tests := []struct { 1513 path, expected string 1514 }{ 1515 {"", ""}, 1516 {"//", ""}, 1517 {"/pods", "pods"}, 1518 {"pods", "pods"}, 1519 {"pods/", "pods"}, 1520 {"good/", "good"}, 1521 {"pods/probes", "pods"}, 1522 {"metrics", "metrics"}, 1523 {"metrics/resource", "metrics/resource"}, 1524 {"metrics/hello", "metrics/hello"}, 1525 } 1526 1527 for _, test := range tests { 1528 assert.Equal(t, test.expected, getURLRootPath(test.path), fmt.Sprintf("path is: %s", test.path)) 1529 } 1530 }