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  }