add e2e tests for static pod and standalone mode

This commit is contained in:
HirazawaUi
2025-10-04 22:31:06 +08:00
parent 879d81ff06
commit 192d93f7cb
2 changed files with 255 additions and 3 deletions

View File

@@ -32,8 +32,10 @@ import (
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/features"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
imageutils "k8s.io/kubernetes/test/utils/image"
admissionapi "k8s.io/pod-security-admission/api"
"sigs.k8s.io/yaml"
@@ -43,7 +45,6 @@ import (
"github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/printers"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
)
@@ -384,6 +385,103 @@ var _ = SIGDescribe("MirrorPod (Pod Generation)", func() {
})
})
var _ = SIGDescribe("MirrorPod with EnvFiles", framework.WithFeatureGate(features.EnvFiles), func() {
f := framework.NewDefaultFramework("mirror-pod-envfiles")
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
var ns, podPath, staticPodName, mirrorPodName string
ginkgo.Context("when creating a static pod with EnvFiles", func() {
ginkgo.BeforeEach(func() {
ns = f.Namespace.Name
staticPodName = "static-pod-envfiles-" + string(uuid.NewUUID())
mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
podPath = kubeletCfg.StaticPodPath
})
ginkgo.AfterEach(func(ctx context.Context) {
ginkgo.By("delete the static pod")
err := deleteStaticPod(podPath, staticPodName, ns)
framework.ExpectNoError(err)
ginkgo.By("wait for the mirror pod to disappear")
gomega.Eventually(ctx, func(ctx context.Context) error {
return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
}, 2*time.Minute, time.Second*4).Should(gomega.Succeed())
})
ginkgo.It("should be able to consume variables from a file", func(ctx context.Context) {
podSpec := v1.PodSpec{
InitContainers: []v1.Container{
{
Name: "setup-envfile",
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Command: []string{"sh", "-c", `echo CONFIG_1='value1' > /data/config.env && echo CONFIG_2=\'value2\' >> /data/config.env`},
VolumeMounts: []v1.VolumeMount{
{
Name: "config",
MountPath: "/data",
},
},
},
},
Containers: []v1.Container{
{
Name: "use-envfile",
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Command: []string{"sh", "-c", "env | grep -E '(CONFIG_1|CONFIG_2)' | sort"},
Env: []v1.EnvVar{
{
Name: "CONFIG_1",
ValueFrom: &v1.EnvVarSource{
FileKeyRef: &v1.FileKeySelector{
VolumeName: "config",
Path: "config.env",
Key: "CONFIG_1",
},
},
},
{
Name: "CONFIG_2",
ValueFrom: &v1.EnvVarSource{
FileKeyRef: &v1.FileKeySelector{
VolumeName: "config",
Path: "config.env",
Key: "CONFIG_2",
},
},
},
},
},
},
RestartPolicy: v1.RestartPolicyNever,
Volumes: []v1.Volume{
{
Name: "config",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{},
},
},
},
}
ginkgo.By("create the static pod with envfiles")
err := createStaticPodWithSpec(podPath, staticPodName, ns, podSpec)
framework.ExpectNoError(err)
ginkgo.By("wait for the mirror pod to succeed")
err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, mirrorPodName, ns)
framework.ExpectNoError(err)
ginkgo.By("checking the logs of the mirror pod")
logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, ns, mirrorPodName, "use-envfile")
framework.ExpectNoError(err)
gomega.Expect(logs).To(gomega.ContainSubstring("CONFIG_1=value1"))
gomega.Expect(logs).To(gomega.ContainSubstring("CONFIG_2=value2"))
})
})
})
func podVolumeDirectoryExists(uid types.UID) bool {
podVolumePath := fmt.Sprintf("/var/lib/kubelet/pods/%s/volumes/", uid)
var podVolumeDirectoryExists bool

View File

@@ -24,27 +24,151 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/kubernetes/pkg/cluster/ports"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/e2e/feature"
"k8s.io/kubernetes/test/e2e/framework"
testutils "k8s.io/kubernetes/test/utils"
imageutils "k8s.io/kubernetes/test/utils/image"
admissionapi "k8s.io/pod-security-admission/api"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
testutils "k8s.io/kubernetes/test/utils"
)
var _ = SIGDescribe(feature.StandaloneMode, framework.WithFeatureGate(features.EnvFiles), func() {
f := framework.NewDefaultFramework("static-pod-envfiles")
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
ginkgo.Context("when creating a static pod with EnvFiles", func() {
var ns, podPath, staticPodName string
ginkgo.It("the pod should be running and consume variables", func(ctx context.Context) {
ns = f.Namespace.Name
staticPodName = "static-pod-envfiles-" + string(uuid.NewUUID())
podPath = kubeletCfg.StaticPodPath
podSpec := &v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: staticPodName,
Namespace: ns,
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Name: "setup-envfile",
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Command: []string{"sh", "-c", `echo CONFIG_1=\'value1\' > /data/config.env && echo CONFIG_2=\'value2\' >> /data/config.env`},
VolumeMounts: []v1.VolumeMount{
{
Name: "config",
MountPath: "/data",
},
},
},
},
Containers: []v1.Container{
{
Name: "use-envfile",
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Command: []string{"sh", "-c", "env | grep -E '(CONFIG_1|CONFIG_2)' | sort"},
Env: []v1.EnvVar{
{
Name: "CONFIG_1",
ValueFrom: &v1.EnvVarSource{
FileKeyRef: &v1.FileKeySelector{
VolumeName: "config",
Path: "config.env",
Key: "CONFIG_1",
},
},
},
{
Name: "CONFIG_2",
ValueFrom: &v1.EnvVarSource{
FileKeyRef: &v1.FileKeySelector{
VolumeName: "config",
Path: "config.env",
Key: "CONFIG_2",
},
},
},
},
},
},
RestartPolicy: v1.RestartPolicyNever,
Volumes: []v1.Volume{
{
Name: "config",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{},
},
},
},
},
}
err := scheduleStaticPod(podPath, staticPodName, ns, podSpec)
framework.ExpectNoError(err)
gomega.Eventually(ctx, func(ctx context.Context) error {
pod, err := getPodFromStandaloneKubelet(ctx, ns, staticPodName)
if err != nil {
return fmt.Errorf("error getting pod(%v/%v) from standalone kubelet: %w", ns, staticPodName, err)
}
if pod.Status.Phase == v1.PodSucceeded {
return nil
}
if pod.Status.Phase == v1.PodFailed {
logs, err := getPodLogsFromStandaloneKubelet(ctx, ns, staticPodName, "use-envfile")
if err != nil {
framework.Logf("failed to get logs on pod failure: %v", err)
}
return fmt.Errorf("pod (%v/%v) failed, logs: %s", ns, staticPodName, logs)
}
return fmt.Errorf("pod (%v/%v) is not succeeded, phase: %s", ns, staticPodName, pod.Status.Phase)
}, f.Timeouts.PodStart, time.Second*5).Should(gomega.Succeed())
logs, err := getPodLogsFromStandaloneKubelet(ctx, ns, staticPodName, "use-envfile")
framework.ExpectNoError(err)
gomega.Expect(logs).To(gomega.ContainSubstring("CONFIG_1=value1"))
gomega.Expect(logs).To(gomega.ContainSubstring("CONFIG_2=value2"))
})
ginkgo.AfterEach(func(ctx context.Context) {
ginkgo.By(fmt.Sprintf("delete the static pod (%v/%v)", ns, staticPodName))
err := deleteStaticPod(podPath, staticPodName, ns)
framework.ExpectNoError(err)
ginkgo.By(fmt.Sprintf("wait for pod to disappear (%v/%v)", ns, staticPodName))
gomega.Eventually(ctx, func(ctx context.Context) error {
_, err := getPodFromStandaloneKubelet(ctx, ns, staticPodName)
if apierrors.IsNotFound(err) {
return nil
}
return fmt.Errorf("pod (%v/%v) still exists", ns, staticPodName)
}).Should(gomega.Succeed())
})
})
})
var _ = SIGDescribe(feature.StandaloneMode, func() {
f := framework.NewDefaultFramework("static-pod")
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
@@ -315,6 +439,36 @@ func getPodFromStandaloneKubelet(ctx context.Context, podNamespace string, podNa
return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "pods"}, podName)
}
func getPodLogsFromStandaloneKubelet(ctx context.Context, podNamespace string, podName string, containerName string) (string, error) {
pod, err := getPodFromStandaloneKubelet(ctx, podNamespace, podName)
if err != nil {
return "", fmt.Errorf("failed to get pod %s/%s: %w", podNamespace, podName, err)
}
logCRIDir := "/var/log/pods"
podLogDir := filepath.Join(logCRIDir, fmt.Sprintf("%s_%s_%s", pod.Namespace, pod.Name, pod.UID))
logFile := filepath.Join(podLogDir, containerName, "0.log")
var content []byte
err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
var errRead error
content, errRead = os.ReadFile(logFile)
if errRead != nil {
if os.IsNotExist(errRead) {
return false, nil
}
return false, errRead
}
return true, nil
})
if err != nil {
return "", fmt.Errorf("could not read log file %s: %w", logFile, err)
}
return string(content), nil
}
// Decodes the http response from /configz and returns a kubeletconfig.KubeletConfiguration (internal type).
func decodePods(respBody []byte) (*v1.PodList, error) {
// This hack because /pods reports the following structure: