1
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
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
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
197 func decodePods(respBody []byte) (*v1.PodList, error) {
198
199
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 }
209
View as plain text