diff --git a/test/e2e/framework/pod/resource.go b/test/e2e/framework/pod/resource.go index 0f29b78c78b..9dc8398f78c 100644 --- a/test/e2e/framework/pod/resource.go +++ b/test/e2e/framework/pod/resource.go @@ -335,6 +335,23 @@ func podContainerStarted(c clientset.Interface, namespace, podName string, conta } } +func isContainerRunning(c clientset.Interface, namespace, podName, containerName string) wait.ConditionFunc { + return func() (bool, error) { + pod, err := c.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + if err != nil { + return false, err + } + for _, statuses := range [][]v1.ContainerStatus{pod.Status.ContainerStatuses, pod.Status.InitContainerStatuses, pod.Status.EphemeralContainerStatuses} { + for _, cs := range statuses { + if cs.Name == containerName { + return cs.State.Running != nil, nil + } + } + } + return false, nil + } +} + // LogPodStates logs basic info of provided pods for debugging. func LogPodStates(pods []v1.Pod) { // Find maximum widths for pod, node, and phase strings for column printing. diff --git a/test/e2e/framework/pod/wait.go b/test/e2e/framework/pod/wait.go index d4780bffc5d..17fca2daa11 100644 --- a/test/e2e/framework/pod/wait.go +++ b/test/e2e/framework/pod/wait.go @@ -565,3 +565,8 @@ func WaitForPodFailedReason(c clientset.Interface, pod *v1.Pod, reason string, t } return nil } + +// WaitForContainerRunning waits for the given Pod container to have a state of running +func WaitForContainerRunning(c clientset.Interface, namespace, podName, containerName string, timeout time.Duration) error { + return wait.PollImmediate(poll, timeout, isContainerRunning(c, namespace, podName, containerName)) +} diff --git a/test/e2e/framework/pods.go b/test/e2e/framework/pods.go index 97c10c1e25c..68a06707b1f 100644 --- a/test/e2e/framework/pods.go +++ b/test/e2e/framework/pods.go @@ -18,6 +18,7 @@ package framework import ( "context" + "encoding/json" "fmt" "regexp" "sync" @@ -27,7 +28,9 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes/scheme" v1core "k8s.io/client-go/kubernetes/typed/core/v1" @@ -146,6 +149,27 @@ func (c *PodClient) Update(name string, updateFn func(pod *v1.Pod)) { })) } +// AddEphemeralContainerSync adds an EphemeralContainer to a pod and waits for it to be running. +func (c *PodClient) AddEphemeralContainerSync(pod *v1.Pod, ec *v1.EphemeralContainer, timeout time.Duration) { + namespace := c.f.Namespace.Name + + podJS, err := json.Marshal(pod) + ExpectNoError(err, "error creating JSON for pod: %v", err) + + ecPod := pod.DeepCopy() + ecPod.Spec.EphemeralContainers = append(ecPod.Spec.EphemeralContainers, *ec) + ecJS, err := json.Marshal(ecPod) + ExpectNoError(err, "error creating JSON for pod with ephemeral container: %v", err) + + patch, err := strategicpatch.CreateTwoWayMergePatch(podJS, ecJS, pod) + ExpectNoError(err, "error creating patch to add ephemeral container: %v", err) + + _, err = c.Patch(context.TODO(), pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "ephemeralcontainers") + ExpectNoError(err, "Failed to patch ephemeral containers in pod %q: %v", pod.Name, err) + + ExpectNoError(e2epod.WaitForContainerRunning(c.f.ClientSet, namespace, pod.Name, ec.Name, timeout)) +} + // DeleteSync deletes the pod and wait for the pod to disappear for `timeout`. If the pod doesn't // disappear before the timeout, it will fail the test. func (c *PodClient) DeleteSync(name string, options metav1.DeleteOptions, timeout time.Duration) { diff --git a/test/e2e_node/ephemeral_containers_test.go b/test/e2e_node/ephemeral_containers_test.go new file mode 100644 index 00000000000..5d9ecc1e402 --- /dev/null +++ b/test/e2e_node/ephemeral_containers_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2017 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 ( + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + imageutils "k8s.io/kubernetes/test/utils/image" + + "github.com/onsi/ginkgo" +) + +var _ = SIGDescribe("Ephemeral Containers [Feature:EphemeralContainers][NodeAlphaFeature:EphemeralContainers]", func() { + f := framework.NewDefaultFramework("ephemeral-containers-test") + var podClient *framework.PodClient + ginkgo.BeforeEach(func() { + podClient = f.PodClient() + }) + + ginkgo.It("will start an ephemeral container in an existing pod", func() { + ginkgo.By("creating a target pod") + pod := podClient.CreateSync(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "ephemeral-containers-target-pod"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test-container-1", + Image: imageutils.GetE2EImage(imageutils.BusyBox), + Command: []string{"/bin/sleep"}, + Args: []string{"10000"}, + }, + }, + }, + }) + + ginkgo.By("adding an ephemeral container") + ec := &v1.EphemeralContainer{ + EphemeralContainerCommon: v1.EphemeralContainerCommon{ + Name: "debugger", + Image: imageutils.GetE2EImage(imageutils.BusyBox), + Command: []string{"/bin/sh"}, + Stdin: true, + TTY: true, + }, + } + podClient.AddEphemeralContainerSync(pod, ec, time.Minute) + + ginkgo.By("confirm that the container is really running") + marco := f.ExecCommandInContainer(pod.Name, "debugger", "/bin/echo", "polo") + framework.ExpectEqual(marco, "polo") + }) +})