Add image pulling node e2e test.

This commit is contained in:
Random-Liu
2016-06-14 01:16:13 -07:00
parent 2b65b5a283
commit 89502d3c8c
4 changed files with 143 additions and 116 deletions

View File

@@ -17,16 +17,12 @@ limitations under the License.
package e2e_node package e2e_node
import ( import (
"errors"
"fmt" "fmt"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
apierrs "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
) )
// One pod one container // One pod one container
@@ -35,40 +31,19 @@ type ConformanceContainer struct {
Client *client.Client Client *client.Client
RestartPolicy api.RestartPolicy RestartPolicy api.RestartPolicy
Volumes []api.Volume Volumes []api.Volume
ImagePullSecrets []string
NodeName string NodeName string
Namespace string Namespace string
podName string podName string
} }
type ConformanceContainerEqualMatcher struct {
Expected interface{}
}
func CContainerEqual(expected interface{}) types.GomegaMatcher {
return &ConformanceContainerEqualMatcher{
Expected: expected,
}
}
func (matcher *ConformanceContainerEqualMatcher) Match(actual interface{}) (bool, error) {
if actual == nil && matcher.Expected == nil {
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
}
val := api.Semantic.DeepDerivative(matcher.Expected, actual)
return val, nil
}
func (matcher *ConformanceContainerEqualMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to equal", matcher.Expected)
}
func (matcher *ConformanceContainerEqualMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to equal", matcher.Expected)
}
func (cc *ConformanceContainer) Create() error { func (cc *ConformanceContainer) Create() error {
cc.podName = cc.Container.Name + string(util.NewUUID()) cc.podName = cc.Container.Name + string(util.NewUUID())
imagePullSecrets := []api.LocalObjectReference{}
for _, s := range cc.ImagePullSecrets {
imagePullSecrets = append(imagePullSecrets, api.LocalObjectReference{Name: s})
}
pod := &api.Pod{ pod := &api.Pod{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: cc.podName, Name: cc.podName,
@@ -81,6 +56,7 @@ func (cc *ConformanceContainer) Create() error {
cc.Container, cc.Container,
}, },
Volumes: cc.Volumes, Volumes: cc.Volumes,
ImagePullSecrets: imagePullSecrets,
}, },
} }
@@ -88,26 +64,8 @@ func (cc *ConformanceContainer) Create() error {
return err return err
} }
//Same with 'delete'
func (cc *ConformanceContainer) Stop() error {
return cc.Client.Pods(cc.Namespace).Delete(cc.podName, &api.DeleteOptions{})
}
func (cc *ConformanceContainer) Delete() error { func (cc *ConformanceContainer) Delete() error {
return cc.Client.Pods(cc.Namespace).Delete(cc.podName, &api.DeleteOptions{}) return cc.Client.Pods(cc.Namespace).Delete(cc.podName, api.NewDeleteOptions(0))
}
func (cc *ConformanceContainer) Get() (ConformanceContainer, error) {
pod, err := cc.Client.Pods(cc.Namespace).Get(cc.podName)
if err != nil {
return ConformanceContainer{}, err
}
containers := pod.Spec.Containers
if containers == nil || len(containers) != 1 {
return ConformanceContainer{}, errors.New("Failed to get container")
}
return ConformanceContainer{containers[0], cc.Client, pod.Spec.RestartPolicy, pod.Spec.Volumes, pod.Spec.NodeName, cc.Namespace, cc.podName}, nil
} }
func (cc *ConformanceContainer) IsReady() (bool, error) { func (cc *ConformanceContainer) IsReady() (bool, error) {
@@ -143,7 +101,7 @@ func (cc *ConformanceContainer) Present() (bool, error) {
if err == nil { if err == nil {
return true, nil return true, nil
} }
if apierrs.IsNotFound(err) { if errors.IsNotFound(err) {
return false, nil return false, nil
} }
return false, err return false, err

View File

@@ -38,8 +38,10 @@ const (
pauseImage pauseImage
// Images just used for explicitly testing pulling of images // Images just used for explicitly testing pulling of images
pullTestExecHealthz pullTestAlpine
pullTestAlpineWithBash pullTestAlpineWithBash
pullTestAuthenticatedAlpine
pullTestExecHealthz
) )
var ImageRegistry = map[int]string{ var ImageRegistry = map[int]string{
@@ -51,9 +53,11 @@ var ImageRegistry = map[int]string{
} }
// These are used by tests that explicitly test the ability to pull images // These are used by tests that explicitly test the ability to pull images
var NoPullImagRegistry = map[int]string{ var NoPullImageRegistry = map[int]string{
pullTestAlpineWithBash: "gcr.io/google_containers/alpine-with-bash:1.0",
pullTestExecHealthz: "gcr.io/google_containers/exechealthz:1.0", pullTestExecHealthz: "gcr.io/google_containers/exechealthz:1.0",
pullTestAlpine: "alpine:3.1",
pullTestAlpineWithBash: "gcr.io/google_containers/alpine-with-bash:1.0",
pullTestAuthenticatedAlpine: "gcr.io/authenticated-image-pulling/alpine:3.1",
} }
// Pre-fetch all images tests depend on so that we don't fail in an actual test // Pre-fetch all images tests depend on so that we don't fail in an actual test

View File

@@ -36,8 +36,8 @@ var _ = Describe("Image Container Conformance Test", func() {
var conformImages []ConformanceImage var conformImages []ConformanceImage
BeforeEach(func() { BeforeEach(func() {
existImageTags := []string{ existImageTags := []string{
NoPullImagRegistry[pullTestExecHealthz], NoPullImageRegistry[pullTestExecHealthz],
NoPullImagRegistry[pullTestAlpineWithBash], NoPullImageRegistry[pullTestAlpineWithBash],
} }
for _, existImageTag := range existImageTags { for _, existImageTag := range existImageTags {
conformImage, _ := NewConformanceImage("docker", existImageTag) conformImage, _ := NewConformanceImage("docker", existImageTag)

View File

@@ -24,32 +24,23 @@ import (
"time" "time"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/test/e2e/framework"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
const ( const (
consistentCheckTimeout = time.Second * 10 consistentCheckTimeout = time.Second * 5
retryTimeout = time.Minute * 5 retryTimeout = time.Minute * 5
pollInterval = time.Second * 5 pollInterval = time.Second * 1
) )
type testCase struct { var _ = framework.KubeDescribe("Container Runtime Conformance Test", func() {
Name string
RestartPolicy api.RestartPolicy
Phase api.PodPhase
State ContainerState
RestartCountOper string
RestartCount int32
Ready bool
}
var _ = Describe("Container Runtime Conformance Test", func() {
f := NewDefaultFramework("runtime-conformance") f := NewDefaultFramework("runtime-conformance")
Describe("container runtime conformance blackbox test", func() { Describe("container runtime conformance blackbox test", func() {
Context("when starting a container that exits", func() { Context("when starting a container that exits", func() {
It("it should run with the expected status [Conformance]", func() { It("it should run with the expected status [Conformance]", func() {
restartCountVolumeName := "restart-count" restartCountVolumeName := "restart-count"
@@ -73,10 +64,17 @@ var _ = Describe("Container Runtime Conformance Test", func() {
}, },
}, },
} }
testCases := []testCase{ testCases := []struct {
{"terminate-cmd-rpa", api.RestartPolicyAlways, api.PodRunning, ContainerStateRunning, "==", 2, true}, Name string
{"terminate-cmd-rpof", api.RestartPolicyOnFailure, api.PodSucceeded, ContainerStateTerminated, "==", 1, false}, RestartPolicy api.RestartPolicy
{"terminate-cmd-rpn", api.RestartPolicyNever, api.PodFailed, ContainerStateTerminated, "==", 0, false}, Phase api.PodPhase
State ContainerState
RestartCount int32
Ready bool
}{
{"terminate-cmd-rpa", api.RestartPolicyAlways, api.PodRunning, ContainerStateRunning, 2, true},
{"terminate-cmd-rpof", api.RestartPolicyOnFailure, api.PodSucceeded, ContainerStateTerminated, 1, false},
{"terminate-cmd-rpn", api.RestartPolicyNever, api.PodFailed, ContainerStateTerminated, 0, false},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
tmpFile, err := ioutil.TempFile("", "restartCount") tmpFile, err := ioutil.TempFile("", "restartCount")
@@ -113,7 +111,7 @@ while true; do sleep 1; done
Eventually(func() (int32, error) { Eventually(func() (int32, error) {
status, err := terminateContainer.GetStatus() status, err := terminateContainer.GetStatus()
return status.RestartCount, err return status.RestartCount, err
}, retryTimeout, pollInterval).Should(BeNumerically(testCase.RestartCountOper, testCase.RestartCount)) }, retryTimeout, pollInterval).Should(Equal(testCase.RestartCount))
By("it should get the expected 'Phase'") By("it should get the expected 'Phase'")
Eventually(terminateContainer.GetPhase, retryTimeout, pollInterval).Should(Equal(testCase.Phase)) Eventually(terminateContainer.GetPhase, retryTimeout, pollInterval).Should(Equal(testCase.Phase))
@@ -134,43 +132,110 @@ while true; do sleep 1; done
}) })
}) })
Context("when running a container with invalid image", func() { Context("when running a container with a new image", func() {
It("it should run with the expected status [Conformance]", func() { // The service account only has pull permission
testContainer := api.Container{ auth := `
Image: "foo.com/foo/foo", {
Command: []string{"false"}, "auths": {
"https://gcr.io": {
"auth": "X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXRlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2VpSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0ZjhwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjIxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlhcbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2xSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=",
"email": "image-pulling@authenticated-image-pulling.iam.gserviceaccount.com"
} }
testCase := testCase{"invalid-image-rpa", api.RestartPolicyAlways, api.PodPending, ContainerStateWaiting, "==", 0, false} }
testContainer.Name = testCase.Name }`
invalidImageContainer := ConformanceContainer{ secret := &api.Secret{
Container: testContainer, Data: map[string][]byte{api.DockerConfigJsonKey: []byte(auth)},
Type: api.SecretTypeDockerConfigJson,
}
for _, testCase := range []struct {
description string
image string
secret bool
phase api.PodPhase
state ContainerState
}{
{
description: "should not be able to pull image from invalid registry",
image: "invalid.com/invalid/alpine:3.1",
phase: api.PodPending,
state: ContainerStateWaiting,
},
{
description: "should not be able to pull non-existing image from gcr.io",
image: "gcr.io/google_containers/invalid-image:invalid-tag",
phase: api.PodPending,
state: ContainerStateWaiting,
},
{
description: "should be able to pull image from gcr.io",
image: NoPullImageRegistry[pullTestAlpineWithBash],
phase: api.PodRunning,
state: ContainerStateRunning,
},
{
description: "should be able to pull image from docker hub",
image: NoPullImageRegistry[pullTestAlpine],
phase: api.PodRunning,
state: ContainerStateRunning,
},
{
description: "should not be able to pull from private registry without secret",
image: NoPullImageRegistry[pullTestAuthenticatedAlpine],
phase: api.PodPending,
state: ContainerStateWaiting,
},
{
description: "should be able to pull from private registry with secret",
image: NoPullImageRegistry[pullTestAuthenticatedAlpine],
secret: true,
phase: api.PodRunning,
state: ContainerStateRunning,
},
} {
testCase := testCase
It(testCase.description, func() {
name := "image-pull-test"
command := []string{"/bin/sh", "-c", "while true; do sleep 1; done"}
container := ConformanceContainer{
Container: api.Container{
Name: name,
Image: testCase.image,
Command: command,
// PullAlways makes sure that the image will always be pulled even if it is present before the test.
ImagePullPolicy: api.PullAlways,
},
Client: f.Client, Client: f.Client,
RestartPolicy: testCase.RestartPolicy, RestartPolicy: api.RestartPolicyNever,
NodeName: *nodeName, NodeName: *nodeName,
Namespace: f.Namespace.Name, Namespace: f.Namespace.Name,
} }
Expect(invalidImageContainer.Create()).To(Succeed()) if testCase.secret {
defer invalidImageContainer.Delete() secret.Name = "image-pull-secret-" + string(util.NewUUID())
By("create image pull secret")
Eventually(invalidImageContainer.GetPhase, retryTimeout, pollInterval).Should(Equal(testCase.Phase)) _, err := f.Client.Secrets(f.Namespace.Name).Create(secret)
Consistently(invalidImageContainer.GetPhase, consistentCheckTimeout, pollInterval).Should(Equal(testCase.Phase))
status, err := invalidImageContainer.GetStatus()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
defer f.Client.Secrets(f.Namespace.Name).Delete(secret.Name)
container.ImagePullSecrets = []string{secret.Name}
}
By("it should get the expected 'RestartCount'") By("create the container")
Expect(status.RestartCount).To(BeNumerically(testCase.RestartCountOper, testCase.RestartCount)) Expect(container.Create()).To(Succeed())
defer container.Delete()
By("it should get the expected 'Ready' status") By("check the pod phase")
Expect(status.Ready).To(Equal(testCase.Ready)) Eventually(container.GetPhase, retryTimeout, pollInterval).Should(Equal(testCase.phase))
Consistently(container.GetPhase, consistentCheckTimeout, pollInterval).Should(Equal(testCase.phase))
By("it should get the expected 'State'") By("check the container state")
Expect(GetContainerState(status.State)).To(Equal(testCase.State)) status, err := container.GetStatus()
Expect(err).NotTo(HaveOccurred())
Expect(GetContainerState(status.State)).To(Equal(testCase.state))
By("it should be possible to delete [Conformance]") By("it should be possible to delete")
Expect(invalidImageContainer.Delete()).To(Succeed()) Expect(container.Delete()).To(Succeed())
Eventually(invalidImageContainer.Present, retryTimeout, pollInterval).Should(BeFalse()) Eventually(container.Present, retryTimeout, pollInterval).Should(BeFalse())
}) })
}
}) })
}) })
}) })