/test/e2e/framework/util.go
Go | 1369 lines | 1063 code | 130 blank | 176 comment | 301 complexity | e42f80a243cde10a6c3648f873528426 MD5 | raw file
- /*
- Copyright 2014 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 framework
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "math"
- "math/rand"
- "net"
- "net/http"
- "net/url"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "regexp"
- goRuntime "runtime"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset"
- unversionedfederation "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned"
- "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_3"
- "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4"
- "k8s.io/kubernetes/pkg/api"
- apierrs "k8s.io/kubernetes/pkg/api/errors"
- "k8s.io/kubernetes/pkg/api/resource"
- "k8s.io/kubernetes/pkg/api/unversioned"
- "k8s.io/kubernetes/pkg/apis/extensions"
- "k8s.io/kubernetes/pkg/client/cache"
- clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
- "k8s.io/kubernetes/pkg/client/restclient"
- "k8s.io/kubernetes/pkg/client/typed/discovery"
- client "k8s.io/kubernetes/pkg/client/unversioned"
- "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
- clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
- gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
- deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
- "k8s.io/kubernetes/pkg/fields"
- "k8s.io/kubernetes/pkg/kubectl"
- "k8s.io/kubernetes/pkg/kubelet/util/format"
- "k8s.io/kubernetes/pkg/labels"
- "k8s.io/kubernetes/pkg/master/ports"
- "k8s.io/kubernetes/pkg/runtime"
- sshutil "k8s.io/kubernetes/pkg/ssh"
- "k8s.io/kubernetes/pkg/types"
- labelsutil "k8s.io/kubernetes/pkg/util/labels"
- "k8s.io/kubernetes/pkg/util/sets"
- "k8s.io/kubernetes/pkg/util/system"
- "k8s.io/kubernetes/pkg/util/uuid"
- "k8s.io/kubernetes/pkg/util/wait"
- utilyaml "k8s.io/kubernetes/pkg/util/yaml"
- "k8s.io/kubernetes/pkg/version"
- "k8s.io/kubernetes/pkg/watch"
- "github.com/blang/semver"
- "golang.org/x/crypto/ssh"
- "golang.org/x/net/websocket"
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
- gomegatypes "github.com/onsi/gomega/types"
- )
- const (
- // How long to wait for the pod to be listable
- PodListTimeout = time.Minute
- // Initial pod start can be delayed O(minutes) by slow docker pulls
- // TODO: Make this 30 seconds once #4566 is resolved.
- PodStartTimeout = 5 * time.Minute
- // How long to wait for the pod to no longer be running
- podNoLongerRunningTimeout = 30 * time.Second
- // If there are any orphaned namespaces to clean up, this test is running
- // on a long lived cluster. A long wait here is preferably to spurious test
- // failures caused by leaked resources from a previous test run.
- NamespaceCleanupTimeout = 15 * time.Minute
- // Some pods can take much longer to get ready due to volume attach/detach latency.
- slowPodStartTimeout = 15 * time.Minute
- // How long to wait for a service endpoint to be resolvable.
- ServiceStartTimeout = 1 * time.Minute
- // String used to mark pod deletion
- nonExist = "NonExist"
- // How often to Poll pods, nodes and claims.
- Poll = 2 * time.Second
- // service accounts are provisioned after namespace creation
- // a service account is required to support pod creation in a namespace as part of admission control
- ServiceAccountProvisionTimeout = 2 * time.Minute
- // How long to try single API calls (like 'get' or 'list'). Used to prevent
- // transient failures from failing tests.
- // TODO: client should not apply this timeout to Watch calls. Increased from 30s until that is fixed.
- SingleCallTimeout = 5 * time.Minute
- // How long nodes have to be "ready" when a test begins. They should already
- // be "ready" before the test starts, so this is small.
- NodeReadyInitialTimeout = 20 * time.Second
- // How long pods have to be "ready" when a test begins.
- PodReadyBeforeTimeout = 5 * time.Minute
- // How long pods have to become scheduled onto nodes
- podScheduledBeforeTimeout = PodListTimeout + (20 * time.Second)
- podRespondingTimeout = 2 * time.Minute
- ServiceRespondingTimeout = 2 * time.Minute
- EndpointRegisterTimeout = time.Minute
- // How long claims have to become dynamically provisioned
- ClaimProvisionTimeout = 5 * time.Minute
- // When these values are updated, also update cmd/kubelet/app/options/options.go
- currentPodInfraContainerImageName = "gcr.io/google_containers/pause"
- currentPodInfraContainerImageVersion = "3.0"
- // How long each node is given during a process that restarts all nodes
- // before the test is considered failed. (Note that the total time to
- // restart all nodes will be this number times the number of nodes.)
- RestartPerNodeTimeout = 5 * time.Minute
- // How often to Poll the statues of a restart.
- RestartPoll = 20 * time.Second
- // How long a node is allowed to become "Ready" after it is restarted before
- // the test is considered failed.
- RestartNodeReadyAgainTimeout = 5 * time.Minute
- // How long a pod is allowed to become "running" and "ready" after a node
- // restart before test is considered failed.
- RestartPodReadyAgainTimeout = 5 * time.Minute
- // Number of times we want to retry Updates in case of conflict
- UpdateRetries = 5
- )
- var (
- // Label allocated to the image puller static pod that runs on each node
- // before e2es.
- ImagePullerLabels = map[string]string{"name": "e2e-image-puller"}
- // For parsing Kubectl version for version-skewed testing.
- gitVersionRegexp = regexp.MustCompile("GitVersion:\"(v.+?)\"")
- )
- // GetServerArchitecture fetches the architecture of the cluster's apiserver.
- func GetServerArchitecture(c *client.Client) string {
- arch := ""
- sVer, err := c.Discovery().ServerVersion()
- if err != nil || sVer.Platform == "" {
- // If we failed to get the server version for some reason, default to amd64.
- arch = "amd64"
- } else {
- // Split the platform string into OS and Arch separately.
- // The platform string may for example be "linux/amd64", "linux/arm" or "windows/amd64".
- osArchArray := strings.Split(sVer.Platform, "/")
- arch = osArchArray[1]
- }
- return arch
- }
- // GetPauseImageName fetches the pause image name for the same architecture as the apiserver.
- func GetPauseImageName(c *client.Client) string {
- return currentPodInfraContainerImageName + "-" + GetServerArchitecture(c) + ":" + currentPodInfraContainerImageVersion
- }
- // GetPauseImageNameForHostArch fetches the pause image name for the same architecture the test is running on.
- func GetPauseImageNameForHostArch() string {
- return currentPodInfraContainerImageName + "-" + goRuntime.GOARCH + ":" + currentPodInfraContainerImageVersion
- }
- // SubResource proxy should have been functional in v1.0.0, but SubResource
- // proxy via tunneling is known to be broken in v1.0. See
- // https://github.com/kubernetes/kubernetes/pull/15224#issuecomment-146769463
- //
- // TODO(ihmccreery): remove once we don't care about v1.0 anymore, (tentatively
- // in v1.3).
- var SubResourcePodProxyVersion = version.MustParse("v1.1.0")
- var subResourceServiceAndNodeProxyVersion = version.MustParse("v1.2.0")
- func GetServicesProxyRequest(c *client.Client, request *restclient.Request) (*restclient.Request, error) {
- subResourceProxyAvailable, err := ServerVersionGTE(subResourceServiceAndNodeProxyVersion, c)
- if err != nil {
- return nil, err
- }
- if subResourceProxyAvailable {
- return request.Resource("services").SubResource("proxy"), nil
- }
- return request.Prefix("proxy").Resource("services"), nil
- }
- // unique identifier of the e2e run
- var RunId = uuid.NewUUID()
- type CreateTestingNSFn func(baseName string, c *client.Client, labels map[string]string) (*api.Namespace, error)
- type ContainerFailures struct {
- status *api.ContainerStateTerminated
- Restarts int
- }
- func GetMasterHost() string {
- masterUrl, err := url.Parse(TestContext.Host)
- ExpectNoError(err)
- return masterUrl.Host
- }
- // Convenient wrapper around cache.Store that returns list of api.Pod instead of interface{}.
- type PodStore struct {
- cache.Store
- stopCh chan struct{}
- }
- func NewPodStore(c *client.Client, namespace string, label labels.Selector, field fields.Selector) *PodStore {
- lw := &cache.ListWatch{
- ListFunc: func(options api.ListOptions) (runtime.Object, error) {
- options.LabelSelector = label
- options.FieldSelector = field
- return c.Pods(namespace).List(options)
- },
- WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
- options.LabelSelector = label
- options.FieldSelector = field
- return c.Pods(namespace).Watch(options)
- },
- }
- store := cache.NewStore(cache.MetaNamespaceKeyFunc)
- stopCh := make(chan struct{})
- cache.NewReflector(lw, &api.Pod{}, store, 0).RunUntil(stopCh)
- return &PodStore{store, stopCh}
- }
- func (s *PodStore) List() []*api.Pod {
- objects := s.Store.List()
- pods := make([]*api.Pod, 0)
- for _, o := range objects {
- pods = append(pods, o.(*api.Pod))
- }
- return pods
- }
- func (s *PodStore) Stop() {
- close(s.stopCh)
- }
- type RCConfig struct {
- Client *client.Client
- Image string
- Command []string
- Name string
- Namespace string
- PollInterval time.Duration
- Timeout time.Duration
- PodStatusFile *os.File
- Replicas int
- CpuRequest int64 // millicores
- CpuLimit int64 // millicores
- MemRequest int64 // bytes
- MemLimit int64 // bytes
- ReadinessProbe *api.Probe
- DNSPolicy *api.DNSPolicy
- // Env vars, set the same for every pod.
- Env map[string]string
- // Extra labels added to every pod.
- Labels map[string]string
- // Node selector for pods in the RC.
- NodeSelector map[string]string
- // Ports to declare in the container (map of name to containerPort).
- Ports map[string]int
- // Ports to declare in the container as host and container ports.
- HostPorts map[string]int
- Volumes []api.Volume
- VolumeMounts []api.VolumeMount
- // Pointer to a list of pods; if non-nil, will be set to a list of pods
- // created by this RC by RunRC.
- CreatedPods *[]*api.Pod
- // Maximum allowable container failures. If exceeded, RunRC returns an error.
- // Defaults to replicas*0.1 if unspecified.
- MaxContainerFailures *int
- // If set to false starting RC will print progress, otherwise only errors will be printed.
- Silent bool
- }
- type DeploymentConfig struct {
- RCConfig
- }
- type ReplicaSetConfig struct {
- RCConfig
- }
- func nowStamp() string {
- return time.Now().Format(time.StampMilli)
- }
- func log(level string, format string, args ...interface{}) {
- fmt.Fprintf(GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
- }
- func Logf(format string, args ...interface{}) {
- log("INFO", format, args...)
- }
- func Failf(format string, args ...interface{}) {
- msg := fmt.Sprintf(format, args...)
- log("INFO", msg)
- Fail(nowStamp()+": "+msg, 1)
- }
- func Skipf(format string, args ...interface{}) {
- msg := fmt.Sprintf(format, args...)
- log("INFO", msg)
- Skip(nowStamp() + ": " + msg)
- }
- func SkipUnlessNodeCountIsAtLeast(minNodeCount int) {
- if TestContext.CloudConfig.NumNodes < minNodeCount {
- Skipf("Requires at least %d nodes (not %d)", minNodeCount, TestContext.CloudConfig.NumNodes)
- }
- }
- func SkipUnlessAtLeast(value int, minValue int, message string) {
- if value < minValue {
- Skipf(message)
- }
- }
- func SkipIfProviderIs(unsupportedProviders ...string) {
- if ProviderIs(unsupportedProviders...) {
- Skipf("Not supported for providers %v (found %s)", unsupportedProviders, TestContext.Provider)
- }
- }
- func SkipUnlessProviderIs(supportedProviders ...string) {
- if !ProviderIs(supportedProviders...) {
- Skipf("Only supported for providers %v (not %s)", supportedProviders, TestContext.Provider)
- }
- }
- func SkipIfContainerRuntimeIs(runtimes ...string) {
- for _, runtime := range runtimes {
- if runtime == TestContext.ContainerRuntime {
- Skipf("Not supported under container runtime %s", runtime)
- }
- }
- }
- func ProviderIs(providers ...string) bool {
- for _, provider := range providers {
- if strings.ToLower(provider) == strings.ToLower(TestContext.Provider) {
- return true
- }
- }
- return false
- }
- func SkipUnlessServerVersionGTE(v semver.Version, c discovery.ServerVersionInterface) {
- gte, err := ServerVersionGTE(v, c)
- if err != nil {
- Failf("Failed to get server version: %v", err)
- }
- if !gte {
- Skipf("Not supported for server versions before %q", v)
- }
- }
- // Detects whether the federation namespace exists in the underlying cluster
- func SkipUnlessFederated(c *client.Client) {
- federationNS := os.Getenv("FEDERATION_NAMESPACE")
- if federationNS == "" {
- federationNS = "federation"
- }
- _, err := c.Namespaces().Get(federationNS)
- if err != nil {
- if apierrs.IsNotFound(err) {
- Skipf("Could not find federation namespace %s: skipping federated test", federationNS)
- } else {
- Failf("Unexpected error getting namespace: %v", err)
- }
- }
- }
- // ProvidersWithSSH are those providers where each node is accessible with SSH
- var ProvidersWithSSH = []string{"gce", "gke", "aws"}
- // providersWithMasterSSH are those providers where master node is accessible with SSH
- var providersWithMasterSSH = []string{"gce", "gke", "kubemark", "aws"}
- type podCondition func(pod *api.Pod) (bool, error)
- // podReady returns whether pod has a condition of Ready with a status of true.
- // TODO: should be replaced with api.IsPodReady
- func podReady(pod *api.Pod) bool {
- for _, cond := range pod.Status.Conditions {
- if cond.Type == api.PodReady && cond.Status == api.ConditionTrue {
- return true
- }
- }
- return false
- }
- // logPodStates logs basic info of provided pods for debugging.
- func logPodStates(pods []api.Pod) {
- // Find maximum widths for pod, node, and phase strings for column printing.
- maxPodW, maxNodeW, maxPhaseW, maxGraceW := len("POD"), len("NODE"), len("PHASE"), len("GRACE")
- for i := range pods {
- pod := &pods[i]
- if len(pod.ObjectMeta.Name) > maxPodW {
- maxPodW = len(pod.ObjectMeta.Name)
- }
- if len(pod.Spec.NodeName) > maxNodeW {
- maxNodeW = len(pod.Spec.NodeName)
- }
- if len(pod.Status.Phase) > maxPhaseW {
- maxPhaseW = len(pod.Status.Phase)
- }
- }
- // Increase widths by one to separate by a single space.
- maxPodW++
- maxNodeW++
- maxPhaseW++
- maxGraceW++
- // Log pod info. * does space padding, - makes them left-aligned.
- Logf("%-[1]*[2]s %-[3]*[4]s %-[5]*[6]s %-[7]*[8]s %[9]s",
- maxPodW, "POD", maxNodeW, "NODE", maxPhaseW, "PHASE", maxGraceW, "GRACE", "CONDITIONS")
- for _, pod := range pods {
- grace := ""
- if pod.DeletionGracePeriodSeconds != nil {
- grace = fmt.Sprintf("%ds", *pod.DeletionGracePeriodSeconds)
- }
- Logf("%-[1]*[2]s %-[3]*[4]s %-[5]*[6]s %-[7]*[8]s %[9]s",
- maxPodW, pod.ObjectMeta.Name, maxNodeW, pod.Spec.NodeName, maxPhaseW, pod.Status.Phase, maxGraceW, grace, pod.Status.Conditions)
- }
- Logf("") // Final empty line helps for readability.
- }
- // PodRunningReady checks whether pod p's phase is running and it has a ready
- // condition of status true.
- func PodRunningReady(p *api.Pod) (bool, error) {
- // Check the phase is running.
- if p.Status.Phase != api.PodRunning {
- return false, fmt.Errorf("want pod '%s' on '%s' to be '%v' but was '%v'",
- p.ObjectMeta.Name, p.Spec.NodeName, api.PodRunning, p.Status.Phase)
- }
- // Check the ready condition is true.
- if !podReady(p) {
- return false, fmt.Errorf("pod '%s' on '%s' didn't have condition {%v %v}; conditions: %v",
- p.ObjectMeta.Name, p.Spec.NodeName, api.PodReady, api.ConditionTrue, p.Status.Conditions)
- }
- return true, nil
- }
- func PodRunningReadyOrSucceeded(p *api.Pod) (bool, error) {
- // Check if the phase is succeeded.
- if p.Status.Phase == api.PodSucceeded {
- return true, nil
- }
- return PodRunningReady(p)
- }
- // PodNotReady checks whether pod p's has a ready condition of status false.
- func PodNotReady(p *api.Pod) (bool, error) {
- // Check the ready condition is false.
- if podReady(p) {
- return false, fmt.Errorf("pod '%s' on '%s' didn't have condition {%v %v}; conditions: %v",
- p.ObjectMeta.Name, p.Spec.NodeName, api.PodReady, api.ConditionFalse, p.Status.Conditions)
- }
- return true, nil
- }
- // check if a Pod is controlled by a Replication Controller in the List
- func hasReplicationControllersForPod(rcs *api.ReplicationControllerList, pod api.Pod) bool {
- for _, rc := range rcs.Items {
- selector := labels.SelectorFromSet(rc.Spec.Selector)
- if selector.Matches(labels.Set(pod.ObjectMeta.Labels)) {
- return true
- }
- }
- return false
- }
- // WaitForPodsSuccess waits till all labels matching the given selector enter
- // the Success state. The caller is expected to only invoke this method once the
- // pods have been created.
- func WaitForPodsSuccess(c *client.Client, ns string, successPodLabels map[string]string, timeout time.Duration) error {
- successPodSelector := labels.SelectorFromSet(successPodLabels)
- start, badPods := time.Now(), []api.Pod{}
- if wait.PollImmediate(30*time.Second, timeout, func() (bool, error) {
- podList, err := c.Pods(ns).List(api.ListOptions{LabelSelector: successPodSelector})
- if err != nil {
- Logf("Error getting pods in namespace %q: %v", ns, err)
- return false, nil
- }
- if len(podList.Items) == 0 {
- Logf("Waiting for pods to enter Success, but no pods in %q match label %v", ns, successPodLabels)
- return true, nil
- }
- badPods = []api.Pod{}
- for _, pod := range podList.Items {
- if pod.Status.Phase != api.PodSucceeded {
- badPods = append(badPods, pod)
- }
- }
- successPods := len(podList.Items) - len(badPods)
- Logf("%d / %d pods in namespace %q are in Success state (%d seconds elapsed)",
- successPods, len(podList.Items), ns, int(time.Since(start).Seconds()))
- if len(badPods) == 0 {
- return true, nil
- }
- return false, nil
- }) != nil {
- logPodStates(badPods)
- LogPodsWithLabels(c, ns, successPodLabels)
- return fmt.Errorf("Not all pods in namespace %q are successful within %v", ns, timeout)
- }
- return nil
- }
- // WaitForPodsRunningReady waits up to timeout to ensure that all pods in
- // namespace ns are either running and ready, or failed but controlled by a
- // replication controller. Also, it ensures that at least minPods are running
- // and ready. It has separate behavior from other 'wait for' pods functions in
- // that it requires the list of pods on every iteration. This is useful, for
- // example, in cluster startup, because the number of pods increases while
- // waiting.
- // If ignoreLabels is not empty, pods matching this selector are ignored and
- // this function waits for minPods to enter Running/Ready and for all pods
- // matching ignoreLabels to enter Success phase. Otherwise an error is returned
- // even if there are minPods pods, some of which are in Running/Ready
- // and some in Success. This is to allow the client to decide if "Success"
- // means "Ready" or not.
- func WaitForPodsRunningReady(c *client.Client, ns string, minPods int32, timeout time.Duration, ignoreLabels map[string]string) error {
- ignoreSelector := labels.SelectorFromSet(ignoreLabels)
- start := time.Now()
- Logf("Waiting up to %v for all pods (need at least %d) in namespace '%s' to be running and ready",
- timeout, minPods, ns)
- wg := sync.WaitGroup{}
- wg.Add(1)
- var waitForSuccessError error
- go func() {
- waitForSuccessError = WaitForPodsSuccess(c, ns, ignoreLabels, timeout)
- wg.Done()
- }()
- if wait.PollImmediate(Poll, timeout, func() (bool, error) {
- // We get the new list of pods and replication controllers in every
- // iteration because more pods come online during startup and we want to
- // ensure they are also checked.
- rcList, err := c.ReplicationControllers(ns).List(api.ListOptions{})
- if err != nil {
- Logf("Error getting replication controllers in namespace '%s': %v", ns, err)
- return false, nil
- }
- replicas := int32(0)
- for _, rc := range rcList.Items {
- replicas += rc.Spec.Replicas
- }
- podList, err := c.Pods(ns).List(api.ListOptions{})
- if err != nil {
- Logf("Error getting pods in namespace '%s': %v", ns, err)
- return false, nil
- }
- nOk, replicaOk, badPods := int32(0), int32(0), []api.Pod{}
- for _, pod := range podList.Items {
- if len(ignoreLabels) != 0 && ignoreSelector.Matches(labels.Set(pod.Labels)) {
- Logf("%v in state %v, ignoring", pod.Name, pod.Status.Phase)
- continue
- }
- if res, err := PodRunningReady(&pod); res && err == nil {
- nOk++
- if hasReplicationControllersForPod(rcList, pod) {
- replicaOk++
- }
- } else {
- if pod.Status.Phase != api.PodFailed {
- Logf("The status of Pod %s is %s, waiting for it to be either Running or Failed", pod.ObjectMeta.Name, pod.Status.Phase)
- badPods = append(badPods, pod)
- } else if !hasReplicationControllersForPod(rcList, pod) {
- Logf("Pod %s is Failed, but it's not controlled by a ReplicationController", pod.ObjectMeta.Name)
- badPods = append(badPods, pod)
- }
- //ignore failed pods that are controlled by a replication controller
- }
- }
- Logf("%d / %d pods in namespace '%s' are running and ready (%d seconds elapsed)",
- nOk, len(podList.Items), ns, int(time.Since(start).Seconds()))
- Logf("expected %d pod replicas in namespace '%s', %d are Running and Ready.", replicas, ns, replicaOk)
- if replicaOk == replicas && nOk >= minPods && len(badPods) == 0 {
- return true, nil
- }
- logPodStates(badPods)
- return false, nil
- }) != nil {
- return fmt.Errorf("Not all pods in namespace '%s' running and ready within %v", ns, timeout)
- }
- wg.Wait()
- if waitForSuccessError != nil {
- return waitForSuccessError
- }
- return nil
- }
- func podFromManifest(filename string) (*api.Pod, error) {
- var pod api.Pod
- Logf("Parsing pod from %v", filename)
- data := ReadOrDie(filename)
- json, err := utilyaml.ToJSON(data)
- if err != nil {
- return nil, err
- }
- if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), json, &pod); err != nil {
- return nil, err
- }
- return &pod, nil
- }
- // Run a test container to try and contact the Kubernetes api-server from a pod, wait for it
- // to flip to Ready, log its output and delete it.
- func RunKubernetesServiceTestContainer(c *client.Client, ns string) {
- path := "test/images/clusterapi-tester/pod.yaml"
- p, err := podFromManifest(path)
- if err != nil {
- Logf("Failed to parse clusterapi-tester from manifest %v: %v", path, err)
- return
- }
- p.Namespace = ns
- if _, err := c.Pods(ns).Create(p); err != nil {
- Logf("Failed to create %v: %v", p.Name, err)
- return
- }
- defer func() {
- if err := c.Pods(ns).Delete(p.Name, nil); err != nil {
- Logf("Failed to delete pod %v: %v", p.Name, err)
- }
- }()
- timeout := 5 * time.Minute
- if err := waitForPodCondition(c, ns, p.Name, "clusterapi-tester", timeout, PodRunningReady); err != nil {
- Logf("Pod %v took longer than %v to enter running/ready: %v", p.Name, timeout, err)
- return
- }
- logs, err := GetPodLogs(c, ns, p.Name, p.Spec.Containers[0].Name)
- if err != nil {
- Logf("Failed to retrieve logs from %v: %v", p.Name, err)
- } else {
- Logf("Output of clusterapi-tester:\n%v", logs)
- }
- }
- func kubectlLogPod(c *client.Client, pod api.Pod, containerNameSubstr string) {
- for _, container := range pod.Spec.Containers {
- if strings.Contains(container.Name, containerNameSubstr) {
- // Contains() matches all strings if substr is empty
- logs, err := GetPodLogs(c, pod.Namespace, pod.Name, container.Name)
- if err != nil {
- logs, err = getPreviousPodLogs(c, pod.Namespace, pod.Name, container.Name)
- if err != nil {
- Logf("Failed to get logs of pod %v, container %v, err: %v", pod.Name, container.Name, err)
- }
- }
- By(fmt.Sprintf("Logs of %v/%v:%v on node %v", pod.Namespace, pod.Name, container.Name, pod.Spec.NodeName))
- Logf("%s : STARTLOG\n%s\nENDLOG for container %v:%v:%v", containerNameSubstr, logs, pod.Namespace, pod.Name, container.Name)
- }
- }
- }
- func LogFailedContainers(c *client.Client, ns string) {
- podList, err := c.Pods(ns).List(api.ListOptions{})
- if err != nil {
- Logf("Error getting pods in namespace '%s': %v", ns, err)
- return
- }
- Logf("Running kubectl logs on non-ready containers in %v", ns)
- for _, pod := range podList.Items {
- if res, err := PodRunningReady(&pod); !res || err != nil {
- kubectlLogPod(c, pod, "")
- }
- }
- }
- func LogPodsWithLabels(c *client.Client, ns string, match map[string]string) {
- podList, err := c.Pods(ns).List(api.ListOptions{LabelSelector: labels.SelectorFromSet(match)})
- if err != nil {
- Logf("Error getting pods in namespace %q: %v", ns, err)
- return
- }
- Logf("Running kubectl logs on pods with labels %v in %v", match, ns)
- for _, pod := range podList.Items {
- kubectlLogPod(c, pod, "")
- }
- }
- func LogContainersInPodsWithLabels(c *client.Client, ns string, match map[string]string, containerSubstr string) {
- podList, err := c.Pods(ns).List(api.ListOptions{LabelSelector: labels.SelectorFromSet(match)})
- if err != nil {
- Logf("Error getting pods in namespace %q: %v", ns, err)
- return
- }
- for _, pod := range podList.Items {
- kubectlLogPod(c, pod, containerSubstr)
- }
- }
- // DeleteNamespaces deletes all namespaces that match the given delete and skip filters.
- // Filter is by simple strings.Contains; first skip filter, then delete filter.
- // Returns the list of deleted namespaces or an error.
- func DeleteNamespaces(c *client.Client, deleteFilter, skipFilter []string) ([]string, error) {
- By("Deleting namespaces")
- nsList, err := c.Namespaces().List(api.ListOptions{})
- Expect(err).NotTo(HaveOccurred())
- var deleted []string
- var wg sync.WaitGroup
- OUTER:
- for _, item := range nsList.Items {
- if skipFilter != nil {
- for _, pattern := range skipFilter {
- if strings.Contains(item.Name, pattern) {
- continue OUTER
- }
- }
- }
- if deleteFilter != nil {
- var shouldDelete bool
- for _, pattern := range deleteFilter {
- if strings.Contains(item.Name, pattern) {
- shouldDelete = true
- break
- }
- }
- if !shouldDelete {
- continue OUTER
- }
- }
- wg.Add(1)
- deleted = append(deleted, item.Name)
- go func(nsName string) {
- defer wg.Done()
- defer GinkgoRecover()
- Expect(c.Namespaces().Delete(nsName)).To(Succeed())
- Logf("namespace : %v api call to delete is complete ", nsName)
- }(item.Name)
- }
- wg.Wait()
- return deleted, nil
- }
- func WaitForNamespacesDeleted(c *client.Client, namespaces []string, timeout time.Duration) error {
- By("Waiting for namespaces to vanish")
- nsMap := map[string]bool{}
- for _, ns := range namespaces {
- nsMap[ns] = true
- }
- //Now POLL until all namespaces have been eradicated.
- return wait.Poll(2*time.Second, timeout,
- func() (bool, error) {
- nsList, err := c.Namespaces().List(api.ListOptions{})
- if err != nil {
- return false, err
- }
- for _, item := range nsList.Items {
- if _, ok := nsMap[item.Name]; ok {
- return false, nil
- }
- }
- return true, nil
- })
- }
- func waitForServiceAccountInNamespace(c *client.Client, ns, serviceAccountName string, timeout time.Duration) error {
- w, err := c.ServiceAccounts(ns).Watch(api.SingleObject(api.ObjectMeta{Name: serviceAccountName}))
- if err != nil {
- return err
- }
- _, err = watch.Until(timeout, w, client.ServiceAccountHasSecrets)
- return err
- }
- func waitForPodCondition(c *client.Client, ns, podName, desc string, timeout time.Duration, condition podCondition) error {
- Logf("Waiting up to %[1]v for pod %[2]s status to be %[3]s", timeout, podName, desc)
- for start := time.Now(); time.Since(start) < timeout; time.Sleep(Poll) {
- pod, err := c.Pods(ns).Get(podName)
- if err != nil {
- if apierrs.IsNotFound(err) {
- Logf("Pod %q in namespace %q disappeared. Error: %v", podName, ns, err)
- return err
- }
- // Aligning this text makes it much more readable
- Logf("Get pod %[1]s in namespace '%[2]s' failed, ignoring for %[3]v. Error: %[4]v",
- podName, ns, Poll, err)
- continue
- }
- done, err := condition(pod)
- if done {
- return err
- }
- Logf("Waiting for pod %[1]s in namespace '%[2]s' status to be '%[3]s'"+
- "(found phase: %[4]q, readiness: %[5]t) (%[6]v elapsed)",
- podName, ns, desc, pod.Status.Phase, podReady(pod), time.Since(start))
- }
- return fmt.Errorf("gave up waiting for pod '%s' to be '%s' after %v", podName, desc, timeout)
- }
- // WaitForMatchPodsCondition finds match pods based on the input ListOptions.
- // waits and checks if all match pods are in the given podCondition
- func WaitForMatchPodsCondition(c *client.Client, opts api.ListOptions, desc string, timeout time.Duration, condition podCondition) error {
- Logf("Waiting up to %v for matching pods' status to be %s", timeout, desc)
- for start := time.Now(); time.Since(start) < timeout; time.Sleep(Poll) {
- pods, err := c.Pods(api.NamespaceAll).List(opts)
- if err != nil {
- return err
- }
- conditionNotMatch := []string{}
- for _, pod := range pods.Items {
- done, err := condition(&pod)
- if done && err != nil {
- return fmt.Errorf("Unexpected error: %v", err)
- }
- if !done {
- conditionNotMatch = append(conditionNotMatch, format.Pod(&pod))
- }
- }
- if len(conditionNotMatch) <= 0 {
- return err
- }
- Logf("%d pods are not %s", len(conditionNotMatch), desc)
- }
- return fmt.Errorf("gave up waiting for matching pods to be '%s' after %v", desc, timeout)
- }
- // WaitForDefaultServiceAccountInNamespace waits for the default service account to be provisioned
- // the default service account is what is associated with pods when they do not specify a service account
- // as a result, pods are not able to be provisioned in a namespace until the service account is provisioned
- func WaitForDefaultServiceAccountInNamespace(c *client.Client, namespace string) error {
- return waitForServiceAccountInNamespace(c, namespace, "default", ServiceAccountProvisionTimeout)
- }
- // WaitForFederationApiserverReady waits for the federation apiserver to be ready.
- // It tests the readiness by sending a GET request and expecting a non error response.
- func WaitForFederationApiserverReady(c *federation_internalclientset.Clientset) error {
- return wait.PollImmediate(time.Second, 1*time.Minute, func() (bool, error) {
- _, err := c.Federation().Clusters().List(api.ListOptions{})
- if err != nil {
- return false, nil
- }
- return true, nil
- })
- }
- // WaitForPersistentVolumePhase waits for a PersistentVolume to be in a specific phase or until timeout occurs, whichever comes first.
- func WaitForPersistentVolumePhase(phase api.PersistentVolumePhase, c *client.Client, pvName string, Poll, timeout time.Duration) error {
- Logf("Waiting up to %v for PersistentVolume %s to have phase %s", timeout, pvName, phase)
- for start := time.Now(); time.Since(start) < timeout; time.Sleep(Poll) {
- pv, err := c.PersistentVolumes().Get(pvName)
- if err != nil {
- Logf("Get persistent volume %s in failed, ignoring for %v: %v", pvName, Poll, err)
- continue
- } else {
- if pv.Status.Phase == phase {
- Logf("PersistentVolume %s found and phase=%s (%v)", pvName, phase, time.Since(start))
- return nil
- } else {
- Logf("PersistentVolume %s found but phase is %s instead of %s.", pvName, pv.Status.Phase, phase)
- }
- }
- }
- return fmt.Errorf("PersistentVolume %s not in phase %s within %v", pvName, phase, timeout)
- }
- // WaitForPersistentVolumeDeleted waits for a PersistentVolume to get deleted or until timeout occurs, whichever comes first.
- func WaitForPersistentVolumeDeleted(c *client.Client, pvName string, Poll, timeout time.Duration) error {
- Logf("Waiting up to %v for PersistentVolume %s to get deleted", timeout, pvName)
- for start := time.Now(); time.Since(start) < timeout; time.Sleep(Poll) {
- pv, err := c.PersistentVolumes().Get(pvName)
- if err == nil {
- Logf("PersistentVolume %s found and phase=%s (%v)", pvName, pv.Status.Phase, time.Since(start))
- continue
- } else {
- if apierrs.IsNotFound(err) {
- Logf("PersistentVolume %s was removed", pvName)
- return nil
- } else {
- Logf("Get persistent volume %s in failed, ignoring for %v: %v", pvName, Poll, err)
- }
- }
- }
- return fmt.Errorf("PersistentVolume %s still exists within %v", pvName, timeout)
- }
- // WaitForPersistentVolumeClaimPhase waits for a PersistentVolumeClaim to be in a specific phase or until timeout occurs, whichever comes first.
- func WaitForPersistentVolumeClaimPhase(phase api.PersistentVolumeClaimPhase, c *client.Client, ns string, pvcName string, Poll, timeout time.Duration) error {
- Logf("Waiting up to %v for PersistentVolumeClaim %s to have phase %s", timeout, pvcName, phase)
- for start := time.Now(); time.Since(start) < timeout; time.Sleep(Poll) {
- pvc, err := c.PersistentVolumeClaims(ns).Get(pvcName)
- if err != nil {
- Logf("Get persistent volume claim %s in failed, ignoring for %v: %v", pvcName, Poll, err)
- continue
- } else {
- if pvc.Status.Phase == phase {
- Logf("PersistentVolumeClaim %s found and phase=%s (%v)", pvcName, phase, time.Since(start))
- return nil
- } else {
- Logf("PersistentVolumeClaim %s found but phase is %s instead of %s.", pvcName, pvc.Status.Phase, phase)
- }
- }
- }
- return fmt.Errorf("PersistentVolumeClaim %s not in phase %s within %v", pvcName, phase, timeout)
- }
- // CreateTestingNS should be used by every test, note that we append a common prefix to the provided test name.
- // Please see NewFramework instead of using this directly.
- func CreateTestingNS(baseName string, c *client.Client, labels map[string]string) (*api.Namespace, error) {
- if labels == nil {
- labels = map[string]string{}
- }
- labels["e2e-run"] = string(RunId)
- namespaceObj := &api.Namespace{
- ObjectMeta: api.ObjectMeta{
- GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName),
- Namespace: "",
- Labels: labels,
- },
- Status: api.NamespaceStatus{},
- }
- // Be robust about making the namespace creation call.
- var got *api.Namespace
- if err := wait.PollImmediate(Poll, SingleCallTimeout, func() (bool, error) {
- var err error
- got, err = c.Namespaces().Create(namespaceObj)
- if err != nil {
- Logf("Unexpected error while creating namespace: %v", err)
- return false, nil
- }
- return true, nil
- }); err != nil {
- return nil, err
- }
- if TestContext.VerifyServiceAccount {
- if err := WaitForDefaultServiceAccountInNamespace(c, got.Name); err != nil {
- return nil, err
- }
- }
- return got, nil
- }
- // CheckTestingNSDeletedExcept checks whether all e2e based existing namespaces are in the Terminating state
- // and waits until they are finally deleted. It ignores namespace skip.
- func CheckTestingNSDeletedExcept(c *client.Client, skip string) error {
- // TODO: Since we don't have support for bulk resource deletion in the API,
- // while deleting a namespace we are deleting all objects from that namespace
- // one by one (one deletion == one API call). This basically exposes us to
- // throttling - currently controller-manager has a limit of max 20 QPS.
- // Once #10217 is implemented and used in namespace-controller, deleting all
- // object from a given namespace should be much faster and we will be able
- // to lower this timeout.
- // However, now Density test is producing ~26000 events and Load capacity test
- // is producing ~35000 events, thus assuming there are no other requests it will
- // take ~30 minutes to fully delete the namespace. Thus I'm setting it to 60
- // minutes to avoid any timeouts here.
- timeout := 60 * time.Minute
- Logf("Waiting for terminating namespaces to be deleted...")
- for start := time.Now(); time.Since(start) < timeout; time.Sleep(15 * time.Second) {
- namespaces, err := c.Namespaces().List(api.ListOptions{})
- if err != nil {
- Logf("Listing namespaces failed: %v", err)
- continue
- }
- terminating := 0
- for _, ns := range namespaces.Items {
- if strings.HasPrefix(ns.ObjectMeta.Name, "e2e-tests-") && ns.ObjectMeta.Name != skip {
- if ns.Status.Phase == api.NamespaceActive {
- return fmt.Errorf("Namespace %s is active", ns.ObjectMeta.Name)
- }
- terminating++
- }
- }
- if terminating == 0 {
- return nil
- }
- }
- return fmt.Errorf("Waiting for terminating namespaces to be deleted timed out")
- }
- // deleteNS deletes the provided namespace, waits for it to be completely deleted, and then checks
- // whether there are any pods remaining in a non-terminating state.
- func deleteNS(c *client.Client, namespace string, timeout time.Duration) error {
- if err := c.Namespaces().Delete(namespace); err != nil {
- return err
- }
- err := wait.PollImmediate(5*time.Second, timeout, func() (bool, error) {
- if _, err := c.Namespaces().Get(namespace); err != nil {
- if apierrs.IsNotFound(err) {
- return true, nil
- }
- Logf("Error while waiting for namespace to be terminated: %v", err)
- return false, nil
- }
- return false, nil
- })
- // check for pods that were not deleted
- remaining := []string{}
- remainingPods := []api.Pod{}
- missingTimestamp := false
- if pods, perr := c.Pods(namespace).List(api.ListOptions{}); perr == nil {
- for _, pod := range pods.Items {
- Logf("Pod %s %s on node %s remains, has deletion timestamp %s", namespace, pod.Name, pod.Spec.NodeName, pod.DeletionTimestamp)
- remaining = append(remaining, fmt.Sprintf("%s{Reason=%s}", pod.Name, pod.Status.Reason))
- remainingPods = append(remainingPods, pod)
- if pod.DeletionTimestamp == nil {
- missingTimestamp = true
- }
- }
- }
- // log pod status
- if len(remainingPods) > 0 {
- logPodStates(remainingPods)
- }
- // a timeout occurred
- if err != nil {
- if missingTimestamp {
- return fmt.Errorf("namespace %s was not deleted within limit: %v, some pods were not marked with a deletion timestamp, pods remaining: %v", namespace, err, remaining)
- }
- return fmt.Errorf("namespace %s was not deleted within limit: %v, pods remaining: %v", namespace, err, remaining)
- }
- // pods were not deleted but the namespace was deleted
- if len(remaining) > 0 {
- return fmt.Errorf("pods remained within namespace %s after deletion: %v", namespace, remaining)
- }
- return nil
- }
- func ContainerInitInvariant(older, newer runtime.Object) error {
- oldPod := older.(*api.Pod)
- newPod := newer.(*api.Pod)
- if len(oldPod.Spec.InitContainers) == 0 {
- return nil
- }
- if len(oldPod.Spec.InitContainers) != len(newPod.Spec.InitContainers) {
- return fmt.Errorf("init container list changed")
- }
- if oldPod.UID != newPod.UID {
- return fmt.Errorf("two different pods exist in the condition: %s vs %s", oldPod.UID, newPod.UID)
- }
- if err := initContainersInvariants(oldPod); err != nil {
- return err
- }
- if err := initContainersInvariants(newPod); err != nil {
- return err
- }
- oldInit, _, _ := podInitialized(oldPod)
- newInit, _, _ := podInitialized(newPod)
- if oldInit && !newInit {
- // TODO: we may in the future enable resetting PodInitialized = false if the kubelet needs to restart it
- // from scratch
- return fmt.Errorf("pod cannot be initialized and then regress to not being initialized")
- }
- return nil
- }
- func podInitialized(pod *api.Pod) (ok bool, failed bool, err error) {
- allInit := true
- initFailed := false
- for _, s := range pod.Status.InitContainerStatuses {
- switch {
- case initFailed && s.State.Waiting == nil:
- return allInit, initFailed, fmt.Errorf("container %s is after a failed container but isn't waiting", s.Name)
- case allInit && s.State.Waiting == nil:
- return allInit, initFailed, fmt.Errorf("container %s is after an initializing container but isn't waiting", s.Name)
- case s.State.Terminated == nil:
- allInit = false
- case s.State.Terminated.ExitCode != 0:
- allInit = false
- initFailed = true
- case !s.Ready:
- return allInit, initFailed, fmt.Errorf("container %s initialized but isn't marked as ready", s.Name)
- }
- }
- return allInit, initFailed, nil
- }
- func initContainersInvariants(pod *api.Pod) error {
- allInit, initFailed, err := podInitialized(pod)
- if err != nil {
- return err
- }
- if !allInit || initFailed {
- for _, s := range pod.Status.ContainerStatuses {
- if s.State.Waiting == nil || s.RestartCount != 0 {
- return fmt.Errorf("container %s is not waiting but initialization not complete", s.Name)
- }
- if s.State.Waiting.Reason != "PodInitializing" {
- return fmt.Errorf("container %s should have reason PodInitializing: %s", s.Name, s.State.Waiting.Reason)
- }
- }
- }
- _, c := api.GetPodCondition(&pod.Status, api.PodInitialized)
- if c == nil {
- return fmt.Errorf("pod does not have initialized condition")
- }
- if c.LastTransitionTime.IsZero() {
- return fmt.Errorf("PodInitialized condition should always have a transition time")
- }
- switch {
- case c.Status == api.ConditionUnknown:
- return fmt.Errorf("PodInitialized condition should never be Unknown")
- case c.Status == api.ConditionTrue && (initFailed || !allInit):
- return fmt.Errorf("PodInitialized condition was True but all not all containers initialized")
- case c.Status == api.ConditionFalse && (!initFailed && allInit):
- return fmt.Errorf("PodInitialized condition was False but all containers initialized")
- }
- return nil
- }
- type InvariantFunc func(older, newer runtime.Object) error
- func CheckInvariants(events []watch.Event, fns ...InvariantFunc) error {
- errs := sets.NewString()
- for i := range events {
- j := i + 1
- if j >= len(events) {
- continue
- }
- for _, fn := range fns {
- if err := fn(events[i].Object, events[j].Object); err != nil {
- errs.Insert(err.Error())
- }
- }
- }
- if errs.Len() > 0 {
- return fmt.Errorf("invariants violated:\n* %s", strings.Join(errs.List(), "\n* "))
- }
- return nil
- }
- // Waits default amount of time (PodStartTimeout) for the specified pod to become running.
- // Returns an error if timeout occurs first, or pod goes in to failed state.
- func WaitForPodRunningInNamespace(c *client.Client, pod *api.Pod) error {
- // this short-cicuit is needed for cases when we pass a list of pods instead
- // of newly created pod (eg. VerifyPods) which means we are getting already
- // running pod for which waiting does not make sense and will always fail
- if pod.Status.Phase == api.PodRunning {
- return nil
- }
- return waitTimeoutForPodRunningInNamespace(c, pod.Name, pod.Namespace, pod.ResourceVersion, PodStartTimeout)
- }
- // Waits default amount of time (PodStartTimeout) for the specified pod to become running.
- // Returns an error if timeout occurs first, or pod goes in to failed state.
- func WaitForPodNameRunningInNamespace(c *client.Client, podName, namespace string) error {
- return waitTimeoutForPodRunningInNamespace(c, podName, namespace, "", PodStartTimeout)
- }
- // Waits an extended amount of time (slowPodStartTimeout) for the specified pod to become running.
- // The resourceVersion is used when Watching object changes, it tells since when we care
- // about changes to the pod. Returns an error if timeout occurs first, or pod goes in to failed state.
- func waitForPodRunningInNamespaceSlow(c *client.Client, podName, namespace, resourceVersion string) error {
- return waitTimeoutForPodRunningInNamespace(c, podName, namespace, resourceVersion, slowPodStartTimeout)
- }
- func waitTimeoutForPodRunningInNamespace(c *client.Client, podName, namespace, resourceVersion string, timeout time.Duration) error {
- w, err := c.Pods(namespace).Watch(api.SingleObject(api.ObjectMeta{Name: podName, ResourceVersion: resourceVersion}))
- if err != nil {
- return err
- }
- _, err = watch.Until(timeout, w, client.PodRunning)
- return err
- }
- // Waits default amount of time (podNoLongerRunningTimeout) for the specified pod to stop running.
- // Returns an error if timeout occurs first.
- func WaitForPodNoLongerRunningInNamespace(c *client.Client, podName, namespace, resourceVersion string) error {
- return waitTimeoutForPodNoLongerRunningInNamespace(c, podName, namespace, resourceVersion, podNoLongerRunningTimeout)
- }
- func waitTimeoutForPodNoLongerRunningInNamespace(c *client.Client, podName, namespace, resourceVersion string, timeout time.Duration) error {
- w, err := c.Pods(namespace).Watch(api.SingleObject(api.ObjectMeta{Name: podName, ResourceVersion: resourceVersion}))
- if err != nil {
- return err
- }
- _, err = watch.Until(timeout, w, client.PodCompleted)
- return err
- }
- func waitTimeoutForPodReadyInNamespace(c *client.Client, podName, namespace, resourceVersion string, timeout time.Duration) error {
- w, err := c.Pods(namespace).Watch(api.SingleObject(api.ObjectMeta{Name: podName, ResourceVersion: resourceVersion}))
- if err != nil {
- return err
- }
- _, err = watch.Until(timeout, w, client.PodRunningAndReady)
- return err
- }
- // WaitForPodNotPending returns an error if it took too long for the pod to go out of pending state.
- // The resourceVersion is used when Watching object changes, it tells since when we care
- // about changes to the pod.
- func WaitForPodNotPending(c *client.Client, ns, podName, resourceVersion string) error {
- w, err := c.Pods(ns).Watch(api.SingleObject(api.ObjectMeta{Name: podName, ResourceVersion: resourceVersion}))
- if err != nil {
- return err
- }
- _, err = watch.Until(PodStartTimeout, w, client.PodNotPending)
- return err
- }
- // waitForPodTerminatedInNamespace returns an error if it took too long for the pod
- // to terminate or if the pod terminated with an unexpected reason.
- func waitForPodTerminatedInNamespace(c *client.Client, podName, reason, namespace string) error {
- return waitForPodCondition(c, namespace, podName, "terminated due to deadline exceeded", PodStartTimeout, func(pod *api.Pod) (bool, error) {
- if pod.Status.Phase == api.PodFailed {
- if pod.Status.Reason == reason {
- return true, nil
- } else {
- return true, fmt.Errorf("Expected pod %v in namespace %v to be terminated with reason %v, got reason: %v", podName, namespace, reason, pod.Status.Reason)
- }
- }
- return false, nil
- })
- }
- // waitForPodSuccessInNamespaceTimeout returns nil if the pod reached state success, or an error if it reached failure or ran too long.
- 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) {
- // Cannot use pod.Status.Phase == api.PodSucceeded/api.PodFailed due to #2632
- ci, ok := api.GetContainerStatus(pod.Status.ContainerStatuses, contName)
- if !ok {
- Logf("No Status.Info for container '%s' in pod '%s' yet", contName, podName)
- } else {
- if ci.State.Terminated != nil {
- if ci.State.Terminated.ExitCode == 0 {
- By("Saw pod success")
- return true, nil
- }
- return true, fmt.Errorf("pod '%s' terminated with failure: %+v", podName, ci.State.Terminated)
- }
- Logf("Nil State.Terminated for container '%s' in pod '%s' in namespace '%s' so far", contName, podName, namespace)
- }
- return false, nil
- })
- }
- // WaitForPodSuccessInNamespace returns nil if the pod reached state success, or an error if it reached failure or until podStartupTimeout.
- func WaitForPodSuccessInNamespace(c *client.Client, podName string, contName string, namespace string) error {
- return waitForPodSuccessInNamespaceTimeout(c, podName, contName, namespace, PodStartTimeout)
- }
- // WaitForPodSuccessInNamespaceSlow returns nil if the pod reached state success, or an error if it reached failure or until slowPodStartupTimeout.
- func WaitForPodSuccessInNamespaceSlow(c *client.Client, podName string, contName string, namespace string) error {
- return waitForPodSuccessInNamespaceTimeout(c, podName, contName, namespace, slowPodStartTimeout)
- }
- // waitForRCPodOnNode returns the pod from the given replication controller (described by rcName) which is scheduled on the given node.
- // In case of failure or too long waiting time, an error is returned.
- func waitForRCPodOnNode(c *client.Client, ns, rcName, node string) (*api.Pod, error) {
- label := labels.SelectorFromSet(labels.Set(map[string]string{"name": rcName}))
- var p *api.Pod = nil
- err := wait.PollImmediate(10*time.Second, 5*time.Minute, func() (bool, error) {
- Logf("Waiting for pod %s to appear on node %s", rcName, node)
- options := api.ListOptions{LabelSelector: label}
- pods, err := c.Pods(ns).List(options)
- if err != nil {
- return false, err
- }
- for _, pod := range pods.Items {
- if pod.Spec.NodeName == node {
- Logf("Pod %s found on node %s", pod.Name, node)
- p = &pod
- return true, nil
- }
- }
- return false, nil
- })
- return p, err
- }
- // WaitForRCToStabilize waits till the RC has a matching generation/replica count between spec and status.
- func WaitForRCToStabilize(c *client.Client, ns, name string, timeout time.Duration) error {
- options := api.ListOptions{FieldSelector: fields.Set{
- "metadata.name": name,
- "metadata.namespace": ns,
- }.AsSelector()}
- w, err := c.ReplicationControllers(ns).Watch(options)
- if err != nil {
- return err
- }
- _, err = watch.Until(timeout, w, func(event watch.Event) (bool, error) {
- switch event.Type {
- case watch.Deleted:
- return false, apierrs.NewNotFound(unversioned.GroupResource{Resource: "replicationcontrollers"}, "")
- }
- switch rc := event.Object.(type) {
- case *api.ReplicationController:
- if rc.Name == name && rc.Namespace == ns &&
- rc.Generation <= rc.Status.ObservedGeneration &&
- rc.Spec.Replicas == rc.Status.Replicas {
- return true, nil
- }
- Logf("Waiting for rc %s to stabilize, generation %v observed generation %v spec.replicas %d status.replicas %d",
- name, rc.Generation, rc.Status.ObservedGeneration, rc.Spec.Replicas, rc.Status.Replicas)
- }
- return false, nil
- })
- return err
- }
- func WaitForPodToDisappear(c *client.Client, ns, podName string, label labels.Selector, interval, timeout time.Duration) error {
- return wait.PollImmediate(interval, timeout, func() (bool, error) {
- Logf("Waiting for pod %s to disappear", podName)
- options := api.ListOptions{LabelSelector: label}
- pods, err := c.Pods(ns).List(options)
- if err != nil {
- return false, err
- }
- found := false
- for _, pod := range pods.Items {
- if pod.Name == podName {
- Logf("Pod %s still exists", podName)
- found = true
- }
- }
- if !found {
- Logf("Pod %s no longer exists", podName)
- return true, nil
- }