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