k8s.io/kubernetes@v1.29.3/test/e2e_node/standalone_test.go (about)

     1  /*
     2  Copyright 2023 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 e2enode
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"os"
    27  	"strings"
    28  	"time"
    29  
    30  	v1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"k8s.io/apimachinery/pkg/util/uuid"
    35  	"k8s.io/cli-runtime/pkg/printers"
    36  	"k8s.io/kubernetes/pkg/cluster/ports"
    37  	"k8s.io/kubernetes/test/e2e/feature"
    38  	"k8s.io/kubernetes/test/e2e/framework"
    39  	imageutils "k8s.io/kubernetes/test/utils/image"
    40  	admissionapi "k8s.io/pod-security-admission/api"
    41  
    42  	"github.com/onsi/ginkgo/v2"
    43  	"github.com/onsi/gomega"
    44  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    45  	testutils "k8s.io/kubernetes/test/utils"
    46  )
    47  
    48  var _ = SIGDescribe(feature.StandaloneMode, func() {
    49  	f := framework.NewDefaultFramework("static-pod")
    50  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    51  	ginkgo.Context("when creating a static pod", func() {
    52  		var ns, podPath, staticPodName string
    53  
    54  		ginkgo.It("the pod should be running", func(ctx context.Context) {
    55  			ns = f.Namespace.Name
    56  			staticPodName = "static-pod-" + string(uuid.NewUUID())
    57  			podPath = kubeletCfg.StaticPodPath
    58  
    59  			err := createBasicStaticPod(podPath, staticPodName, ns)
    60  			framework.ExpectNoError(err)
    61  
    62  			gomega.Eventually(ctx, func(ctx context.Context) error {
    63  				pod, err := getPodFromStandaloneKubelet(ctx, ns, staticPodName)
    64  				if err != nil {
    65  					return fmt.Errorf("error getting pod(%v/%v) from standalone kubelet: %v", ns, staticPodName, err)
    66  				}
    67  
    68  				isReady, err := testutils.PodRunningReady(pod)
    69  				if err != nil {
    70  					return fmt.Errorf("error checking if pod (%v/%v) is running ready: %v", ns, staticPodName, err)
    71  				}
    72  				if !isReady {
    73  					return fmt.Errorf("pod (%v/%v) is not running", ns, staticPodName)
    74  				}
    75  				return nil
    76  			}, f.Timeouts.PodStart, time.Second*5).Should(gomega.BeNil())
    77  		})
    78  
    79  		ginkgo.AfterEach(func(ctx context.Context) {
    80  			ginkgo.By(fmt.Sprintf("delete the static pod (%v/%v)", ns, staticPodName))
    81  			err := deleteStaticPod(podPath, staticPodName, ns)
    82  			framework.ExpectNoError(err)
    83  
    84  			ginkgo.By(fmt.Sprintf("wait for pod to disappear (%v/%v)", ns, staticPodName))
    85  			gomega.Eventually(ctx, func(ctx context.Context) error {
    86  				_, err := getPodFromStandaloneKubelet(ctx, ns, staticPodName)
    87  
    88  				if apierrors.IsNotFound(err) {
    89  					return nil
    90  				}
    91  				return fmt.Errorf("pod (%v/%v) still exists", ns, staticPodName)
    92  			}).Should(gomega.Succeed())
    93  		})
    94  	})
    95  })
    96  
    97  func createBasicStaticPod(dir, name, namespace string) error {
    98  	podSpec := &v1.Pod{
    99  		TypeMeta: metav1.TypeMeta{
   100  			Kind:       "Pod",
   101  			APIVersion: "v1",
   102  		},
   103  		ObjectMeta: metav1.ObjectMeta{
   104  			Name:      name,
   105  			Namespace: namespace,
   106  		},
   107  		Spec: v1.PodSpec{
   108  			RestartPolicy: v1.RestartPolicyAlways,
   109  			Containers: []v1.Container{
   110  				{
   111  					Name:  "regular1",
   112  					Image: imageutils.GetE2EImage(imageutils.BusyBox),
   113  					Command: []string{
   114  						"/bin/sh", "-c", "touch /tmp/healthy; sleep 10000",
   115  					},
   116  					Resources: v1.ResourceRequirements{
   117  						Requests: v1.ResourceList{
   118  							v1.ResourceMemory: resource.MustParse("15Mi"),
   119  						},
   120  						Limits: v1.ResourceList{
   121  							v1.ResourceMemory: resource.MustParse("15Mi"),
   122  						},
   123  					},
   124  					ReadinessProbe: &v1.Probe{
   125  						InitialDelaySeconds: 2,
   126  						TimeoutSeconds:      2,
   127  						ProbeHandler: v1.ProbeHandler{
   128  							Exec: &v1.ExecAction{
   129  								Command: []string{"/bin/sh", "-c", "cat /tmp/healthy"},
   130  							},
   131  						},
   132  					},
   133  				},
   134  			},
   135  		},
   136  	}
   137  
   138  	file := staticPodPath(dir, name, namespace)
   139  	f, err := os.OpenFile(file, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	defer f.Close()
   144  
   145  	y := printers.YAMLPrinter{}
   146  	y.PrintObj(podSpec, f)
   147  
   148  	return nil
   149  }
   150  
   151  func getPodFromStandaloneKubelet(ctx context.Context, podNamespace string, podName string) (*v1.Pod, error) {
   152  	endpoint := fmt.Sprintf("http://127.0.0.1:%d/pods", ports.KubeletReadOnlyPort)
   153  	// TODO: we do not need TLS and bearer token for this test
   154  	tr := &http.Transport{
   155  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   156  	}
   157  	client := &http.Client{Transport: tr}
   158  	req, err := http.NewRequest("GET", endpoint, nil)
   159  	framework.ExpectNoError(err)
   160  	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", framework.TestContext.BearerToken))
   161  	req.Header.Add("Accept", "application/json")
   162  
   163  	resp, err := client.Do(req)
   164  	if err != nil {
   165  		framework.Logf("Failed to get /pods: %v", err)
   166  		return nil, err
   167  	}
   168  	defer resp.Body.Close()
   169  
   170  	if resp.StatusCode != 200 {
   171  		framework.Logf("/pods response status not 200. Response was: %+v", resp)
   172  		return nil, fmt.Errorf("/pods response was not 200: %v", err)
   173  	}
   174  
   175  	respBody, err := io.ReadAll(resp.Body)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("/pods response was unable to be read: %v", err)
   178  	}
   179  
   180  	pods, err := decodePods(respBody)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("unable to decode /pods: %v", err)
   183  	}
   184  
   185  	for _, p := range pods.Items {
   186  		// Static pods has a node name suffix so comparing as substring
   187  		p := p
   188  		if strings.Contains(p.Name, podName) && strings.Contains(p.Namespace, podNamespace) {
   189  			return &p, nil
   190  		}
   191  	}
   192  
   193  	return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "pods"}, podName)
   194  }
   195  
   196  // Decodes the http response from /configz and returns a kubeletconfig.KubeletConfiguration (internal type).
   197  func decodePods(respBody []byte) (*v1.PodList, error) {
   198  	// This hack because /pods reports the following structure:
   199  	// {"kind":"PodList","apiVersion":"v1","metadata":{},"items":[{"metadata":{"name":"kube-dns-autoscaler-758c4689b9-htpqj","generateName":"kube-dns-autoscaler-758c4689b9-",
   200  
   201  	var pods v1.PodList
   202  	err := json.Unmarshal(respBody, &pods)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	return &pods, nil
   208  }