Merge pull request #24126 from Random-Liu/fix-pull-image

Automatic merge from submit-queue

Fix PullImage and add corresponding node e2e test

Fixes #24101. This is a bug introduced by #23506, since ref #23563.

The root cause of #24101 is described [here](https://github.com/kubernetes/kubernetes/issues/24101#issuecomment-208547623).

This PR
1) Fixes #24101 by decoding the messages returned during pulling image, and return error if any of the messages contains error.
2) Add the node e2e test to detect this kind of failure.
3) Get present check out of `ConformanceImage.Remove()` and `ConformanceImage.Pull()`. Because sometimes we may expect error to occur in `PullImage()` and `RemoveImage()`, but even that doesn't happen, the `Present()` check will still return error and let the test pass.

@yujuhong @freehan @liangchenye 

Also /cc @resouer, because he is doing the image related functions refactoring.
pull/6/head
k8s-merge-robot 2016-04-18 07:05:44 -07:00
commit d37e6ad332
3 changed files with 70 additions and 64 deletions

View File

@ -26,7 +26,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/docker/docker/pkg/stdcopy" dockermessage "github.com/docker/docker/pkg/jsonmessage"
dockerstdcopy "github.com/docker/docker/pkg/stdcopy"
dockerapi "github.com/docker/engine-api/client" dockerapi "github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types" dockertypes "github.com/docker/engine-api/types"
dockerfilters "github.com/docker/engine-api/types/filters" dockerfilters "github.com/docker/engine-api/types/filters"
@ -201,8 +202,21 @@ func (d *kubeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.A
} }
defer resp.Close() defer resp.Close()
// TODO(random-liu): Use the image pulling progress information. // TODO(random-liu): Use the image pulling progress information.
_, err = io.Copy(ioutil.Discard, resp) decoder := json.NewDecoder(resp)
return err for {
var msg dockermessage.JSONMessage
err := decoder.Decode(&msg)
if err == io.EOF {
break
}
if err != nil {
return err
}
if msg.Error != nil {
return msg.Error
}
}
return nil
} }
func (d *kubeDockerClient) RemoveImage(image string) error { func (d *kubeDockerClient) RemoveImage(image string) error {
@ -326,7 +340,7 @@ func (d *kubeDockerClient) redirectResponseToOutputStream(tty bool, outputStream
if tty { if tty {
_, err = io.Copy(outputStream, resp) _, err = io.Copy(outputStream, resp)
} else { } else {
_, err = stdcopy.StdCopy(outputStream, errorStream, resp) _, err = dockerstdcopy.StdCopy(outputStream, errorStream, resp)
} }
return err return err
} }

View File

@ -51,7 +51,9 @@ var _ = Describe("Container Conformance Test", func() {
"gcr.io/google_containers/node-conformance:v3", "gcr.io/google_containers/node-conformance:v3",
"gcr.io/google_containers/node-conformance:v4", "gcr.io/google_containers/node-conformance:v4",
} }
It("it should pull successfully [Conformance]", func() { // TODO(random-liu): Each It should be independent, we should not let them depend on
// each other.
It("should pull successfully [Conformance]", func() {
for _, imageTag := range conformImageTags { for _, imageTag := range conformImageTags {
image, _ := NewConformanceImage("docker", imageTag) image, _ := NewConformanceImage("docker", imageTag)
conformImages = append(conformImages, image) conformImages = append(conformImages, image)
@ -61,46 +63,62 @@ var _ = Describe("Container Conformance Test", func() {
Eventually(func() error { Eventually(func() error {
return image.Pull() return image.Pull()
}, imageRetryTimeout, imagePullInterval).ShouldNot(HaveOccurred()) }, imageRetryTimeout, imagePullInterval).ShouldNot(HaveOccurred())
present, err := image.Present()
Expect(err).ShouldNot(HaveOccurred())
Expect(present).Should(BeTrue())
} }
}) })
It("it should list pulled images [Conformance]", func() { It("should list pulled images [Conformance]", func() {
image, _ := NewConformanceImage("docker", "") image, _ := NewConformanceImage("docker", "")
tags, _ := image.List() tags, _ := image.List()
for _, tag := range conformImageTags { for _, tag := range conformImageTags {
Expect(tags).To(ContainElement(tag)) Expect(tags).To(ContainElement(tag))
} }
}) })
It("it should remove successfully [Conformance]", func() { It("should remove successfully [Conformance]", func() {
for _, image := range conformImages { for _, image := range conformImages {
if err := image.Remove(); err != nil { err := image.Remove()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
break present, err := image.Present()
} Expect(err).NotTo(HaveOccurred())
Expect(present).To(BeFalse())
} }
}) })
}) })
Context("when testing image that does not exist", func() { Context("when testing image that does not exist", func() {
var invalidImage ConformanceImage It("should not pull successfully [Conformance]", func() {
var invalidImageTag string invalidImageTags := []string{
It("it should not pull successfully [Conformance]", func() { // nonexistent image registry
invalidImageTag = "foo.com/foo/foo" "foo.com/foo/foo",
invalidImage, _ = NewConformanceImage("docker", invalidImageTag) // nonexistent image
err := invalidImage.Pull() "gcr.io/google_containers/not_exist",
Expect(err).To(HaveOccurred()) // TODO(random-liu): Add test for image pulling credential
}) }
It("it should not list pulled images [Conformance]", func() { for _, invalidImageTag := range invalidImageTags {
image, _ := NewConformanceImage("docker", "") invalidImage, _ := NewConformanceImage("docker", invalidImageTag)
tags, _ := image.List() By("start pulling image")
Expect(tags).NotTo(ContainElement(invalidImageTag)) err := invalidImage.Pull()
}) Expect(err).Should(HaveOccurred())
It("it should not remove successfully [Conformance]", func() {
err := invalidImage.Remove() By("check image present")
Expect(err).To(HaveOccurred()) present, err := invalidImage.Present()
Expect(err).ShouldNot(HaveOccurred())
Expect(present).To(BeFalse())
By("listing image")
tags, err := invalidImage.List()
Expect(err).ShouldNot(HaveOccurred())
Expect(tags).NotTo(ContainElement(invalidImage))
By("removing image")
err = invalidImage.Remove()
Expect(err).Should(HaveOccurred())
}
}) })
}) })
Context("when running a container that terminates", func() { Context("when running a container that terminates", func() {
var terminateCase ConformanceContainer var terminateCase ConformanceContainer
It("it should run successfully to completion [Conformance]", func() { It("should run successfully to completion [Conformance]", func() {
terminateCase = ConformanceContainer{ terminateCase = ConformanceContainer{
Container: api.Container{ Container: api.Container{
Image: "gcr.io/google_containers/busybox", Image: "gcr.io/google_containers/busybox",
@ -121,19 +139,19 @@ var _ = Describe("Container Conformance Test", func() {
return pod.Phase, err return pod.Phase, err
}, retryTimeout, pollInterval).Should(Equal(terminateCase.Phase)) }, retryTimeout, pollInterval).Should(Equal(terminateCase.Phase))
}) })
It("it should report its phase as 'succeeded' [Conformance]", func() { It("should report its phase as 'succeeded' [Conformance]", func() {
ccontainer, err := terminateCase.Get() ccontainer, err := terminateCase.Get()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(ccontainer).Should(CContainerEqual(terminateCase)) Expect(ccontainer).Should(CContainerEqual(terminateCase))
}) })
It("it should be possible to delete [Conformance]", func() { It("should be possible to delete [Conformance]", func() {
err := terminateCase.Delete() err := terminateCase.Delete()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
}) })
Context("when running a container with invalid image", func() { Context("when running a container with invalid image", func() {
var invalidImageCase ConformanceContainer var invalidImageCase ConformanceContainer
It("it should not start successfully [Conformance]", func() { It("should not start successfully [Conformance]", func() {
invalidImageCase = ConformanceContainer{ invalidImageCase = ConformanceContainer{
Container: api.Container{ Container: api.Container{
Image: "foo.com/foo/foo", Image: "foo.com/foo/foo",
@ -152,12 +170,12 @@ var _ = Describe("Container Conformance Test", func() {
return pod.Phase, err return pod.Phase, err
}, retryTimeout, pollInterval).Should(Equal(invalidImageCase.Phase)) }, retryTimeout, pollInterval).Should(Equal(invalidImageCase.Phase))
}) })
It("it should report its phase as 'pending' [Conformance]", func() { It("should report its phase as 'pending' [Conformance]", func() {
ccontainer, err := invalidImageCase.Get() ccontainer, err := invalidImageCase.Get()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(ccontainer).Should(CContainerEqual(invalidImageCase)) Expect(ccontainer).Should(CContainerEqual(invalidImageCase))
}) })
It("it should be possible to delete [Conformance]", func() { It("should be possible to delete [Conformance]", func() {
err := invalidImageCase.Delete() err := invalidImageCase.Delete()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })

View File

@ -18,7 +18,6 @@ package e2e_node
import ( import (
"fmt" "fmt"
"time"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/dockertools" "k8s.io/kubernetes/pkg/kubelet/dockertools"
@ -55,18 +54,11 @@ func dockerRuntime() kubecontainer.Runtime {
} }
func (ci *ConformanceImage) Pull() error { func (ci *ConformanceImage) Pull() error {
err := ci.Runtime.PullImage(ci.Image, nil) return ci.Runtime.PullImage(ci.Image, nil)
if err != nil { }
return err
}
if present, err := ci.Runtime.IsImagePresent(ci.Image); err != nil { func (ci *ConformanceImage) Present() (bool, error) {
return err return ci.Runtime.IsImagePresent(ci.Image)
} else if !present {
return fmt.Errorf("Failed to detect the pulled image :%s.", ci.Image.Image)
}
return nil
} }
func (ci *ConformanceImage) List() ([]string, error) { func (ci *ConformanceImage) List() ([]string, error) {
@ -82,23 +74,5 @@ func (ci *ConformanceImage) List() ([]string, error) {
} }
func (ci *ConformanceImage) Remove() error { func (ci *ConformanceImage) Remove() error {
ci.Runtime.GarbageCollect(kubecontainer.ContainerGCPolicy{MinAge: time.Second * 30, MaxPerPodContainer: 1, MaxContainers: 0}) return ci.Runtime.RemoveImage(ci.Image)
var err error
for start := time.Now(); time.Since(start) < time.Minute*2; time.Sleep(time.Second * 30) {
if err = ci.Runtime.RemoveImage(ci.Image); err == nil {
break
}
}
if err != nil {
return err
}
if present, err := ci.Runtime.IsImagePresent(ci.Image); err != nil {
return err
} else if present {
return fmt.Errorf("Failed to remove the pulled image %s.", ci.Image.Image)
}
return nil
} }