From 24f374a3953a069624c0aa636bcc9a677f615a24 Mon Sep 17 00:00:00 2001 From: SataQiu Date: Tue, 29 Oct 2019 13:04:59 +0800 Subject: [PATCH] move test specific functions from rc_util.go --- test/e2e/apps/network_partition.go | 14 ++- test/e2e/apps/rc.go | 32 +++++- test/e2e/cloud/gcp/BUILD | 1 + test/e2e/cloud/gcp/addon_update.go | 47 +++++++- test/e2e/common/util.go | 14 ++- test/e2e/framework/rc_util.go | 174 ---------------------------- test/e2e/kubectl/kubectl.go | 175 ++++++++++++++++++++--------- 7 files changed, 224 insertions(+), 233 deletions(-) diff --git a/test/e2e/apps/network_partition.go b/test/e2e/apps/network_partition.go index 6040f3d67b3..093c225073c 100644 --- a/test/e2e/apps/network_partition.go +++ b/test/e2e/apps/network_partition.go @@ -269,7 +269,7 @@ var _ = SIGDescribe("Network Partition [Disruptive] [Slow]", func() { ginkgo.By(fmt.Sprintf("blocking network traffic from node %s", node.Name)) framework.TestUnderTemporaryNetworkFailure(c, ns, node, func() { framework.Logf("Waiting for pod %s to be removed", pods.Items[0].Name) - err := framework.WaitForRCPodToDisappear(c, ns, name, pods.Items[0].Name) + err := waitForRCPodToDisappear(c, ns, name, pods.Items[0].Name) framework.ExpectNoError(err) ginkgo.By("verifying whether the pod from the unreachable node is recreated") @@ -338,7 +338,7 @@ var _ = SIGDescribe("Network Partition [Disruptive] [Slow]", func() { ginkgo.By(fmt.Sprintf("blocking network traffic from node %s", node.Name)) framework.TestUnderTemporaryNetworkFailure(c, ns, node, func() { framework.Logf("Waiting for pod %s to be removed", pods.Items[0].Name) - err := framework.WaitForRCPodToDisappear(c, ns, name, pods.Items[0].Name) + err := waitForRCPodToDisappear(c, ns, name, pods.Items[0].Name) framework.ExpectEqual(err, wait.ErrWaitTimeout, "Pod was not deleted during network partition.") ginkgo.By(fmt.Sprintf("verifying that there are %v running pods during partition", replicas)) @@ -658,3 +658,13 @@ var _ = SIGDescribe("Network Partition [Disruptive] [Slow]", func() { }) }) }) + +// waitForRCPodToDisappear returns nil if the pod from the given replication controller (described by rcName) no longer exists. +// In case of failure or too long waiting time, an error is returned. +func waitForRCPodToDisappear(c clientset.Interface, ns, rcName, podName string) error { + label := labels.SelectorFromSet(labels.Set(map[string]string{"name": rcName})) + // NodeController evicts pod after 5 minutes, so we need timeout greater than that to observe effects. + // The grace period must be set to 0 on the pod for it to be deleted during the partition. + // Otherwise, it goes to the 'Terminating' state till the kubelet confirms deletion. + return e2epod.WaitForPodToDisappear(c, ns, podName, label, 20*time.Second, 10*time.Minute) +} diff --git a/test/e2e/apps/rc.go b/test/e2e/apps/rc.go index fbafd5d0986..3da81a4a35e 100644 --- a/test/e2e/apps/rc.go +++ b/test/e2e/apps/rc.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/wait" + clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/controller/replication" "k8s.io/kubernetes/test/e2e/framework" e2epod "k8s.io/kubernetes/test/e2e/framework/pod" @@ -227,7 +228,7 @@ func testReplicationControllerConditionCheck(f *framework.Framework) { framework.ExpectNoError(err) ginkgo.By(fmt.Sprintf("Scaling down rc %q to satisfy pod quota", name)) - rc, err = framework.UpdateReplicationControllerWithRetries(c, namespace, name, func(update *v1.ReplicationController) { + rc, err = updateReplicationControllerWithRetries(c, namespace, name, func(update *v1.ReplicationController) { x := int32(2) update.Spec.Replicas = &x }) @@ -348,3 +349,32 @@ func testRCReleaseControlledNotMatching(f *framework.Framework) { }) framework.ExpectNoError(err) } + +type updateRcFunc func(d *v1.ReplicationController) + +// updateReplicationControllerWithRetries retries updating the given rc on conflict with the following steps: +// 1. Get latest resource +// 2. applyUpdate +// 3. Update the resource +func updateReplicationControllerWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateRcFunc) (*v1.ReplicationController, error) { + var rc *v1.ReplicationController + var updateErr error + pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) { + var err error + if rc, err = c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{}); err != nil { + return false, err + } + // Apply the update, then attempt to push it to the apiserver. + applyUpdate(rc) + if rc, err = c.CoreV1().ReplicationControllers(namespace).Update(rc); err == nil { + framework.Logf("Updating replication controller %q", name) + return true, nil + } + updateErr = err + return false, nil + }) + if pollErr == wait.ErrWaitTimeout { + pollErr = fmt.Errorf("couldn't apply the provided updated to rc %q: %v", name, updateErr) + } + return rc, pollErr +} diff --git a/test/e2e/cloud/gcp/BUILD b/test/e2e/cloud/gcp/BUILD index 605663aa131..74b388f1205 100644 --- a/test/e2e/cloud/gcp/BUILD +++ b/test/e2e/cloud/gcp/BUILD @@ -24,6 +24,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/client-go/discovery:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//test/e2e/chaosmonkey:go_default_library", diff --git a/test/e2e/cloud/gcp/addon_update.go b/test/e2e/cloud/gcp/addon_update.go index d86b20d335e..18e71483edf 100644 --- a/test/e2e/cloud/gcp/addon_update.go +++ b/test/e2e/cloud/gcp/addon_update.go @@ -27,6 +27,7 @@ import ( "golang.org/x/crypto/ssh" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/test/e2e/framework" e2essh "k8s.io/kubernetes/test/e2e/framework/ssh" @@ -345,7 +346,7 @@ func waitForServiceInAddonTest(c clientset.Interface, addonNamespace, name strin } func waitForReplicationControllerInAddonTest(c clientset.Interface, addonNamespace, name string, exist bool) { - framework.ExpectNoError(framework.WaitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout)) + framework.ExpectNoError(waitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout)) } func waitForServicewithSelectorInAddonTest(c clientset.Interface, addonNamespace string, exist bool, selector labels.Selector) { @@ -353,10 +354,52 @@ func waitForServicewithSelectorInAddonTest(c clientset.Interface, addonNamespace } func waitForReplicationControllerwithSelectorInAddonTest(c clientset.Interface, addonNamespace string, exist bool, selector labels.Selector) { - framework.ExpectNoError(framework.WaitForReplicationControllerwithSelector(c, addonNamespace, selector, exist, addonTestPollInterval, + framework.ExpectNoError(waitForReplicationControllerWithSelector(c, addonNamespace, selector, exist, addonTestPollInterval, addonTestPollTimeout)) } +// waitForReplicationController waits until the RC appears (exist == true), or disappears (exist == false) +func waitForReplicationController(c clientset.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error { + err := wait.PollImmediate(interval, timeout, func() (bool, error) { + _, err := c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + framework.Logf("Get ReplicationController %s in namespace %s failed (%v).", name, namespace, err) + return !exist, nil + } + framework.Logf("ReplicationController %s in namespace %s found.", name, namespace) + return exist, nil + }) + if err != nil { + stateMsg := map[bool]string{true: "to appear", false: "to disappear"} + return fmt.Errorf("error waiting for ReplicationController %s/%s %s: %v", namespace, name, stateMsg[exist], err) + } + return nil +} + +// waitForReplicationControllerWithSelector waits until any RC with given selector appears (exist == true), or disappears (exist == false) +func waitForReplicationControllerWithSelector(c clientset.Interface, namespace string, selector labels.Selector, exist bool, interval, + timeout time.Duration) error { + err := wait.PollImmediate(interval, timeout, func() (bool, error) { + rcs, err := c.CoreV1().ReplicationControllers(namespace).List(metav1.ListOptions{LabelSelector: selector.String()}) + switch { + case len(rcs.Items) != 0: + framework.Logf("ReplicationController with %s in namespace %s found.", selector.String(), namespace) + return exist, nil + case len(rcs.Items) == 0: + framework.Logf("ReplicationController with %s in namespace %s disappeared.", selector.String(), namespace) + return !exist, nil + default: + framework.Logf("List ReplicationController with %s in namespace %s failed: %v", selector.String(), namespace, err) + return false, nil + } + }) + if err != nil { + stateMsg := map[bool]string{true: "to appear", false: "to disappear"} + return fmt.Errorf("error waiting for ReplicationControllers with %s in namespace %s %s: %v", selector.String(), namespace, stateMsg[exist], err) + } + return nil +} + // TODO use the ssh.SSH code, either adding an SCP to it or copying files // differently. func getMasterSSHClient() (*ssh.Client, error) { diff --git a/test/e2e/common/util.go b/test/e2e/common/util.go index f1fb06162f4..55b0b574d29 100644 --- a/test/e2e/common/util.go +++ b/test/e2e/common/util.go @@ -146,7 +146,7 @@ func NewRCByName(c clientset.Interface, ns, name string, replicas int32, gracePe containerArgs = []string{"serve-hostname"} } - return c.CoreV1().ReplicationControllers(ns).Create(framework.RcByNamePort( + return c.CoreV1().ReplicationControllers(ns).Create(rcByNamePort( name, replicas, framework.ServeHostnameImage, containerArgs, 9376, v1.ProtocolTCP, map[string]string{}, gracePeriod)) } @@ -194,3 +194,15 @@ func RestartNodes(c clientset.Interface, nodes []v1.Node) error { } return nil } + +// rcByNamePort returns a ReplicationController with specified name and port +func rcByNamePort(name string, replicas int32, image string, containerArgs []string, port int, protocol v1.Protocol, + labels map[string]string, gracePeriod *int64) *v1.ReplicationController { + + return framework.RcByNameContainer(name, replicas, image, labels, v1.Container{ + Name: name, + Image: image, + Args: containerArgs, + Ports: []v1.ContainerPort{{ContainerPort: int32(port), Protocol: protocol}}, + }, gracePeriod) +} diff --git a/test/e2e/framework/rc_util.go b/test/e2e/framework/rc_util.go index 8cfe7edfbe8..7176e4477f4 100644 --- a/test/e2e/framework/rc_util.go +++ b/test/e2e/framework/rc_util.go @@ -18,34 +18,17 @@ package framework import ( "fmt" - "strings" - "time" "github.com/onsi/ginkgo" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" scaleclient "k8s.io/client-go/scale" api "k8s.io/kubernetes/pkg/apis/core" - e2epod "k8s.io/kubernetes/test/e2e/framework/pod" testutils "k8s.io/kubernetes/test/utils" ) -// RcByNamePort returns a ReplicationController with specified name and port -func RcByNamePort(name string, replicas int32, image string, containerArgs []string, port int, protocol v1.Protocol, - labels map[string]string, gracePeriod *int64) *v1.ReplicationController { - - return RcByNameContainer(name, replicas, image, labels, v1.Container{ - Name: name, - Image: image, - Args: containerArgs, - Ports: []v1.ContainerPort{{ContainerPort: int32(port), Protocol: protocol}}, - }, gracePeriod) -} - // RcByNameContainer returns a ReplicationController with specified name and container func RcByNameContainer(name string, replicas int32, image string, labels map[string]string, c v1.Container, gracePeriod *int64) *v1.ReplicationController { @@ -83,35 +66,6 @@ func RcByNameContainer(name string, replicas int32, image string, labels map[str } } -type updateRcFunc func(d *v1.ReplicationController) - -// UpdateReplicationControllerWithRetries retries updating the given rc on conflict with the following steps: -// 1. Get latest resource -// 2. applyUpdate -// 3. Update the resource -func UpdateReplicationControllerWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateRcFunc) (*v1.ReplicationController, error) { - var rc *v1.ReplicationController - var updateErr error - pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) { - var err error - if rc, err = c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{}); err != nil { - return false, err - } - // Apply the update, then attempt to push it to the apiserver. - applyUpdate(rc) - if rc, err = c.CoreV1().ReplicationControllers(namespace).Update(rc); err == nil { - Logf("Updating replication controller %q", name) - return true, nil - } - updateErr = err - return false, nil - }) - if pollErr == wait.ErrWaitTimeout { - pollErr = fmt.Errorf("couldn't apply the provided updated to rc %q: %v", name, updateErr) - } - return rc, pollErr -} - // DeleteRCAndWaitForGC deletes only the Replication Controller and waits for GC to delete the pods. func DeleteRCAndWaitForGC(c clientset.Interface, ns, name string) error { return DeleteResourceAndWaitForGC(c, api.Kind("ReplicationController"), ns, name) @@ -130,131 +84,3 @@ func RunRC(config testutils.RCConfig) error { config.ContainerDumpFunc = LogFailedContainers return testutils.RunRC(config) } - -// WaitForRCPodToDisappear returns nil if the pod from the given replication controller (described by rcName) no longer exists. -// In case of failure or too long waiting time, an error is returned. -func WaitForRCPodToDisappear(c clientset.Interface, ns, rcName, podName string) error { - label := labels.SelectorFromSet(labels.Set(map[string]string{"name": rcName})) - // NodeController evicts pod after 5 minutes, so we need timeout greater than that to observe effects. - // The grace period must be set to 0 on the pod for it to be deleted during the partition. - // Otherwise, it goes to the 'Terminating' state till the kubelet confirms deletion. - return e2epod.WaitForPodToDisappear(c, ns, podName, label, 20*time.Second, 10*time.Minute) -} - -// WaitForReplicationController waits until the RC appears (exist == true), or disappears (exist == false) -func WaitForReplicationController(c clientset.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error { - err := wait.PollImmediate(interval, timeout, func() (bool, error) { - _, err := c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - Logf("Get ReplicationController %s in namespace %s failed (%v).", name, namespace, err) - return !exist, nil - } - Logf("ReplicationController %s in namespace %s found.", name, namespace) - return exist, nil - }) - if err != nil { - stateMsg := map[bool]string{true: "to appear", false: "to disappear"} - return fmt.Errorf("error waiting for ReplicationController %s/%s %s: %v", namespace, name, stateMsg[exist], err) - } - return nil -} - -// WaitForReplicationControllerwithSelector waits until any RC with given selector appears (exist == true), or disappears (exist == false) -func WaitForReplicationControllerwithSelector(c clientset.Interface, namespace string, selector labels.Selector, exist bool, interval, - timeout time.Duration) error { - err := wait.PollImmediate(interval, timeout, func() (bool, error) { - rcs, err := c.CoreV1().ReplicationControllers(namespace).List(metav1.ListOptions{LabelSelector: selector.String()}) - switch { - case len(rcs.Items) != 0: - Logf("ReplicationController with %s in namespace %s found.", selector.String(), namespace) - return exist, nil - case len(rcs.Items) == 0: - Logf("ReplicationController with %s in namespace %s disappeared.", selector.String(), namespace) - return !exist, nil - default: - Logf("List ReplicationController with %s in namespace %s failed: %v", selector.String(), namespace, err) - return false, nil - } - }) - if err != nil { - stateMsg := map[bool]string{true: "to appear", false: "to disappear"} - return fmt.Errorf("error waiting for ReplicationControllers with %s in namespace %s %s: %v", selector.String(), namespace, stateMsg[exist], err) - } - return nil -} - -// trimDockerRegistry is the function for trimming the docker.io/library from the beginning of the imagename. -// If community docker installed it will not prefix the registry names with the dockerimages vs registry names prefixed with other runtimes or docker installed via RHEL extra repo. -// So this function will help to trim the docker.io/library if exists -func trimDockerRegistry(imagename string) string { - imagename = strings.Replace(imagename, "docker.io/", "", 1) - return strings.Replace(imagename, "library/", "", 1) -} - -// validatorFn is the function which is individual tests will implement. -// we may want it to return more than just an error, at some point. -type validatorFn func(c clientset.Interface, podID string) error - -// ValidateController is a generic mechanism for testing RC's that are running. -// It takes a container name, a test name, and a validator function which is plugged in by a specific test. -// "containername": this is grepped for. -// "containerImage" : this is the name of the image we expect to be launched. Not to confuse w/ images (kitten.jpg) which are validated. -// "testname": which gets bubbled up to the logging/failure messages if errors happen. -// "validator" function: This function is given a podID and a client, and it can do some specific validations that way. -func ValidateController(c clientset.Interface, containerImage string, replicas int, containername string, testname string, validator validatorFn, ns string) { - containerImage = trimDockerRegistry(containerImage) - getPodsTemplate := "--template={{range.items}}{{.metadata.name}} {{end}}" - // NB: kubectl adds the "exists" function to the standard template functions. - // This lets us check to see if the "running" entry exists for each of the containers - // we care about. Exists will never return an error and it's safe to check a chain of - // things, any one of which may not exist. In the below template, all of info, - // containername, and running might be nil, so the normal index function isn't very - // helpful. - // This template is unit-tested in kubectl, so if you change it, update the unit test. - // You can read about the syntax here: http://golang.org/pkg/text/template/. - getContainerStateTemplate := fmt.Sprintf(`--template={{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "%s") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`, containername) - - getImageTemplate := fmt.Sprintf(`--template={{if (exists . "spec" "containers")}}{{range .spec.containers}}{{if eq .name "%s"}}{{.image}}{{end}}{{end}}{{end}}`, containername) - - ginkgo.By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector -waitLoop: - for start := time.Now(); time.Since(start) < PodStartTimeout; time.Sleep(5 * time.Second) { - getPodsOutput := RunKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "-l", testname, fmt.Sprintf("--namespace=%v", ns)) - pods := strings.Fields(getPodsOutput) - if numPods := len(pods); numPods != replicas { - ginkgo.By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods)) - continue - } - var runningPods []string - for _, podID := range pods { - running := RunKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, fmt.Sprintf("--namespace=%v", ns)) - if running != "true" { - Logf("%s is created but not running", podID) - continue waitLoop - } - - currentImage := RunKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, fmt.Sprintf("--namespace=%v", ns)) - currentImage = trimDockerRegistry(currentImage) - if currentImage != containerImage { - Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage) - continue waitLoop - } - - // Call the generic validator function here. - // This might validate for example, that (1) getting a url works and (2) url is serving correct content. - if err := validator(c, podID); err != nil { - Logf("%s is running right image but validator function failed: %v", podID, err) - continue waitLoop - } - - Logf("%s is verified up and running", podID) - runningPods = append(runningPods, podID) - } - // If we reach here, then all our checks passed. - if len(runningPods) == replicas { - return - } - } - // Reaching here means that one of more checks failed multiple times. Assuming its not a race condition, something is broken. - Failf("Timed out after %v seconds waiting for %s pods to reach valid state", PodStartTimeout.Seconds(), testname) -} diff --git a/test/e2e/kubectl/kubectl.go b/test/e2e/kubectl/kubectl.go index e6737a7b7c2..7f7244184aa 100644 --- a/test/e2e/kubectl/kubectl.go +++ b/test/e2e/kubectl/kubectl.go @@ -116,6 +116,53 @@ var ( cronJobGroupVersionResourceBeta = schema.GroupVersionResource{Group: "batch", Version: "v1beta1", Resource: "cronjobs"} ) +var schemaFoo = []byte(`description: Foo CRD for Testing +type: object +properties: + spec: + type: object + description: Specification of Foo + properties: + bars: + description: List of Bars and their specs. + type: array + items: + type: object + required: + - name + properties: + name: + description: Name of Bar. + type: string + age: + description: Age of Bar. + type: string + bazs: + description: List of Bazs. + items: + type: string + type: array + status: + description: Status of Foo + type: object + properties: + bars: + description: List of Bars and their statuses. + type: array + items: + type: object + properties: + name: + description: Name of Bar. + type: string + available: + description: Whether the Bar is installed. + type: boolean + quxType: + description: Indicates to external qux type. + pattern: in-tree|out-of-tree + type: string`) + // Stops everything from filePath from namespace ns and checks if everything matching selectors from the given namespace is correctly stopped. // Aware of the kubectl example files map. func cleanupKubectlInputs(fileContents string, ns string, selectors ...string) { @@ -286,7 +333,7 @@ var _ = SIGDescribe("Kubectl client", func() { ginkgo.By("creating a replication controller") framework.RunKubectlOrDieInput(nautilus, "create", "-f", "-", fmt.Sprintf("--namespace=%v", ns)) - framework.ValidateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) + validateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) }) /* @@ -299,15 +346,15 @@ var _ = SIGDescribe("Kubectl client", func() { ginkgo.By("creating a replication controller") framework.RunKubectlOrDieInput(nautilus, "create", "-f", "-", fmt.Sprintf("--namespace=%v", ns)) - framework.ValidateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) + validateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) ginkgo.By("scaling down the replication controller") debugDiscovery() framework.RunKubectlOrDie("scale", "rc", "update-demo-nautilus", "--replicas=1", "--timeout=5m", fmt.Sprintf("--namespace=%v", ns)) - framework.ValidateController(c, nautilusImage, 1, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) + validateController(c, nautilusImage, 1, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) ginkgo.By("scaling up the replication controller") debugDiscovery() framework.RunKubectlOrDie("scale", "rc", "update-demo-nautilus", "--replicas=2", "--timeout=5m", fmt.Sprintf("--namespace=%v", ns)) - framework.ValidateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) + validateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) }) /* @@ -318,11 +365,11 @@ var _ = SIGDescribe("Kubectl client", func() { framework.ConformanceIt("should do a rolling update of a replication controller ", func() { ginkgo.By("creating the initial replication controller") framework.RunKubectlOrDieInput(string(nautilus[:]), "create", "-f", "-", fmt.Sprintf("--namespace=%v", ns)) - framework.ValidateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) + validateController(c, nautilusImage, 2, "update-demo", updateDemoSelector, getUDData("nautilus.jpg", ns), ns) ginkgo.By("rolling-update to new replication controller") debugDiscovery() framework.RunKubectlOrDieInput(string(kitten[:]), "rolling-update", "update-demo-nautilus", "--update-period=1s", "-f", "-", fmt.Sprintf("--namespace=%v", ns)) - framework.ValidateController(c, kittenImage, 2, "update-demo", updateDemoSelector, getUDData("kitten.jpg", ns), ns) + validateController(c, kittenImage, 2, "update-demo", updateDemoSelector, getUDData("kitten.jpg", ns), ns) // Everything will hopefully be cleaned up when the namespace is deleted. }) }) @@ -1565,7 +1612,7 @@ metadata: debugDiscovery() runKubectlRetryOrDie("rolling-update", rcName, "--update-period=1s", "--image="+httpdImage, "--image-pull-policy="+string(v1.PullIfNotPresent), nsFlag) - framework.ValidateController(c, httpdImage, 1, rcName, "run="+rcName, noOpValidatorFn, ns) + validateController(c, httpdImage, 1, rcName, "run="+rcName, noOpValidatorFn, ns) }) }) @@ -2367,49 +2414,71 @@ func createApplyCustomResource(resource, namespace, name string, crd *crd.TestCr return nil } -var schemaFoo = []byte(`description: Foo CRD for Testing -type: object -properties: - spec: - type: object - description: Specification of Foo - properties: - bars: - description: List of Bars and their specs. - type: array - items: - type: object - required: - - name - properties: - name: - description: Name of Bar. - type: string - age: - description: Age of Bar. - type: string - bazs: - description: List of Bazs. - items: - type: string - type: array - status: - description: Status of Foo - type: object - properties: - bars: - description: List of Bars and their statuses. - type: array - items: - type: object - properties: - name: - description: Name of Bar. - type: string - available: - description: Whether the Bar is installed. - type: boolean - quxType: - description: Indicates to external qux type. - pattern: in-tree|out-of-tree - type: string`) +// trimDockerRegistry is the function for trimming the docker.io/library from the beginning of the imagename. +// If community docker installed it will not prefix the registry names with the dockerimages vs registry names prefixed with other runtimes or docker installed via RHEL extra repo. +// So this function will help to trim the docker.io/library if exists +func trimDockerRegistry(imagename string) string { + imagename = strings.Replace(imagename, "docker.io/", "", 1) + return strings.Replace(imagename, "library/", "", 1) +} + +// validatorFn is the function which is individual tests will implement. +// we may want it to return more than just an error, at some point. +type validatorFn func(c clientset.Interface, podID string) error + +// validateController is a generic mechanism for testing RC's that are running. +// It takes a container name, a test name, and a validator function which is plugged in by a specific test. +// "containername": this is grepped for. +// "containerImage" : this is the name of the image we expect to be launched. Not to confuse w/ images (kitten.jpg) which are validated. +// "testname": which gets bubbled up to the logging/failure messages if errors happen. +// "validator" function: This function is given a podID and a client, and it can do some specific validations that way. +func validateController(c clientset.Interface, containerImage string, replicas int, containername string, testname string, validator validatorFn, ns string) { + containerImage = trimDockerRegistry(containerImage) + getPodsTemplate := "--template={{range.items}}{{.metadata.name}} {{end}}" + + getContainerStateTemplate := fmt.Sprintf(`--template={{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "%s") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`, containername) + + getImageTemplate := fmt.Sprintf(`--template={{if (exists . "spec" "containers")}}{{range .spec.containers}}{{if eq .name "%s"}}{{.image}}{{end}}{{end}}{{end}}`, containername) + + ginkgo.By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector +waitLoop: + for start := time.Now(); time.Since(start) < framework.PodStartTimeout; time.Sleep(5 * time.Second) { + getPodsOutput := framework.RunKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "-l", testname, fmt.Sprintf("--namespace=%v", ns)) + pods := strings.Fields(getPodsOutput) + if numPods := len(pods); numPods != replicas { + ginkgo.By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods)) + continue + } + var runningPods []string + for _, podID := range pods { + running := framework.RunKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, fmt.Sprintf("--namespace=%v", ns)) + if running != "true" { + framework.Logf("%s is created but not running", podID) + continue waitLoop + } + + currentImage := framework.RunKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, fmt.Sprintf("--namespace=%v", ns)) + currentImage = trimDockerRegistry(currentImage) + if currentImage != containerImage { + framework.Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage) + continue waitLoop + } + + // Call the generic validator function here. + // This might validate for example, that (1) getting a url works and (2) url is serving correct content. + if err := validator(c, podID); err != nil { + framework.Logf("%s is running right image but validator function failed: %v", podID, err) + continue waitLoop + } + + framework.Logf("%s is verified up and running", podID) + runningPods = append(runningPods, podID) + } + // If we reach here, then all our checks passed. + if len(runningPods) == replicas { + return + } + } + // Reaching here means that one of more checks failed multiple times. Assuming its not a race condition, something is broken. + framework.Failf("Timed out after %v seconds waiting for %s pods to reach valid state", framework.PodStartTimeout.Seconds(), testname) +}