Add container lifecycle hook test.

This commit is contained in:
Random-Liu 2016-09-23 13:36:37 -07:00
parent 72524e45b5
commit 5eb41e9acb
4 changed files with 201 additions and 2 deletions

View File

@ -63,14 +63,14 @@ func (hr *HandlerRunner) Run(containerID kubecontainer.ContainerID, pod *api.Pod
output := ioutils.WriteCloserWrapper(&buffer) output := ioutils.WriteCloserWrapper(&buffer)
err := hr.commandRunner.ExecInContainer(containerID, handler.Exec.Command, nil, output, output, false, nil) err := hr.commandRunner.ExecInContainer(containerID, handler.Exec.Command, nil, output, output, false, nil)
if err != nil { if err != nil {
msg := fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - %q", handler.Exec.Command, container.Name, format.Pod(pod), buffer.String()) msg := fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - error: %v, message: %q", handler.Exec.Command, container.Name, format.Pod(pod), err, buffer.String())
glog.V(1).Infof(msg) glog.V(1).Infof(msg)
} }
return msg, err return msg, err
case handler.HTTPGet != nil: case handler.HTTPGet != nil:
msg, err := hr.runHTTPHandler(pod, container, handler) msg, err := hr.runHTTPHandler(pod, container, handler)
if err != nil { if err != nil {
msg := fmt.Sprintf("Http lifecycle hook (%s) for Container %q in Pod %q failed - %q", handler.HTTPGet.Path, container.Name, format.Pod(pod), msg) msg := fmt.Sprintf("Http lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", handler.HTTPGet.Path, container.Name, format.Pod(pod), err, msg)
glog.V(1).Infof(msg) glog.V(1).Infof(msg)
} }
return msg, err return msg, err

View File

@ -149,3 +149,19 @@ func (c *PodClient) mungeSpec(pod *api.Pod) {
} }
// TODO(random-liu): Move pod wait function into this file // TODO(random-liu): Move pod wait function into this file
// WaitForSuccess waits for pod to success.
func (c *PodClient) WaitForSuccess(name string, timeout time.Duration) {
f := c.f
Expect(waitForPodCondition(f.Client, f.Namespace.Name, name, "success or failure", timeout,
func(pod *api.Pod) (bool, error) {
switch pod.Status.Phase {
case api.PodFailed:
return true, fmt.Errorf("pod %q failed with reason: %q, message: %q", name, pod.Status.Reason, pod.Status.Message)
case api.PodSucceeded:
return true, nil
default:
return false, nil
}
},
)).To(Succeed(), "wait for pod %q to success", name)
}

View File

