kubernetes/test/e2e_node/runtime_conformance_test.go
Stanislav Láznička 805eb885e3
node e2e: add tests for Ensure Secret Image Pulls default policy
Signed-off-by: Stanislav Láznička <slznika@microsoft.com>

Co-authored-by: Anish Ramasekar <anish.ramasekar@gmail.com>
2025-11-11 11:15:53 -05:00

172 lines
6.2 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2enode
import (
"context"
"fmt"
"os"
"path/filepath"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/kubelet/images"
"k8s.io/kubernetes/test/e2e/common/node"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2eregistry "k8s.io/kubernetes/test/e2e/framework/registry"
"k8s.io/kubernetes/test/e2e_node/services"
admissionapi "k8s.io/pod-security-admission/api"
"github.com/onsi/ginkgo/v2"
)
var _ = SIGDescribe("Container Runtime Conformance Test", func() {
f := framework.NewDefaultFramework("runtime-conformance")
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged // custom registry pods need HostPorts
ginkgo.Describe("container runtime conformance blackbox test", func() {
ginkgo.Context("when running a container with a new image", func() {
// The following images are not added into NodePrePullImageList, because this test is
// testing image pulling, these images don't need to be prepulled. The ImagePullPolicy
// is v1.PullAlways, so it won't be blocked by framework image pre-pull list check.
for _, testCase := range []struct {
description string
image string
setupRegisty bool
phase v1.PodPhase
waiting bool
}{
{
description: "should be able to pull from private registry with credential provider",
image: "pause:testing",
setupRegisty: true,
phase: v1.PodRunning,
waiting: false,
},
} {
var registryAddress string
var podNodes []string
ginkgo.BeforeEach(func(ctx context.Context) {
var err error
if !testCase.setupRegisty {
return
}
registryAddress, podNodes, err = e2eregistry.SetupRegistry(ctx, f, true)
framework.ExpectNoError(err)
// we need to wait for the registry to be removed and so we need to delete the whole NS ourselves
ginkgo.DeferCleanup(func(ctx context.Context) {
f.DeleteNamespace(ctx, f.Namespace.Name)
})
})
f.It(testCase.description+"", f.WithNodeConformance(), f.WithDisruptive(), f.WithSerial(), func(ctx context.Context) {
name := "image-pull-test"
container := node.ConformanceContainer{
PodClient: e2epod.NewPodClient(f),
Container: v1.Container{
Name: name,
Image: registryAddress + "/" + testCase.image,
// PullAlways makes sure that the image will always be pulled even if it is present before the test.
ImagePullPolicy: v1.PullAlways,
},
RestartPolicy: v1.RestartPolicyNever,
}
if testCase.setupRegisty {
container.NodeName = podNodes[0]
}
auth := e2eregistry.User1DockerSecret(registryAddress).Data[v1.DockerConfigJsonKey]
configFile := filepath.Join(services.KubeletRootDirectory, "config.json")
// the kubelet would cache the config for 5 minutes, let's restart instead
restartKubelet(context.Background(), true)
err := os.WriteFile(configFile, []byte(auth), 0644)
framework.ExpectNoError(err)
ginkgo.DeferCleanup(func() {
framework.ExpectNoError(os.Remove(configFile))
framework.ExpectNoError(os.RemoveAll(filepath.Join(services.KubeletRootDirectory, "image_manager")))
})
// checkContainerStatus checks whether the container status matches expectation.
checkContainerStatus := func(ctx context.Context) error {
status, err := container.GetStatus(ctx)
if err != nil {
return fmt.Errorf("failed to get container status: %w", err)
}
// We need to check container state first. The default pod status is pending, If we check
// pod phase first, and the expected pod phase is Pending, the container status may not
// even show up when we check it.
// Check container state
if !testCase.waiting {
if status.State.Running == nil {
return fmt.Errorf("expected container state: Running, got: %q",
node.GetContainerState(status.State))
}
}
if testCase.waiting {
if status.State.Waiting == nil {
return fmt.Errorf("expected container state: Waiting, got: %q",
node.GetContainerState(status.State))
}
reason := status.State.Waiting.Reason
if reason != images.ErrImagePull.Error() &&
reason != images.ErrImagePullBackOff.Error() {
return fmt.Errorf("unexpected waiting reason: %q", reason)
}
}
// Check pod phase
phase, err := container.GetPhase(ctx)
if err != nil {
return fmt.Errorf("failed to get pod phase: %w", err)
}
if phase != testCase.phase {
return fmt.Errorf("expected pod phase: %q, got: %q", testCase.phase, phase)
}
return nil
}
ginkgo.By("create the container")
container.Create(ctx)
ginkgo.DeferCleanup(func(ctx context.Context) {
ginkgo.By("delete the conformance container")
if err := container.Delete(ctx); err != nil {
framework.Logf("error deleting a conformance container: %v", err)
}
})
ginkgo.By("check the container status")
var latestErr error
err = wait.PollUntilContextTimeout(ctx, node.ContainerStatusPollInterval, node.ContainerStatusRetryTimeout, true, func(ctx context.Context) (bool, error) {
if latestErr = checkContainerStatus(ctx); latestErr != nil {
return false, nil
}
return true, nil
})
if err != nil {
credsContent, readErr := os.ReadFile(configFile)
framework.Logf("credentials read error: %v; credentials used:\n%v", readErr, credsContent)
framework.Failf("Failed to read container status: %v; last observed error from wait loop: %v", err, latestErr)
}
})
}
})
})
})