/
githubmirror
/
kubernetes
Обзор
Документация
Войти
/
githubmirror
/
kubernetes
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/utils/runners.go
893 строки
28 KB
Patrick Ohly
test: move code under scheduler_perf
16 июн 2026, 16:20
16 июн 2026, 16:20
24bf142
Код
Авторство
О чём код?
/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package utils import ( "context" "fmt" "math" "os" "strings" "time" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/uuid" clientset "k8s.io/client-go/kubernetes" scaleclient "k8s.io/client-go/scale" "k8s.io/utils/ptr" "k8s.io/klog/v2" ) const ( // String used to mark pod deletion nonExist = "NonExist" ) func waitUntilPodIsScheduled(ctx context.Context, c clientset.Interface, name, namespace string, timeout time.Duration) (*v1.Pod, error) { // Wait until it's scheduled p, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{ResourceVersion: "0"}) if err == nil && p.Spec.NodeName != "" { return p, nil } pollingPeriod := 200 * time.Millisecond startTime := time.Now() for startTime.Add(timeout).After(time.Now()) && ctx.Err() == nil { time.Sleep(pollingPeriod) p, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{ResourceVersion: "0"}) if err == nil && p.Spec.NodeName != "" { return p, nil } } return nil, fmt.Errorf("timed out after %v when waiting for pod %v/%v to start", timeout, namespace, name) } func RunPodAndGetNodeName(ctx context.Context, c clientset.Interface, pod *v1.Pod, timeout time.Duration) (string, error) { name := pod.Name namespace := pod.Namespace if err := CreatePodWithRetries(c, namespace, pod); err != nil { return "", err } p, err := waitUntilPodIsScheduled(ctx, c, name, namespace, timeout) if err != nil { return "", err } return p.Spec.NodeName, nil } type RunObjectConfig interface { Run() error GetName() string GetNamespace() string GetKind() schema.GroupKind GetClient() clientset.Interface GetScalesGetter() scaleclient.ScalesGetter SetClient(clientset.Interface) SetScalesClient(scaleclient.ScalesGetter) GetReplicas() int GetLabelValue(string) (string, bool) GetGroupResource() schema.GroupResource GetGroupVersionResource() schema.GroupVersionResource } type RCConfig struct { Affinity *v1.Affinity Client clientset.Interface ScalesGetter scaleclient.ScalesGetter 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 GpuLimit int64 // count PodResources *v1.ResourceRequirements // Pod-level resources ReadinessProbe *v1.Probe DNSPolicy *v1.DNSPolicy PriorityClassName string TerminationGracePeriodSeconds *int64 Lifecycle *v1.Lifecycle SchedulerName string // Env vars, set the same for every pod. Env map[string]string // Extra labels and annotations added to every pod. Labels map[string]string Annotations map[string]string // Node selector for pods in the RC. NodeSelector map[string]string // Tolerations for pods in the RC. Tolerations []v1.Toleration // 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 []v1.Volume VolumeMounts []v1.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 *[]*v1.Pod // Maximum allowable container failures. If exceeded, RunRC returns an error. // Defaults to replicas*0.1 if unspecified. MaxContainerFailures *int // Maximum allowed pod deletions count. If exceeded, RunRC returns an error. // Defaults to 0. MaxAllowedPodDeletions int // If set to false starting RC will print progress, otherwise only errors will be printed. Silent bool // If set this function will be used to print log lines instead of klog. LogFunc func(fmt string, args ...interface{}) // If set those functions will be used to gather data from Nodes - in integration tests where no // kubelets are running those variables should be nil. NodeDumpFunc func(ctx context.Context, c clientset.Interface, nodeNames []string, logFunc func(fmt string, args ...interface{})) ContainerDumpFunc func(ctx context.Context, c clientset.Interface, ns string, logFunc func(ftm string, args ...interface{})) // Names of the secrets and configmaps to mount. SecretNames []string ConfigMapNames []string ServiceAccountTokenProjections int // Additional containers to run in the pod AdditionalContainers []v1.Container // Security context for created pods SecurityContext *v1.SecurityContext } func (rc *RCConfig) RCConfigLog(fmt string, args ...interface{}) { if rc.LogFunc != nil { rc.LogFunc(fmt, args...) } klog.Infof(fmt, args...) } func (config *RCConfig) getTerminationGracePeriodSeconds(defaultGrace *int64) *int64 { if config.TerminationGracePeriodSeconds == nil || *config.TerminationGracePeriodSeconds < 0 { return defaultGrace } return config.TerminationGracePeriodSeconds } type DeploymentConfig struct { RCConfig } type ReplicaSetConfig struct { RCConfig } type JobConfig struct { RCConfig } // podInfo contains pod information useful for debugging e2e tests. type podInfo struct { oldHostname string oldPhase string hostname string phase string } // podDiff is a map of pod name to podInfos type podDiff map[string]*podInfo // Print formats and prints the give podDiff. func (p podDiff) String(ignorePhases sets.String) string { ret := "" for name, info := range p { if ignorePhases.Has(info.phase) { continue } if info.phase == nonExist { ret += fmt.Sprintf("Pod %v was deleted, had phase %v and host %v\n", name, info.oldPhase, info.oldHostname) continue } phaseChange, hostChange := false, false msg := fmt.Sprintf("Pod %v ", name) if info.oldPhase != info.phase { phaseChange = true if info.oldPhase == nonExist { msg += fmt.Sprintf("in phase %v ", info.phase) } else { msg += fmt.Sprintf("went from phase: %v -> %v ", info.oldPhase, info.phase) } } if info.oldHostname != info.hostname { hostChange = true if info.oldHostname == nonExist || info.oldHostname == "" { msg += fmt.Sprintf("assigned host %v ", info.hostname) } else { msg += fmt.Sprintf("went from host: %v -> %v ", info.oldHostname, info.hostname) } } if phaseChange || hostChange { ret += msg + "\n" } } return ret } // DeletedPods returns a slice of pods that were present at the beginning // and then disappeared. func (p podDiff) DeletedPods() []string { var deletedPods []string for podName, podInfo := range p { if podInfo.hostname == nonExist { deletedPods = append(deletedPods, podName) } } return deletedPods } // diff computes a podDiff given 2 lists of pods. func diff(oldPods []*v1.Pod, curPods []*v1.Pod) podDiff { podInfoMap := podDiff{} // New pods will show up in the curPods list but not in oldPods. They have oldhostname/phase == nonexist. for _, pod := range curPods { podInfoMap[pod.Name] = &podInfo{hostname: pod.Spec.NodeName, phase: string(pod.Status.Phase), oldHostname: nonExist, oldPhase: nonExist} } // Deleted pods will show up in the oldPods list but not in curPods. They have a hostname/phase == nonexist. for _, pod := range oldPods { if info, ok := podInfoMap[pod.Name]; ok { info.oldHostname, info.oldPhase = pod.Spec.NodeName, string(pod.Status.Phase) } else { podInfoMap[pod.Name] = &podInfo{hostname: nonExist, phase: nonExist, oldHostname: pod.Spec.NodeName, oldPhase: string(pod.Status.Phase)} } } return podInfoMap } // RunDeployment Launches (and verifies correctness) of a Deployment // and will wait for all pods it spawns to become "Running". // It's the caller's responsibility to clean up externally (i.e. use the // namespace lifecycle for handling Cleanup). func RunDeployment(ctx context.Context, config DeploymentConfig) error { err := config.create() if err != nil { return err } return config.start(ctx) } func (config *DeploymentConfig) create() error { deployment := &apps.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: config.Name, }, Spec: apps.DeploymentSpec{ Replicas: ptr.To[int32](int32(config.Replicas)), Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "name": config.Name, }, }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"name": config.Name}, Annotations: config.Annotations, }, Spec: v1.PodSpec{ Affinity: config.Affinity, TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil), Containers: []v1.Container{ { Name: config.Name, Image: config.Image, Command: config.Command, Ports: []v1.ContainerPort{{ContainerPort: 80}}, Lifecycle: config.Lifecycle, SecurityContext: config.SecurityContext, }, }, }, }, }, } if config.PodResources != nil { deployment.Spec.Template.Spec.Resources = config.PodResources.DeepCopy() } if len(config.AdditionalContainers) > 0 { deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, config.AdditionalContainers...) } if len(config.SecretNames) > 0 { attachSecrets(&deployment.Spec.Template, config.SecretNames) } if len(config.ConfigMapNames) > 0 { attachConfigMaps(&deployment.Spec.Template, config.ConfigMapNames) } for i := 0; i < config.ServiceAccountTokenProjections; i++ { attachServiceAccountTokenProjection(&deployment.Spec.Template, fmt.Sprintf("tok-%d", i)) } config.applyTo(&deployment.Spec.Template) if err := CreateDeploymentWithRetries(config.Client, config.Namespace, deployment); err != nil { return fmt.Errorf("error creating deployment: %v", err) } config.RCConfigLog("Created deployment with name: %v, namespace: %v, replica count: %v", deployment.Name, config.Namespace, ptr.Deref(deployment.Spec.Replicas, 0)) return nil } // RunReplicaSet launches (and verifies correctness) of a ReplicaSet // and waits until all the pods it launches to reach the "Running" state. // It's the caller's responsibility to clean up externally (i.e. use the // namespace lifecycle for handling Cleanup). func RunReplicaSet(ctx context.Context, config ReplicaSetConfig) error { err := config.create() if err != nil { return err } return config.start(ctx) } func (config *ReplicaSetConfig) create() error { rs := &apps.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: config.Name, }, Spec: apps.ReplicaSetSpec{ Replicas: ptr.To[int32](int32(config.Replicas)), Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "name": config.Name, }, }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"name": config.Name}, Annotations: config.Annotations, }, Spec: v1.PodSpec{ Affinity: config.Affinity, TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil), Containers: []v1.Container{ { Name: config.Name, Image: config.Image, Command: config.Command, Ports: []v1.ContainerPort{{ContainerPort: 80}}, Lifecycle: config.Lifecycle, SecurityContext: config.SecurityContext, }, }, }, }, }, } if config.PodResources != nil { rs.Spec.Template.Spec.Resources = config.PodResources.DeepCopy() } if len(config.AdditionalContainers) > 0 { rs.Spec.Template.Spec.Containers = append(rs.Spec.Template.Spec.Containers, config.AdditionalContainers...) } if len(config.SecretNames) > 0 { attachSecrets(&rs.Spec.Template, config.SecretNames) } if len(config.ConfigMapNames) > 0 { attachConfigMaps(&rs.Spec.Template, config.ConfigMapNames) } config.applyTo(&rs.Spec.Template) if err := CreateReplicaSetWithRetries(config.Client, config.Namespace, rs); err != nil { return fmt.Errorf("error creating replica set: %v", err) } config.RCConfigLog("Created replica set with name: %v, namespace: %v, replica count: %v", rs.Name, config.Namespace, ptr.Deref(rs.Spec.Replicas, 0)) return nil } // RunRC Launches (and verifies correctness) of a Replication Controller // and will wait for all pods it spawns to become "Running". // It's the caller's responsibility to clean up externally (i.e. use the // namespace lifecycle for handling Cleanup). func RunRC(ctx context.Context, config RCConfig) error { err := config.create() if err != nil { return err } return config.start(ctx) } func (config *RCConfig) create() error { dnsDefault := v1.DNSDefault if config.DNSPolicy == nil { config.DNSPolicy = &dnsDefault } one := int64(1) rc := &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: config.Name, }, Spec: v1.ReplicationControllerSpec{ Replicas: ptr.To[int32](int32(config.Replicas)), Selector: map[string]string{ "name": config.Name, }, Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"name": config.Name}, Annotations: config.Annotations, }, Spec: v1.PodSpec{ SchedulerName: config.SchedulerName, Affinity: config.Affinity, Containers: []v1.Container{ { Name: config.Name, Image: config.Image, Command: config.Command, Ports: []v1.ContainerPort{{ContainerPort: 80}}, ReadinessProbe: config.ReadinessProbe, Lifecycle: config.Lifecycle, SecurityContext: config.SecurityContext, }, }, DNSPolicy: *config.DNSPolicy, NodeSelector: config.NodeSelector, Tolerations: config.Tolerations, TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(&one), PriorityClassName: config.PriorityClassName, }, }, }, } if config.PodResources != nil { rc.Spec.Template.Spec.Resources = config.PodResources.DeepCopy() } if len(config.AdditionalContainers) > 0 { rc.Spec.Template.Spec.Containers = append(rc.Spec.Template.Spec.Containers, config.AdditionalContainers...) } if len(config.SecretNames) > 0 { attachSecrets(rc.Spec.Template, config.SecretNames) } if len(config.ConfigMapNames) > 0 { attachConfigMaps(rc.Spec.Template, config.ConfigMapNames) } config.applyTo(rc.Spec.Template) if err := CreateRCWithRetries(config.Client, config.Namespace, rc); err != nil { return fmt.Errorf("error creating replication controller: %v", err) } config.RCConfigLog("Created replication controller with name: %v, namespace: %v, replica count: %v", rc.Name, config.Namespace, ptr.Deref(rc.Spec.Replicas, 0)) return nil } func (config *RCConfig) applyTo(template *v1.PodTemplateSpec) { for k, v := range config.Env { c := &template.Spec.Containers[0] c.Env = append(c.Env, v1.EnvVar{Name: k, Value: v}) } for k, v := range config.Labels { template.ObjectMeta.Labels[k] = v } template.Spec.NodeSelector = make(map[string]string) for k, v := range config.NodeSelector { template.Spec.NodeSelector[k] = v } if config.Tolerations != nil { template.Spec.Tolerations = append([]v1.Toleration{}, config.Tolerations...) } for k, v := range config.Ports { c := &template.Spec.Containers[0] c.Ports = append(c.Ports, v1.ContainerPort{Name: k, ContainerPort: int32(v)}) } for k, v := range config.HostPorts { c := &template.Spec.Containers[0] c.Ports = append(c.Ports, v1.ContainerPort{Name: k, ContainerPort: int32(v), HostPort: int32(v)}) } if config.CPULimit > 0 || config.MemLimit > 0 || config.GpuLimit > 0 { template.Spec.Containers[0].Resources.Limits = v1.ResourceList{} } if config.CPULimit > 0 { template.Spec.Containers[0].Resources.Limits[v1.ResourceCPU] = *resource.NewMilliQuantity(config.CPULimit, resource.DecimalSI) } if config.MemLimit > 0 { template.Spec.Containers[0].Resources.Limits[v1.ResourceMemory] = *resource.NewQuantity(config.MemLimit, resource.DecimalSI) } if config.CPURequest > 0 || config.MemRequest > 0 { template.Spec.Containers[0].Resources.Requests = v1.ResourceList{} } if config.CPURequest > 0 { template.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(config.CPURequest, resource.DecimalSI) } if config.MemRequest > 0 { template.Spec.Containers[0].Resources.Requests[v1.ResourceMemory] = *resource.NewQuantity(config.MemRequest, resource.DecimalSI) } if config.GpuLimit > 0 { template.Spec.Containers[0].Resources.Limits["nvidia.com/gpu"] = *resource.NewQuantity(config.GpuLimit, resource.DecimalSI) } if config.Lifecycle != nil { template.Spec.Containers[0].Lifecycle = config.Lifecycle } if len(config.Volumes) > 0 { template.Spec.Volumes = config.Volumes } if len(config.VolumeMounts) > 0 { template.Spec.Containers[0].VolumeMounts = config.VolumeMounts } if config.PriorityClassName != "" { template.Spec.PriorityClassName = config.PriorityClassName } } type RCStartupStatus struct { Expected int Terminating int Running int RunningButNotReady int Waiting int Pending int Scheduled int Unknown int Inactive int FailedContainers int Created []*v1.Pod ContainerRestartNodes sets.String } func (s *RCStartupStatus) String(name string) string { return fmt.Sprintf("%v Pods: %d out of %d created, %d running, %d pending, %d waiting, %d inactive, %d terminating, %d unknown, %d runningButNotReady ", name, len(s.Created), s.Expected, s.Running, s.Pending, s.Waiting, s.Inactive, s.Terminating, s.Unknown, s.RunningButNotReady) } func computeRCStartupStatus(pods []*v1.Pod, expected int) RCStartupStatus { startupStatus := RCStartupStatus{ Expected: expected, Created: make([]*v1.Pod, 0, expected), ContainerRestartNodes: sets.NewString(), } for _, p := range pods { if p.DeletionTimestamp != nil { startupStatus.Terminating++ continue } startupStatus.Created = append(startupStatus.Created, p) if p.Status.Phase == v1.PodRunning { ready := false for _, c := range p.Status.Conditions { if c.Type == v1.PodReady && c.Status == v1.ConditionTrue { ready = true break } } if ready { // Only count a pod is running when it is also ready. startupStatus.Running++ } else { startupStatus.RunningButNotReady++ } for _, v := range FailedContainers(p) { startupStatus.FailedContainers = startupStatus.FailedContainers + v.Restarts startupStatus.ContainerRestartNodes.Insert(p.Spec.NodeName) } } else if p.Status.Phase == v1.PodPending { if p.Spec.NodeName == "" { startupStatus.Waiting++ } else { startupStatus.Pending++ } } else if p.Status.Phase == v1.PodSucceeded || p.Status.Phase == v1.PodFailed { startupStatus.Inactive++ } else if p.Status.Phase == v1.PodUnknown { startupStatus.Unknown++ } // Record count of scheduled pods (useful for computing scheduler throughput). if p.Spec.NodeName != "" { startupStatus.Scheduled++ } } return startupStatus } func (config *RCConfig) start(ctx context.Context) error { // Don't force tests to fail if they don't care about containers restarting. var maxContainerFailures int if config.MaxContainerFailures == nil { maxContainerFailures = int(math.Max(1.0, float64(config.Replicas)*.01)) } else { maxContainerFailures = *config.MaxContainerFailures } label := labels.SelectorFromSet(labels.Set(map[string]string{"name": config.Name})) ps, err := NewPodStore(config.Client, config.Namespace, label, fields.Everything()) if err != nil { return err } defer ps.Stop() interval := config.PollInterval if interval <= 0 { interval = 10 * time.Second } timeout := config.Timeout if timeout <= 0 { timeout = 5 * time.Minute } oldPods := make([]*v1.Pod, 0) oldRunning := 0 lastChange := time.Now() podDeletionsCount := 0 for oldRunning != config.Replicas { time.Sleep(interval) pods := ps.List() startupStatus := computeRCStartupStatus(pods, config.Replicas) if config.CreatedPods != nil { *config.CreatedPods = startupStatus.Created } if !config.Silent { config.RCConfigLog("%s", startupStatus.String(config.Name)) } if config.PodStatusFile != nil { fmt.Fprintf(config.PodStatusFile, "%d, running, %d, pending, %d, waiting, %d, inactive, %d, unknown, %d, runningButNotReady\n", startupStatus.Running, startupStatus.Pending, startupStatus.Waiting, startupStatus.Inactive, startupStatus.Unknown, startupStatus.RunningButNotReady) } if startupStatus.FailedContainers > maxContainerFailures { if config.NodeDumpFunc != nil { config.NodeDumpFunc(ctx, config.Client, startupStatus.ContainerRestartNodes.List(), config.RCConfigLog) } if config.ContainerDumpFunc != nil { // Get the logs from the failed containers to help diagnose what caused them to fail config.ContainerDumpFunc(ctx, config.Client, config.Namespace, config.RCConfigLog) } return fmt.Errorf("%d containers failed which is more than allowed %d", startupStatus.FailedContainers, maxContainerFailures) } diff := diff(oldPods, pods) deletedPods := diff.DeletedPods() podDeletionsCount += len(deletedPods) if podDeletionsCount > config.MaxAllowedPodDeletions { // Number of pods which disappeared is over threshold err := fmt.Errorf("%d pods disappeared for %s: %v", podDeletionsCount, config.Name, strings.Join(deletedPods, ", ")) config.RCConfigLog("%s", err.Error()) config.RCConfigLog("%s", diff.String(sets.NewString())) return err } if len(pods) > len(oldPods) || startupStatus.Running > oldRunning { lastChange = time.Now() } oldPods = pods oldRunning = startupStatus.Running if time.Since(lastChange) > timeout { break } } if oldRunning != config.Replicas { // List only pods from a given replication controller. options := metav1.ListOptions{LabelSelector: label.String()} if pods, err := config.Client.CoreV1().Pods(config.Namespace).List(ctx, options); err == nil { for _, pod := range pods.Items { config.RCConfigLog("Pod %s\t%s\t%s\t%s", pod.Name, pod.Spec.NodeName, pod.Status.Phase, pod.DeletionTimestamp) } } else { config.RCConfigLog("Can't list pod debug info: %v", err) } return fmt.Errorf("only %d pods started out of %d", oldRunning, config.Replicas) } return nil } // Simplified version of RunRC, that does not create RC, but creates plain Pods. // Optionally waits for pods to start running (if waitForRunning == true). // The number of replicas must be non-zero. func StartPods(c clientset.Interface, replicas int, namespace string, podNamePrefix string, pod v1.Pod, waitForRunning bool, logFunc func(fmt string, args ...interface{})) error { // no pod to start if replicas < 1 { panic("StartPods: number of replicas must be non-zero") } startPodsID := string(uuid.NewUUID()) // So that we can label and find them for i := range replicas { podName := fmt.Sprintf("%v-%v", podNamePrefix, i) pod.ObjectMeta.Name = podName pod.ObjectMeta.Labels["name"] = podName pod.ObjectMeta.Labels["startPodsID"] = startPodsID pod.Spec.Containers[0].Name = podName if err := CreatePodWithRetries(c, namespace, &pod); err != nil { return err } } logFunc("Waiting for running...") if waitForRunning { label := labels.SelectorFromSet(labels.Set(map[string]string{"startPodsID": startPodsID})) err := WaitForPodsWithLabelRunning(c, namespace, label) if err != nil { return fmt.Errorf("error waiting for %d pods to be running - probably a timeout: %v", replicas, err) } } return nil } // Wait up to 10 minutes for all matching pods to become Running and at least one // matching pod exists. func WaitForPodsWithLabelRunning(c clientset.Interface, ns string, label labels.Selector) error { return WaitForEnoughPodsWithLabelRunning(c, ns, label, -1) } // Wait up to 10 minutes for at least 'replicas' many pods to be Running and at least // one matching pod exists. If 'replicas' is < 0, wait for all matching pods running. func WaitForEnoughPodsWithLabelRunning(c clientset.Interface, ns string, label labels.Selector, replicas int) error { running := false ps, err := NewPodStore(c, ns, label, fields.Everything()) if err != nil { return err } defer ps.Stop() for start := time.Now(); time.Since(start) < 10*time.Minute; time.Sleep(5 * time.Second) { pods := ps.List() if len(pods) == 0 { continue } runningPodsCount := 0 for _, p := range pods { if p.Status.Phase == v1.PodRunning { runningPodsCount++ } } if (replicas < 0 && runningPodsCount < len(pods)) || (runningPodsCount < replicas) { continue } running = true break } if !running { return fmt.Errorf("timeout while waiting for pods with labels %q to be running", label.String()) } return nil } // TODO: attach secrets using different possibilities: env vars, image pull secrets. func attachSecrets(template *v1.PodTemplateSpec, secretNames []string) { volumes := make([]v1.Volume, 0, len(secretNames)) mounts := make([]v1.VolumeMount, 0, len(secretNames)) for _, name := range secretNames { volumes = append(volumes, v1.Volume{ Name: name, VolumeSource: v1.VolumeSource{ Secret: &v1.SecretVolumeSource{ SecretName: name, }, }, }) mounts = append(mounts, v1.VolumeMount{ Name: name, MountPath: fmt.Sprintf("/%v", name), }) } template.Spec.Volumes = volumes template.Spec.Containers[0].VolumeMounts = mounts } // TODO: attach configmaps using different possibilities: env vars. func attachConfigMaps(template *v1.PodTemplateSpec, configMapNames []string) { volumes := make([]v1.Volume, 0, len(configMapNames)) mounts := make([]v1.VolumeMount, 0, len(configMapNames)) for _, name := range configMapNames { volumes = append(volumes, v1.Volume{ Name: name, VolumeSource: v1.VolumeSource{ ConfigMap: &v1.ConfigMapVolumeSource{ LocalObjectReference: v1.LocalObjectReference{ Name: name, }, }, }, }) mounts = append(mounts, v1.VolumeMount{ Name: name, MountPath: fmt.Sprintf("/%v", name), }) } template.Spec.Volumes = volumes template.Spec.Containers[0].VolumeMounts = mounts } func attachServiceAccountTokenProjection(template *v1.PodTemplateSpec, name string) { template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{ Name: name, MountPath: "/var/service-account-tokens/" + name, }) template.Spec.Volumes = append(template.Spec.Volumes, v1.Volume{ Name: name, VolumeSource: v1.VolumeSource{ Projected: &v1.ProjectedVolumeSource{ Sources: []v1.VolumeProjection{ { ServiceAccountToken: &v1.ServiceAccountTokenProjection{ Path: "token", Audience: name, }, }, { ConfigMap: &v1.ConfigMapProjection{ LocalObjectReference: v1.LocalObjectReference{ Name: "kube-root-ca-crt", }, Items: []v1.KeyToPath{ { Key: "ca.crt", Path: "ca.crt", }, }, }, }, { DownwardAPI: &v1.DownwardAPIProjection{ Items: []v1.DownwardAPIVolumeFile{ { Path: "namespace", FieldRef: &v1.ObjectFieldSelector{ APIVersion: "v1", FieldPath: "metadata.namespace", }, }, }, }, }, }, }, }, }) }