@ -1456,6 +1456,7 @@ func waitForPodTerminatedInNamespace(c *client.Client, podName, reason, namespac
func waitForPodSuccessInNamespaceTimeout(c *client.Client, podName string, contName string, namespace string, timeout time.Duration) error { func waitForPodSuccessInNamespaceTimeout(c *client.Client, podName string, contName string, namespace string, timeout time.Duration) error {
return waitForPodCondition(c, namespace, podName, "success or failure", timeout, func(pod *api.Pod) (bool, error) { return waitForPodCondition(c, namespace, podName, "success or failure", timeout, func(pod *api.Pod) (bool, error) {
// Cannot use pod.Status.Phase == api.PodSucceeded/api.PodFailed due to #2632 // Cannot use pod.Status.Phase == api.PodSucceeded/api.PodFailed due to #2632
// TODO: This was not true from long time ago. We can use api.PodSucceeded now.
ci, ok := api.GetContainerStatus(pod.Status.ContainerStatuses, contName) ci, ok := api.GetContainerStatus(pod.Status.ContainerStatuses, contName)
if !ok { if !ok {
Logf("No Status.Info for container '%s' in pod '%s' yet", contName, podName) Logf("No Status.Info for container '%s' in pod '%s' yet", contName, podName)

View File

@ -0,0 +1,182 @@
/*
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 e2e_node
import (
"fmt"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/uuid"
"k8s.io/kubernetes/test/e2e/framework"
. "github.com/onsi/ginkgo"
)
var _ = framework.KubeDescribe("Container Lifecycle Hook", func() {
f := framework.NewDefaultFramework("container-lifecycle-hook")
var podClient *framework.PodClient
var file string
const podWaitTimeout = 2 * time.Minute
testPodWithHook := func(podWithHook *api.Pod) {
podCheckHook := getLifecycleHookTestPod("pod-check-hook",
// Wait until the file is created.
[]string{"sh", "-c", fmt.Sprintf("while [ ! -e %s ]; do sleep 1; done", file)},
)
By("create the pod with lifecycle hook")
podClient.CreateSync(podWithHook)
if podWithHook.Spec.Containers[0].Lifecycle.PostStart != nil {
By("create the hook check pod")
podClient.Create(podCheckHook)
By("wait for the hook check pod to success")
podClient.WaitForSuccess(podCheckHook.Name, podWaitTimeout)
}
By("delete the pod with lifecycle hook")
podClient.DeleteSync(podWithHook.Name, api.NewDeleteOptions(15), podWaitTimeout)
if podWithHook.Spec.Containers[0].Lifecycle.PreStop != nil {
By("create the hook check pod")
podClient.Create(podCheckHook)
By("wait for the prestop check pod to success")
podClient.WaitForSuccess(podCheckHook.Name, podWaitTimeout)
}
}
Context("when create a pod with lifecycle hook", func() {
BeforeEach(func() {
podClient = f.PodClient()
file = "/tmp/test-" + string(uuid.NewUUID())
})
AfterEach(func() {
By("cleanup the temporary file created in the test.")
cleanupPod := getLifecycleHookTestPod("pod-clean-up", []string{"rm", file})
podClient.Create(cleanupPod)
podClient.WaitForSuccess(cleanupPod.Name, podWaitTimeout)
})
Context("when it is exec hook", func() {
It("should execute poststart exec hook properly", func() {
podWithHook := getLifecycleHookTestPod("pod-with-poststart-exec-hook",
// Block forever
[]string{"tail", "-f", "/dev/null"},
)
podWithHook.Spec.Containers[0].Lifecycle = &api.Lifecycle{
PostStart: &api.Handler{
Exec: &api.ExecAction{Command: []string{"touch", file}},
},
}
testPodWithHook(podWithHook)
})
It("should execute prestop exec hook properly", func() {
podWithHook := getLifecycleHookTestPod("pod-with-prestop-exec-hook",
// Block forever
[]string{"tail", "-f", "/dev/null"},
)
podWithHook.Spec.Containers[0].Lifecycle = &api.Lifecycle{
PreStop: &api.Handler{
Exec: &api.ExecAction{Command: []string{"touch", file}},
},
}
testPodWithHook(podWithHook)
})
})
Context("when it is http hook", func() {
var targetIP string
BeforeEach(func() {
By("cleanup the container to handle the HTTPGet hook request.")
podHandleHookRequest := getLifecycleHookTestPod("pod-handle-http-request",
[]string{"sh", "-c",
// Create test file when receive request on 1234.
fmt.Sprintf("echo -e \"HTTP/1.1 200 OK\n\" | nc -l -p 1234; touch %s", file),
},
)
podHandleHookRequest.Spec.Containers[0].Ports = []api.ContainerPort{
{
ContainerPort: 1234,
Protocol: api.ProtocolTCP,
},
}
podHandleHookRequest = podClient.CreateSync(podHandleHookRequest)
targetIP = podHandleHookRequest.Status.PodIP
})
It("should execute poststart http hook properly", func() {
podWithHook := getLifecycleHookTestPod("pod-with-poststart-http-hook",
// Block forever
[]string{"tail", "-f", "/dev/null"},
)
podWithHook.Spec.Containers[0].Lifecycle = &api.Lifecycle{
PostStart: &api.Handler{
HTTPGet: &api.HTTPGetAction{
Host: targetIP,
Port: intstr.FromInt(1234),
},
},
}
testPodWithHook(podWithHook)
})
It("should execute prestop http hook properly", func() {
podWithHook := getLifecycleHookTestPod("pod-with-prestop-http-hook",
// Block forever
[]string{"tail", "-f", "/dev/null"},
)
podWithHook.Spec.Containers[0].Lifecycle = &api.Lifecycle{
PreStop: &api.Handler{
HTTPGet: &api.HTTPGetAction{
Host: targetIP,
Port: intstr.FromInt(1234),
},
},
}
testPodWithHook(podWithHook)
})
})
})
})
func getLifecycleHookTestPod(name string, cmd []string) *api.Pod {
return &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: name,
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: "gcr.io/google_containers/busybox:1.24",
VolumeMounts: []api.VolumeMount{
{
Name: "tmpfs",
MountPath: "/tmp",
},
},
Command: cmd,
},
},
RestartPolicy: api.RestartPolicyNever,
Volumes: []api.Volume{
{
Name: "tmpfs",
VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/tmp"}},
},
},
},
}
}