k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/utils/runners.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package utils 18 19 import ( 20 "context" 21 "fmt" 22 "math" 23 "os" 24 "strings" 25 "sync" 26 "time" 27 28 apps "k8s.io/api/apps/v1" 29 v1 "k8s.io/api/core/v1" 30 storagev1 "k8s.io/api/storage/v1" 31 storagev1beta1 "k8s.io/api/storage/v1beta1" 32 apierrors "k8s.io/apimachinery/pkg/api/errors" 33 "k8s.io/apimachinery/pkg/api/resource" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/fields" 36 "k8s.io/apimachinery/pkg/labels" 37 "k8s.io/apimachinery/pkg/runtime/schema" 38 "k8s.io/apimachinery/pkg/types" 39 "k8s.io/apimachinery/pkg/util/json" 40 "k8s.io/apimachinery/pkg/util/sets" 41 "k8s.io/apimachinery/pkg/util/strategicpatch" 42 "k8s.io/apimachinery/pkg/util/uuid" 43 "k8s.io/apimachinery/pkg/util/wait" 44 clientset "k8s.io/client-go/kubernetes" 45 scaleclient "k8s.io/client-go/scale" 46 "k8s.io/client-go/util/workqueue" 47 api "k8s.io/kubernetes/pkg/apis/core" 48 extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions" 49 "k8s.io/utils/pointer" 50 51 "k8s.io/klog/v2" 52 ) 53 54 const ( 55 // String used to mark pod deletion 56 nonExist = "NonExist" 57 ) 58 59 func removePtr(replicas *int32) int32 { 60 if replicas == nil { 61 return 0 62 } 63 return *replicas 64 } 65 66 func waitUntilPodIsScheduled(ctx context.Context, c clientset.Interface, name, namespace string, timeout time.Duration) (*v1.Pod, error) { 67 // Wait until it's scheduled 68 p, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{ResourceVersion: "0"}) 69 if err == nil && p.Spec.NodeName != "" { 70 return p, nil 71 } 72 pollingPeriod := 200 * time.Millisecond 73 startTime := time.Now() 74 for startTime.Add(timeout).After(time.Now()) && ctx.Err() == nil { 75 time.Sleep(pollingPeriod) 76 p, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{ResourceVersion: "0"}) 77 if err == nil && p.Spec.NodeName != "" { 78 return p, nil 79 } 80 } 81 return nil, fmt.Errorf("timed out after %v when waiting for pod %v/%v to start", timeout, namespace, name) 82 } 83 84 func RunPodAndGetNodeName(ctx context.Context, c clientset.Interface, pod *v1.Pod, timeout time.Duration) (string, error) { 85 name := pod.Name 86 namespace := pod.Namespace 87 if err := CreatePodWithRetries(c, namespace, pod); err != nil { 88 return "", err 89 } 90 p, err := waitUntilPodIsScheduled(ctx, c, name, namespace, timeout) 91 if err != nil { 92 return "", err 93 } 94 return p.Spec.NodeName, nil 95 } 96 97 type RunObjectConfig interface { 98 Run() error 99 GetName() string 100 GetNamespace() string 101 GetKind() schema.GroupKind 102 GetClient() clientset.Interface 103 GetScalesGetter() scaleclient.ScalesGetter 104 SetClient(clientset.Interface) 105 SetScalesClient(scaleclient.ScalesGetter) 106 GetReplicas() int 107 GetLabelValue(string) (string, bool) 108 GetGroupResource() schema.GroupResource 109 GetGroupVersionResource() schema.GroupVersionResource 110 } 111 112 type RCConfig struct { 113 Affinity *v1.Affinity 114 Client clientset.Interface 115 ScalesGetter scaleclient.ScalesGetter 116 Image string 117 Command []string 118 Name string 119 Namespace string 120 PollInterval time.Duration 121 Timeout time.Duration 122 PodStatusFile *os.File 123 Replicas int 124 CpuRequest int64 // millicores 125 CpuLimit int64 // millicores 126 MemRequest int64 // bytes 127 MemLimit int64 // bytes 128 GpuLimit int64 // count 129 ReadinessProbe *v1.Probe 130 DNSPolicy *v1.DNSPolicy 131 PriorityClassName string 132 TerminationGracePeriodSeconds *int64 133 Lifecycle *v1.Lifecycle 134 SchedulerName string 135 136 // Env vars, set the same for every pod. 137 Env map[string]string 138 139 // Extra labels and annotations added to every pod. 140 Labels map[string]string 141 Annotations map[string]string 142 143 // Node selector for pods in the RC. 144 NodeSelector map[string]string 145 146 // Tolerations for pods in the RC. 147 Tolerations []v1.Toleration 148 149 // Ports to declare in the container (map of name to containerPort). 150 Ports map[string]int 151 // Ports to declare in the container as host and container ports. 152 HostPorts map[string]int 153 154 Volumes []v1.Volume 155 VolumeMounts []v1.VolumeMount 156 157 // Pointer to a list of pods; if non-nil, will be set to a list of pods 158 // created by this RC by RunRC. 159 CreatedPods *[]*v1.Pod 160 161 // Maximum allowable container failures. If exceeded, RunRC returns an error. 162 // Defaults to replicas*0.1 if unspecified. 163 MaxContainerFailures *int 164 // Maximum allowed pod deletions count. If exceeded, RunRC returns an error. 165 // Defaults to 0. 166 MaxAllowedPodDeletions int 167 168 // If set to false starting RC will print progress, otherwise only errors will be printed. 169 Silent bool 170 171 // If set this function will be used to print log lines instead of klog. 172 LogFunc func(fmt string, args ...interface{}) 173 // If set those functions will be used to gather data from Nodes - in integration tests where no 174 // kubelets are running those variables should be nil. 175 NodeDumpFunc func(ctx context.Context, c clientset.Interface, nodeNames []string, logFunc func(fmt string, args ...interface{})) 176 ContainerDumpFunc func(ctx context.Context, c clientset.Interface, ns string, logFunc func(ftm string, args ...interface{})) 177 178 // Names of the secrets and configmaps to mount. 179 SecretNames []string 180 ConfigMapNames []string 181 182 ServiceAccountTokenProjections int 183 184 // Additional containers to run in the pod 185 AdditionalContainers []v1.Container 186 187 // Security context for created pods 188 SecurityContext *v1.SecurityContext 189 } 190 191 func (rc *RCConfig) RCConfigLog(fmt string, args ...interface{}) { 192 if rc.LogFunc != nil { 193 rc.LogFunc(fmt, args...) 194 } 195 klog.Infof(fmt, args...) 196 } 197 198 type DeploymentConfig struct { 199 RCConfig 200 } 201 202 type ReplicaSetConfig struct { 203 RCConfig 204 } 205 206 type JobConfig struct { 207 RCConfig 208 } 209 210 // podInfo contains pod information useful for debugging e2e tests. 211 type podInfo struct { 212 oldHostname string 213 oldPhase string 214 hostname string 215 phase string 216 } 217 218 // podDiff is a map of pod name to podInfos 219 type podDiff map[string]*podInfo 220 221 // Print formats and prints the give podDiff. 222 func (p podDiff) String(ignorePhases sets.String) string { 223 ret := "" 224 for name, info := range p { 225 if ignorePhases.Has(info.phase) { 226 continue 227 } 228 if info.phase == nonExist { 229 ret += fmt.Sprintf("Pod %v was deleted, had phase %v and host %v\n", name, info.oldPhase, info.oldHostname) 230 continue 231 } 232 phaseChange, hostChange := false, false 233 msg := fmt.Sprintf("Pod %v ", name) 234 if info.oldPhase != info.phase { 235 phaseChange = true 236 if info.oldPhase == nonExist { 237 msg += fmt.Sprintf("in phase %v ", info.phase) 238 } else { 239 msg += fmt.Sprintf("went from phase: %v -> %v ", info.oldPhase, info.phase) 240 } 241 } 242 if info.oldHostname != info.hostname { 243 hostChange = true 244 if info.oldHostname == nonExist || info.oldHostname == "" { 245 msg += fmt.Sprintf("assigned host %v ", info.hostname) 246 } else { 247 msg += fmt.Sprintf("went from host: %v -> %v ", info.oldHostname, info.hostname) 248 } 249 } 250 if phaseChange || hostChange { 251 ret += msg + "\n" 252 } 253 } 254 return ret 255 } 256 257 // DeletedPods returns a slice of pods that were present at the beginning 258 // and then disappeared. 259 func (p podDiff) DeletedPods() []string { 260 var deletedPods []string 261 for podName, podInfo := range p { 262 if podInfo.hostname == nonExist { 263 deletedPods = append(deletedPods, podName) 264 } 265 } 266 return deletedPods 267 } 268 269 // diff computes a podDiff given 2 lists of pods. 270 func diff(oldPods []*v1.Pod, curPods []*v1.Pod) podDiff { 271 podInfoMap := podDiff{} 272 273 // New pods will show up in the curPods list but not in oldPods. They have oldhostname/phase == nonexist. 274 for _, pod := range curPods { 275 podInfoMap[pod.Name] = &podInfo{hostname: pod.Spec.NodeName, phase: string(pod.Status.Phase), oldHostname: nonExist, oldPhase: nonExist} 276 } 277 278 // Deleted pods will show up in the oldPods list but not in curPods. They have a hostname/phase == nonexist. 279 for _, pod := range oldPods { 280 if info, ok := podInfoMap[pod.Name]; ok { 281 info.oldHostname, info.oldPhase = pod.Spec.NodeName, string(pod.Status.Phase) 282 } else { 283 podInfoMap[pod.Name] = &podInfo{hostname: nonExist, phase: nonExist, oldHostname: pod.Spec.NodeName, oldPhase: string(pod.Status.Phase)} 284 } 285 } 286 return podInfoMap 287 } 288 289 // RunDeployment Launches (and verifies correctness) of a Deployment 290 // and will wait for all pods it spawns to become "Running". 291 // It's the caller's responsibility to clean up externally (i.e. use the 292 // namespace lifecycle for handling Cleanup). 293 func RunDeployment(ctx context.Context, config DeploymentConfig) error { 294 err := config.create() 295 if err != nil { 296 return err 297 } 298 return config.start(ctx) 299 } 300 301 func (config *DeploymentConfig) Run(ctx context.Context) error { 302 return RunDeployment(ctx, *config) 303 } 304 305 func (config *DeploymentConfig) GetKind() schema.GroupKind { 306 return extensionsinternal.Kind("Deployment") 307 } 308 309 func (config *DeploymentConfig) GetGroupResource() schema.GroupResource { 310 return extensionsinternal.Resource("deployments") 311 } 312 313 func (config *DeploymentConfig) GetGroupVersionResource() schema.GroupVersionResource { 314 return extensionsinternal.SchemeGroupVersion.WithResource("deployments") 315 } 316 317 func (config *DeploymentConfig) create() error { 318 deployment := &apps.Deployment{ 319 ObjectMeta: metav1.ObjectMeta{ 320 Name: config.Name, 321 }, 322 Spec: apps.DeploymentSpec{ 323 Replicas: pointer.Int32(int32(config.Replicas)), 324 Selector: &metav1.LabelSelector{ 325 MatchLabels: map[string]string{ 326 "name": config.Name, 327 }, 328 }, 329 Template: v1.PodTemplateSpec{ 330 ObjectMeta: metav1.ObjectMeta{ 331 Labels: map[string]string{"name": config.Name}, 332 Annotations: config.Annotations, 333 }, 334 Spec: v1.PodSpec{ 335 Affinity: config.Affinity, 336 TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil), 337 Containers: []v1.Container{ 338 { 339 Name: config.Name, 340 Image: config.Image, 341 Command: config.Command, 342 Ports: []v1.ContainerPort{{ContainerPort: 80}}, 343 Lifecycle: config.Lifecycle, 344 SecurityContext: config.SecurityContext, 345 }, 346 }, 347 }, 348 }, 349 }, 350 } 351 352 if len(config.AdditionalContainers) > 0 { 353 deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, config.AdditionalContainers...) 354 } 355 356 if len(config.SecretNames) > 0 { 357 attachSecrets(&deployment.Spec.Template, config.SecretNames) 358 } 359 if len(config.ConfigMapNames) > 0 { 360 attachConfigMaps(&deployment.Spec.Template, config.ConfigMapNames) 361 } 362 363 for i := 0; i < config.ServiceAccountTokenProjections; i++ { 364 attachServiceAccountTokenProjection(&deployment.Spec.Template, fmt.Sprintf("tok-%d", i)) 365 } 366 367 config.applyTo(&deployment.Spec.Template) 368 369 if err := CreateDeploymentWithRetries(config.Client, config.Namespace, deployment); err != nil { 370 return fmt.Errorf("error creating deployment: %v", err) 371 } 372 config.RCConfigLog("Created deployment with name: %v, namespace: %v, replica count: %v", deployment.Name, config.Namespace, removePtr(deployment.Spec.Replicas)) 373 return nil 374 } 375 376 // RunReplicaSet launches (and verifies correctness) of a ReplicaSet 377 // and waits until all the pods it launches to reach the "Running" state. 378 // It's the caller's responsibility to clean up externally (i.e. use the 379 // namespace lifecycle for handling Cleanup). 380 func RunReplicaSet(ctx context.Context, config ReplicaSetConfig) error { 381 err := config.create() 382 if err != nil { 383 return err 384 } 385 return config.start(ctx) 386 } 387 388 func (config *ReplicaSetConfig) Run(ctx context.Context) error { 389 return RunReplicaSet(ctx, *config) 390 } 391 392 func (config *ReplicaSetConfig) GetKind() schema.GroupKind { 393 return extensionsinternal.Kind("ReplicaSet") 394 } 395 396 func (config *ReplicaSetConfig) GetGroupResource() schema.GroupResource { 397 return extensionsinternal.Resource("replicasets") 398 } 399 400 func (config *ReplicaSetConfig) GetGroupVersionResource() schema.GroupVersionResource { 401 return extensionsinternal.SchemeGroupVersion.WithResource("replicasets") 402 } 403 404 func (config *ReplicaSetConfig) create() error { 405 rs := &apps.ReplicaSet{ 406 ObjectMeta: metav1.ObjectMeta{ 407 Name: config.Name, 408 }, 409 Spec: apps.ReplicaSetSpec{ 410 Replicas: pointer.Int32(int32(config.Replicas)), 411 Selector: &metav1.LabelSelector{ 412 MatchLabels: map[string]string{ 413 "name": config.Name, 414 }, 415 }, 416 Template: v1.PodTemplateSpec{ 417 ObjectMeta: metav1.ObjectMeta{ 418 Labels: map[string]string{"name": config.Name}, 419 Annotations: config.Annotations, 420 }, 421 Spec: v1.PodSpec{ 422 Affinity: config.Affinity, 423 TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(nil), 424 Containers: []v1.Container{ 425 { 426 Name: config.Name, 427 Image: config.Image, 428 Command: config.Command, 429 Ports: []v1.ContainerPort{{ContainerPort: 80}}, 430 Lifecycle: config.Lifecycle, 431 SecurityContext: config.SecurityContext, 432 }, 433 }, 434 }, 435 }, 436 }, 437 } 438 439 if len(config.AdditionalContainers) > 0 { 440 rs.Spec.Template.Spec.Containers = append(rs.Spec.Template.Spec.Containers, config.AdditionalContainers...) 441 } 442 443 if len(config.SecretNames) > 0 { 444 attachSecrets(&rs.Spec.Template, config.SecretNames) 445 } 446 if len(config.ConfigMapNames) > 0 { 447 attachConfigMaps(&rs.Spec.Template, config.ConfigMapNames) 448 } 449 450 config.applyTo(&rs.Spec.Template) 451 452 if err := CreateReplicaSetWithRetries(config.Client, config.Namespace, rs); err != nil { 453 return fmt.Errorf("error creating replica set: %v", err) 454 } 455 config.RCConfigLog("Created replica set with name: %v, namespace: %v, replica count: %v", rs.Name, config.Namespace, removePtr(rs.Spec.Replicas)) 456 return nil 457 } 458 459 // RunRC Launches (and verifies correctness) of a Replication Controller 460 // and will wait for all pods it spawns to become "Running". 461 // It's the caller's responsibility to clean up externally (i.e. use the 462 // namespace lifecycle for handling Cleanup). 463 func RunRC(ctx context.Context, config RCConfig) error { 464 err := config.create() 465 if err != nil { 466 return err 467 } 468 return config.start(ctx) 469 } 470 471 func (config *RCConfig) Run(ctx context.Context) error { 472 return RunRC(ctx, *config) 473 } 474 475 func (config *RCConfig) GetName() string { 476 return config.Name 477 } 478 479 func (config *RCConfig) GetNamespace() string { 480 return config.Namespace 481 } 482 483 func (config *RCConfig) GetKind() schema.GroupKind { 484 return api.Kind("ReplicationController") 485 } 486 487 func (config *RCConfig) GetGroupResource() schema.GroupResource { 488 return api.Resource("replicationcontrollers") 489 } 490 491 func (config *RCConfig) GetGroupVersionResource() schema.GroupVersionResource { 492 return api.SchemeGroupVersion.WithResource("replicationcontrollers") 493 } 494 495 func (config *RCConfig) GetClient() clientset.Interface { 496 return config.Client 497 } 498 499 func (config *RCConfig) GetScalesGetter() scaleclient.ScalesGetter { 500 return config.ScalesGetter 501 } 502 503 func (config *RCConfig) SetClient(c clientset.Interface) { 504 config.Client = c 505 } 506 507 func (config *RCConfig) SetScalesClient(getter scaleclient.ScalesGetter) { 508 config.ScalesGetter = getter 509 } 510 511 func (config *RCConfig) GetReplicas() int { 512 return config.Replicas 513 } 514 515 func (config *RCConfig) GetLabelValue(key string) (string, bool) { 516 value, found := config.Labels[key] 517 return value, found 518 } 519 520 func (config *RCConfig) create() error { 521 dnsDefault := v1.DNSDefault 522 if config.DNSPolicy == nil { 523 config.DNSPolicy = &dnsDefault 524 } 525 one := int64(1) 526 rc := &v1.ReplicationController{ 527 ObjectMeta: metav1.ObjectMeta{ 528 Name: config.Name, 529 }, 530 Spec: v1.ReplicationControllerSpec{ 531 Replicas: pointer.Int32(int32(config.Replicas)), 532 Selector: map[string]string{ 533 "name": config.Name, 534 }, 535 Template: &v1.PodTemplateSpec{ 536 ObjectMeta: metav1.ObjectMeta{ 537 Labels: map[string]string{"name": config.Name}, 538 Annotations: config.Annotations, 539 }, 540 Spec: v1.PodSpec{ 541 SchedulerName: config.SchedulerName, 542 Affinity: config.Affinity, 543 Containers: []v1.Container{ 544 { 545 Name: config.Name, 546 Image: config.Image, 547 Command: config.Command, 548 Ports: []v1.ContainerPort{{ContainerPort: 80}}, 549 ReadinessProbe: config.ReadinessProbe, 550 Lifecycle: config.Lifecycle, 551 SecurityContext: config.SecurityContext, 552 }, 553 }, 554 DNSPolicy: *config.DNSPolicy, 555 NodeSelector: config.NodeSelector, 556 Tolerations: config.Tolerations, 557 TerminationGracePeriodSeconds: config.getTerminationGracePeriodSeconds(&one), 558 PriorityClassName: config.PriorityClassName, 559 }, 560 }, 561 }, 562 } 563 564 if len(config.AdditionalContainers) > 0 { 565 rc.Spec.Template.Spec.Containers = append(rc.Spec.Template.Spec.Containers, config.AdditionalContainers...) 566 } 567 568 if len(config.SecretNames) > 0 { 569 attachSecrets(rc.Spec.Template, config.SecretNames) 570 } 571 if len(config.ConfigMapNames) > 0 { 572 attachConfigMaps(rc.Spec.Template, config.ConfigMapNames) 573 } 574 575 config.applyTo(rc.Spec.Template) 576 577 if err := CreateRCWithRetries(config.Client, config.Namespace, rc); err != nil { 578 return fmt.Errorf("error creating replication controller: %v", err) 579 } 580 config.RCConfigLog("Created replication controller with name: %v, namespace: %v, replica count: %v", rc.Name, config.Namespace, removePtr(rc.Spec.Replicas)) 581 return nil 582 } 583 584 func (config *RCConfig) applyTo(template *v1.PodTemplateSpec) { 585 for k, v := range config.Env { 586 c := &template.Spec.Containers[0] 587 c.Env = append(c.Env, v1.EnvVar{Name: k, Value: v}) 588 } 589 for k, v := range config.Labels { 590 template.ObjectMeta.Labels[k] = v 591 } 592 template.Spec.NodeSelector = make(map[string]string) 593 for k, v := range config.NodeSelector { 594 template.Spec.NodeSelector[k] = v 595 } 596 if config.Tolerations != nil { 597 template.Spec.Tolerations = append([]v1.Toleration{}, config.Tolerations...) 598 } 599 for k, v := range config.Ports { 600 c := &template.Spec.Containers[0] 601 c.Ports = append(c.Ports, v1.ContainerPort{Name: k, ContainerPort: int32(v)}) 602 } 603 for k, v := range config.HostPorts { 604 c := &template.Spec.Containers[0] 605 c.Ports = append(c.Ports, v1.ContainerPort{Name: k, ContainerPort: int32(v), HostPort: int32(v)}) 606 } 607 if config.CpuLimit > 0 || config.MemLimit > 0 || config.GpuLimit > 0 { 608 template.Spec.Containers[0].Resources.Limits = v1.ResourceList{} 609 } 610 if config.CpuLimit > 0 { 611 template.Spec.Containers[0].Resources.Limits[v1.ResourceCPU] = *resource.NewMilliQuantity(config.CpuLimit, resource.DecimalSI) 612 } 613 if config.MemLimit > 0 { 614 template.Spec.Containers[0].Resources.Limits[v1.ResourceMemory] = *resource.NewQuantity(config.MemLimit, resource.DecimalSI) 615 } 616 if config.CpuRequest > 0 || config.MemRequest > 0 { 617 template.Spec.Containers[0].Resources.Requests = v1.ResourceList{} 618 } 619 if config.CpuRequest > 0 { 620 template.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(config.CpuRequest, resource.DecimalSI) 621 } 622 if config.MemRequest > 0 { 623 template.Spec.Containers[0].Resources.Requests[v1.ResourceMemory] = *resource.NewQuantity(config.MemRequest, resource.DecimalSI) 624 } 625 if config.GpuLimit > 0 { 626 template.Spec.Containers[0].Resources.Limits["nvidia.com/gpu"] = *resource.NewQuantity(config.GpuLimit, resource.DecimalSI) 627 } 628 if config.Lifecycle != nil { 629 template.Spec.Containers[0].Lifecycle = config.Lifecycle 630 } 631 if len(config.Volumes) > 0 { 632 template.Spec.Volumes = config.Volumes 633 } 634 if len(config.VolumeMounts) > 0 { 635 template.Spec.Containers[0].VolumeMounts = config.VolumeMounts 636 } 637 if config.PriorityClassName != "" { 638 template.Spec.PriorityClassName = config.PriorityClassName 639 } 640 } 641 642 type RCStartupStatus struct { 643 Expected int 644 Terminating int 645 Running int 646 RunningButNotReady int 647 Waiting int 648 Pending int 649 Scheduled int 650 Unknown int 651 Inactive int 652 FailedContainers int 653 Created []*v1.Pod 654 ContainerRestartNodes sets.String 655 } 656 657 func (s *RCStartupStatus) String(name string) string { 658 return fmt.Sprintf("%v Pods: %d out of %d created, %d running, %d pending, %d waiting, %d inactive, %d terminating, %d unknown, %d runningButNotReady ", 659 name, len(s.Created), s.Expected, s.Running, s.Pending, s.Waiting, s.Inactive, s.Terminating, s.Unknown, s.RunningButNotReady) 660 } 661 662 func computeRCStartupStatus(pods []*v1.Pod, expected int) RCStartupStatus { 663 startupStatus := RCStartupStatus{ 664 Expected: expected, 665 Created: make([]*v1.Pod, 0, expected), 666 ContainerRestartNodes: sets.NewString(), 667 } 668 for _, p := range pods { 669 if p.DeletionTimestamp != nil { 670 startupStatus.Terminating++ 671 continue 672 } 673 startupStatus.Created = append(startupStatus.Created, p) 674 if p.Status.Phase == v1.PodRunning { 675 ready := false 676 for _, c := range p.Status.Conditions { 677 if c.Type == v1.PodReady && c.Status == v1.ConditionTrue { 678 ready = true 679 break 680 } 681 } 682 if ready { 683 // Only count a pod is running when it is also ready. 684 startupStatus.Running++ 685 } else { 686 startupStatus.RunningButNotReady++ 687 } 688 for _, v := range FailedContainers(p) { 689 startupStatus.FailedContainers = startupStatus.FailedContainers + v.Restarts 690 startupStatus.ContainerRestartNodes.Insert(p.Spec.NodeName) 691 } 692 } else if p.Status.Phase == v1.PodPending { 693 if p.Spec.NodeName == "" { 694 startupStatus.Waiting++ 695 } else { 696 startupStatus.Pending++ 697 } 698 } else if p.Status.Phase == v1.PodSucceeded || p.Status.Phase == v1.PodFailed { 699 startupStatus.Inactive++ 700 } else if p.Status.Phase == v1.PodUnknown { 701 startupStatus.Unknown++ 702 } 703 // Record count of scheduled pods (useful for computing scheduler throughput). 704 if p.Spec.NodeName != "" { 705 startupStatus.Scheduled++ 706 } 707 } 708 return startupStatus 709 } 710 711 func (config *RCConfig) start(ctx context.Context) error { 712 // Don't force tests to fail if they don't care about containers restarting. 713 var maxContainerFailures int 714 if config.MaxContainerFailures == nil { 715 maxContainerFailures = int(math.Max(1.0, float64(config.Replicas)*.01)) 716 } else { 717 maxContainerFailures = *config.MaxContainerFailures 718 } 719 720 label := labels.SelectorFromSet(labels.Set(map[string]string{"name": config.Name})) 721 722 ps, err := NewPodStore(config.Client, config.Namespace, label, fields.Everything()) 723 if err != nil { 724 return err 725 } 726 defer ps.Stop() 727 728 interval := config.PollInterval 729 if interval <= 0 { 730 interval = 10 * time.Second 731 } 732 timeout := config.Timeout 733 if timeout <= 0 { 734 timeout = 5 * time.Minute 735 } 736 oldPods := make([]*v1.Pod, 0) 737 oldRunning := 0 738 lastChange := time.Now() 739 podDeletionsCount := 0 740 for oldRunning != config.Replicas { 741 time.Sleep(interval) 742 743 pods := ps.List() 744 startupStatus := computeRCStartupStatus(pods, config.Replicas) 745 746 if config.CreatedPods != nil { 747 *config.CreatedPods = startupStatus.Created 748 } 749 if !config.Silent { 750 config.RCConfigLog(startupStatus.String(config.Name)) 751 } 752 753 if config.PodStatusFile != nil { 754 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) 755 } 756 757 if startupStatus.FailedContainers > maxContainerFailures { 758 if config.NodeDumpFunc != nil { 759 config.NodeDumpFunc(ctx, config.Client, startupStatus.ContainerRestartNodes.List(), config.RCConfigLog) 760 } 761 if config.ContainerDumpFunc != nil { 762 // Get the logs from the failed containers to help diagnose what caused them to fail 763 config.ContainerDumpFunc(ctx, config.Client, config.Namespace, config.RCConfigLog) 764 } 765 return fmt.Errorf("%d containers failed which is more than allowed %d", startupStatus.FailedContainers, maxContainerFailures) 766 } 767 768 diff := diff(oldPods, pods) 769 deletedPods := diff.DeletedPods() 770 podDeletionsCount += len(deletedPods) 771 if podDeletionsCount > config.MaxAllowedPodDeletions { 772 // Number of pods which disappeared is over threshold 773 err := fmt.Errorf("%d pods disappeared for %s: %v", podDeletionsCount, config.Name, strings.Join(deletedPods, ", ")) 774 config.RCConfigLog(err.Error()) 775 config.RCConfigLog(diff.String(sets.NewString())) 776 return err 777 } 778 779 if len(pods) > len(oldPods) || startupStatus.Running > oldRunning { 780 lastChange = time.Now() 781 } 782 oldPods = pods 783 oldRunning = startupStatus.Running 784 785 if time.Since(lastChange) > timeout { 786 break 787 } 788 } 789 790 if oldRunning != config.Replicas { 791 // List only pods from a given replication controller. 792 options := metav1.ListOptions{LabelSelector: label.String()} 793 if pods, err := config.Client.CoreV1().Pods(config.Namespace).List(ctx, options); err == nil { 794 for _, pod := range pods.Items { 795 config.RCConfigLog("Pod %s\t%s\t%s\t%s", pod.Name, pod.Spec.NodeName, pod.Status.Phase, pod.DeletionTimestamp) 796 } 797 } else { 798 config.RCConfigLog("Can't list pod debug info: %v", err) 799 } 800 return fmt.Errorf("only %d pods started out of %d", oldRunning, config.Replicas) 801 } 802 return nil 803 } 804 805 // Simplified version of RunRC, that does not create RC, but creates plain Pods. 806 // Optionally waits for pods to start running (if waitForRunning == true). 807 // The number of replicas must be non-zero. 808 func StartPods(c clientset.Interface, replicas int, namespace string, podNamePrefix string, 809 pod v1.Pod, waitForRunning bool, logFunc func(fmt string, args ...interface{})) error { 810 // no pod to start 811 if replicas < 1 { 812 panic("StartPods: number of replicas must be non-zero") 813 } 814 startPodsID := string(uuid.NewUUID()) // So that we can label and find them 815 for i := 0; i < replicas; i++ { 816 podName := fmt.Sprintf("%v-%v", podNamePrefix, i) 817 pod.ObjectMeta.Name = podName 818 pod.ObjectMeta.Labels["name"] = podName 819 pod.ObjectMeta.Labels["startPodsID"] = startPodsID 820 pod.Spec.Containers[0].Name = podName 821 if err := CreatePodWithRetries(c, namespace, &pod); err != nil { 822 return err 823 } 824 } 825 logFunc("Waiting for running...") 826 if waitForRunning { 827 label := labels.SelectorFromSet(labels.Set(map[string]string{"startPodsID": startPodsID})) 828 err := WaitForPodsWithLabelRunning(c, namespace, label) 829 if err != nil { 830 return fmt.Errorf("error waiting for %d pods to be running - probably a timeout: %v", replicas, err) 831 } 832 } 833 return nil 834 } 835 836 // Wait up to 10 minutes for all matching pods to become Running and at least one 837 // matching pod exists. 838 func WaitForPodsWithLabelRunning(c clientset.Interface, ns string, label labels.Selector) error { 839 return WaitForEnoughPodsWithLabelRunning(c, ns, label, -1) 840 } 841 842 // Wait up to 10 minutes for at least 'replicas' many pods to be Running and at least 843 // one matching pod exists. If 'replicas' is < 0, wait for all matching pods running. 844 func WaitForEnoughPodsWithLabelRunning(c clientset.Interface, ns string, label labels.Selector, replicas int) error { 845 running := false 846 ps, err := NewPodStore(c, ns, label, fields.Everything()) 847 if err != nil { 848 return err 849 } 850 defer ps.Stop() 851 852 for start := time.Now(); time.Since(start) < 10*time.Minute; time.Sleep(5 * time.Second) { 853 pods := ps.List() 854 if len(pods) == 0 { 855 continue 856 } 857 runningPodsCount := 0 858 for _, p := range pods { 859 if p.Status.Phase == v1.PodRunning { 860 runningPodsCount++ 861 } 862 } 863 if (replicas < 0 && runningPodsCount < len(pods)) || (runningPodsCount < replicas) { 864 continue 865 } 866 running = true 867 break 868 } 869 if !running { 870 return fmt.Errorf("timeout while waiting for pods with labels %q to be running", label.String()) 871 } 872 return nil 873 } 874 875 type PrepareNodeStrategy interface { 876 // Modify pre-created Node objects before the test starts. 877 PreparePatch(node *v1.Node) []byte 878 // Create or modify any objects that depend on the node before the test starts. 879 // Caller will re-try when http.StatusConflict error is returned. 880 PrepareDependentObjects(ctx context.Context, node *v1.Node, client clientset.Interface) error 881 // Clean up any node modifications after the test finishes. 882 CleanupNode(ctx context.Context, node *v1.Node) *v1.Node 883 // Clean up any objects that depend on the node after the test finishes. 884 // Caller will re-try when http.StatusConflict error is returned. 885 CleanupDependentObjects(ctx context.Context, nodeName string, client clientset.Interface) error 886 } 887 888 type TrivialNodePrepareStrategy struct{} 889 890 var _ PrepareNodeStrategy = &TrivialNodePrepareStrategy{} 891 892 func (*TrivialNodePrepareStrategy) PreparePatch(*v1.Node) []byte { 893 return []byte{} 894 } 895 896 func (*TrivialNodePrepareStrategy) CleanupNode(ctx context.Context, node *v1.Node) *v1.Node { 897 nodeCopy := *node 898 return &nodeCopy 899 } 900 901 func (*TrivialNodePrepareStrategy) PrepareDependentObjects(ctx context.Context, node *v1.Node, client clientset.Interface) error { 902 return nil 903 } 904 905 func (*TrivialNodePrepareStrategy) CleanupDependentObjects(ctx context.Context, nodeName string, client clientset.Interface) error { 906 return nil 907 } 908 909 type LabelNodePrepareStrategy struct { 910 LabelKey string 911 LabelValues []string 912 roundRobinIdx int 913 } 914 915 var _ PrepareNodeStrategy = &LabelNodePrepareStrategy{} 916 917 func NewLabelNodePrepareStrategy(labelKey string, labelValues ...string) *LabelNodePrepareStrategy { 918 return &LabelNodePrepareStrategy{ 919 LabelKey: labelKey, 920 LabelValues: labelValues, 921 } 922 } 923 924 func (s *LabelNodePrepareStrategy) PreparePatch(*v1.Node) []byte { 925 labelString := fmt.Sprintf("{\"%v\":\"%v\"}", s.LabelKey, s.LabelValues[s.roundRobinIdx]) 926 patch := fmt.Sprintf(`{"metadata":{"labels":%v}}`, labelString) 927 s.roundRobinIdx++ 928 if s.roundRobinIdx == len(s.LabelValues) { 929 s.roundRobinIdx = 0 930 } 931 return []byte(patch) 932 } 933 934 func (s *LabelNodePrepareStrategy) CleanupNode(ctx context.Context, node *v1.Node) *v1.Node { 935 nodeCopy := node.DeepCopy() 936 if node.Labels != nil && len(node.Labels[s.LabelKey]) != 0 { 937 delete(nodeCopy.Labels, s.LabelKey) 938 } 939 return nodeCopy 940 } 941 942 func (*LabelNodePrepareStrategy) PrepareDependentObjects(ctx context.Context, node *v1.Node, client clientset.Interface) error { 943 return nil 944 } 945 946 func (*LabelNodePrepareStrategy) CleanupDependentObjects(ctx context.Context, nodeName string, client clientset.Interface) error { 947 return nil 948 } 949 950 // NodeAllocatableStrategy fills node.status.allocatable and csiNode.spec.drivers[*].allocatable. 951 // csiNode is created if it does not exist. On cleanup, any csiNode.spec.drivers[*].allocatable is 952 // set to nil. 953 type NodeAllocatableStrategy struct { 954 // Node.status.allocatable to fill to all nodes. 955 NodeAllocatable map[v1.ResourceName]string 956 // Map <driver_name> -> VolumeNodeResources to fill into csiNode.spec.drivers[<driver_name>]. 957 CsiNodeAllocatable map[string]*storagev1.VolumeNodeResources 958 // List of in-tree volume plugins migrated to CSI. 959 MigratedPlugins []string 960 } 961 962 var _ PrepareNodeStrategy = &NodeAllocatableStrategy{} 963 964 func NewNodeAllocatableStrategy(nodeAllocatable map[v1.ResourceName]string, csiNodeAllocatable map[string]*storagev1.VolumeNodeResources, migratedPlugins []string) *NodeAllocatableStrategy { 965 return &NodeAllocatableStrategy{ 966 NodeAllocatable: nodeAllocatable, 967 CsiNodeAllocatable: csiNodeAllocatable, 968 MigratedPlugins: migratedPlugins, 969 } 970 } 971 972 func (s *NodeAllocatableStrategy) PreparePatch(node *v1.Node) []byte { 973 newNode := node.DeepCopy() 974 for name, value := range s.NodeAllocatable { 975 newNode.Status.Allocatable[name] = resource.MustParse(value) 976 } 977 978 oldJSON, err := json.Marshal(node) 979 if err != nil { 980 panic(err) 981 } 982 newJSON, err := json.Marshal(newNode) 983 if err != nil { 984 panic(err) 985 } 986 987 patch, err := strategicpatch.CreateTwoWayMergePatch(oldJSON, newJSON, v1.Node{}) 988 if err != nil { 989 panic(err) 990 } 991 return patch 992 } 993 994 func (s *NodeAllocatableStrategy) CleanupNode(ctx context.Context, node *v1.Node) *v1.Node { 995 nodeCopy := node.DeepCopy() 996 for name := range s.NodeAllocatable { 997 delete(nodeCopy.Status.Allocatable, name) 998 } 999 return nodeCopy 1000 } 1001 1002 func (s *NodeAllocatableStrategy) createCSINode(ctx context.Context, nodeName string, client clientset.Interface) error { 1003 csiNode := &storagev1.CSINode{ 1004 ObjectMeta: metav1.ObjectMeta{ 1005 Name: nodeName, 1006 Annotations: map[string]string{ 1007 v1.MigratedPluginsAnnotationKey: strings.Join(s.MigratedPlugins, ","), 1008 }, 1009 }, 1010 Spec: storagev1.CSINodeSpec{ 1011 Drivers: []storagev1.CSINodeDriver{}, 1012 }, 1013 } 1014 1015 for driver, allocatable := range s.CsiNodeAllocatable { 1016 d := storagev1.CSINodeDriver{ 1017 Name: driver, 1018 Allocatable: allocatable, 1019 NodeID: nodeName, 1020 } 1021 csiNode.Spec.Drivers = append(csiNode.Spec.Drivers, d) 1022 } 1023 1024 _, err := client.StorageV1().CSINodes().Create(ctx, csiNode, metav1.CreateOptions{}) 1025 if apierrors.IsAlreadyExists(err) { 1026 // Something created CSINode instance after we checked it did not exist. 1027 // Make the caller to re-try PrepareDependentObjects by returning Conflict error 1028 err = apierrors.NewConflict(storagev1beta1.Resource("csinodes"), nodeName, err) 1029 } 1030 return err 1031 } 1032 1033 func (s *NodeAllocatableStrategy) updateCSINode(ctx context.Context, csiNode *storagev1.CSINode, client clientset.Interface) error { 1034 for driverName, allocatable := range s.CsiNodeAllocatable { 1035 found := false 1036 for i, driver := range csiNode.Spec.Drivers { 1037 if driver.Name == driverName { 1038 found = true 1039 csiNode.Spec.Drivers[i].Allocatable = allocatable 1040 break 1041 } 1042 } 1043 if !found { 1044 d := storagev1.CSINodeDriver{ 1045 Name: driverName, 1046 Allocatable: allocatable, 1047 } 1048 1049 csiNode.Spec.Drivers = append(csiNode.Spec.Drivers, d) 1050 } 1051 } 1052 csiNode.Annotations[v1.MigratedPluginsAnnotationKey] = strings.Join(s.MigratedPlugins, ",") 1053 1054 _, err := client.StorageV1().CSINodes().Update(ctx, csiNode, metav1.UpdateOptions{}) 1055 return err 1056 } 1057 1058 func (s *NodeAllocatableStrategy) PrepareDependentObjects(ctx context.Context, node *v1.Node, client clientset.Interface) error { 1059 csiNode, err := client.StorageV1().CSINodes().Get(ctx, node.Name, metav1.GetOptions{}) 1060 if err != nil { 1061 if apierrors.IsNotFound(err) { 1062 return s.createCSINode(ctx, node.Name, client) 1063 } 1064 return err 1065 } 1066 return s.updateCSINode(ctx, csiNode, client) 1067 } 1068 1069 func (s *NodeAllocatableStrategy) CleanupDependentObjects(ctx context.Context, nodeName string, client clientset.Interface) error { 1070 csiNode, err := client.StorageV1().CSINodes().Get(ctx, nodeName, metav1.GetOptions{}) 1071 if err != nil { 1072 if apierrors.IsNotFound(err) { 1073 return nil 1074 } 1075 return err 1076 } 1077 1078 for driverName := range s.CsiNodeAllocatable { 1079 for i, driver := range csiNode.Spec.Drivers { 1080 if driver.Name == driverName { 1081 csiNode.Spec.Drivers[i].Allocatable = nil 1082 } 1083 } 1084 } 1085 return s.updateCSINode(ctx, csiNode, client) 1086 } 1087 1088 // UniqueNodeLabelStrategy sets a unique label for each node. 1089 type UniqueNodeLabelStrategy struct { 1090 LabelKey string 1091 } 1092 1093 var _ PrepareNodeStrategy = &UniqueNodeLabelStrategy{} 1094 1095 func NewUniqueNodeLabelStrategy(labelKey string) *UniqueNodeLabelStrategy { 1096 return &UniqueNodeLabelStrategy{ 1097 LabelKey: labelKey, 1098 } 1099 } 1100 1101 func (s *UniqueNodeLabelStrategy) PreparePatch(*v1.Node) []byte { 1102 labelString := fmt.Sprintf("{\"%v\":\"%v\"}", s.LabelKey, string(uuid.NewUUID())) 1103 patch := fmt.Sprintf(`{"metadata":{"labels":%v}}`, labelString) 1104 return []byte(patch) 1105 } 1106 1107 func (s *UniqueNodeLabelStrategy) CleanupNode(ctx context.Context, node *v1.Node) *v1.Node { 1108 nodeCopy := node.DeepCopy() 1109 if node.Labels != nil && len(node.Labels[s.LabelKey]) != 0 { 1110 delete(nodeCopy.Labels, s.LabelKey) 1111 } 1112 return nodeCopy 1113 } 1114 1115 func (*UniqueNodeLabelStrategy) PrepareDependentObjects(ctx context.Context, node *v1.Node, client clientset.Interface) error { 1116 return nil 1117 } 1118 1119 func (*UniqueNodeLabelStrategy) CleanupDependentObjects(ctx context.Context, nodeName string, client clientset.Interface) error { 1120 return nil 1121 } 1122 1123 func DoPrepareNode(ctx context.Context, client clientset.Interface, node *v1.Node, strategy PrepareNodeStrategy) error { 1124 var err error 1125 patch := strategy.PreparePatch(node) 1126 if len(patch) == 0 { 1127 return nil 1128 } 1129 for attempt := 0; attempt < retries; attempt++ { 1130 if _, err = client.CoreV1().Nodes().Patch(ctx, node.Name, types.MergePatchType, []byte(patch), metav1.PatchOptions{}); err == nil { 1131 break 1132 } 1133 if !apierrors.IsConflict(err) { 1134 return fmt.Errorf("error while applying patch %v to Node %v: %v", string(patch), node.Name, err) 1135 } 1136 time.Sleep(100 * time.Millisecond) 1137 } 1138 if err != nil { 1139 return fmt.Errorf("too many conflicts when applying patch %v to Node %v: %s", string(patch), node.Name, err) 1140 } 1141 1142 for attempt := 0; attempt < retries; attempt++ { 1143 if err = strategy.PrepareDependentObjects(ctx, node, client); err == nil { 1144 break 1145 } 1146 if !apierrors.IsConflict(err) { 1147 return fmt.Errorf("error while preparing objects for node %s: %s", node.Name, err) 1148 } 1149 time.Sleep(100 * time.Millisecond) 1150 } 1151 if err != nil { 1152 return fmt.Errorf("too many conflicts when creating objects for node %s: %s", node.Name, err) 1153 } 1154 return nil 1155 } 1156 1157 type TestPodCreateStrategy func(ctx context.Context, client clientset.Interface, namespace string, podCount int) error 1158 1159 type CountToPodStrategy struct { 1160 Count int 1161 Strategy TestPodCreateStrategy 1162 } 1163 1164 type TestPodCreatorConfig map[string][]CountToPodStrategy 1165 1166 func NewTestPodCreatorConfig() *TestPodCreatorConfig { 1167 config := make(TestPodCreatorConfig) 1168 return &config 1169 } 1170 1171 type CountToStrategy struct { 1172 Count int 1173 Strategy PrepareNodeStrategy 1174 } 1175 1176 type TestNodePreparer interface { 1177 PrepareNodes(ctx context.Context, nextNodeIndex int) error 1178 CleanupNodes(ctx context.Context) error 1179 } 1180 1181 func (c *TestPodCreatorConfig) AddStrategy( 1182 namespace string, podCount int, strategy TestPodCreateStrategy) { 1183 (*c)[namespace] = append((*c)[namespace], CountToPodStrategy{Count: podCount, Strategy: strategy}) 1184 } 1185 1186 type TestPodCreator struct { 1187 Client clientset.Interface 1188 // namespace -> count -> strategy 1189 Config *TestPodCreatorConfig 1190 } 1191 1192 func NewTestPodCreator(client clientset.Interface, config *TestPodCreatorConfig) *TestPodCreator { 1193 return &TestPodCreator{ 1194 Client: client, 1195 Config: config, 1196 } 1197 } 1198 1199 func (c *TestPodCreator) CreatePods(ctx context.Context) error { 1200 for ns, v := range *(c.Config) { 1201 for _, countToStrategy := range v { 1202 if err := countToStrategy.Strategy(ctx, c.Client, ns, countToStrategy.Count); err != nil { 1203 return err 1204 } 1205 } 1206 } 1207 return nil 1208 } 1209 1210 func MakePodSpec() v1.PodSpec { 1211 return v1.PodSpec{ 1212 Containers: []v1.Container{{ 1213 Name: "pause", 1214 Image: "registry.k8s.io/pause:3.9", 1215 Ports: []v1.ContainerPort{{ContainerPort: 80}}, 1216 Resources: v1.ResourceRequirements{ 1217 Limits: v1.ResourceList{ 1218 v1.ResourceCPU: resource.MustParse("100m"), 1219 v1.ResourceMemory: resource.MustParse("500Mi"), 1220 }, 1221 Requests: v1.ResourceList{ 1222 v1.ResourceCPU: resource.MustParse("100m"), 1223 v1.ResourceMemory: resource.MustParse("500Mi"), 1224 }, 1225 }, 1226 }}, 1227 } 1228 } 1229 1230 func makeCreatePod(client clientset.Interface, namespace string, podTemplate *v1.Pod) error { 1231 if err := CreatePodWithRetries(client, namespace, podTemplate); err != nil { 1232 return fmt.Errorf("error creating pod: %v", err) 1233 } 1234 return nil 1235 } 1236 1237 func CreatePod(ctx context.Context, client clientset.Interface, namespace string, podCount int, podTemplate *v1.Pod) error { 1238 var createError error 1239 lock := sync.Mutex{} 1240 createPodFunc := func(i int) { 1241 // client-go writes into the object that is passed to Create, 1242 // causing a data race unless we create a new copy for each 1243 // parallel call. 1244 if err := makeCreatePod(client, namespace, podTemplate.DeepCopy()); err != nil { 1245 lock.Lock() 1246 defer lock.Unlock() 1247 createError = err 1248 } 1249 } 1250 1251 if podCount < 30 { 1252 workqueue.ParallelizeUntil(ctx, podCount, podCount, createPodFunc) 1253 } else { 1254 workqueue.ParallelizeUntil(ctx, 30, podCount, createPodFunc) 1255 } 1256 return createError 1257 } 1258 1259 func CreatePodWithPersistentVolume(ctx context.Context, client clientset.Interface, namespace string, claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod, count int, bindVolume bool) error { 1260 var createError error 1261 lock := sync.Mutex{} 1262 createPodFunc := func(i int) { 1263 pvcName := fmt.Sprintf("pvc-%d", i) 1264 // pvc 1265 pvc := claimTemplate.DeepCopy() 1266 pvc.Name = pvcName 1267 // pv 1268 pv := factory(i) 1269 // PVs are cluster-wide resources. 1270 // Prepend a namespace to make the name globally unique. 1271 pv.Name = fmt.Sprintf("%s-%s", namespace, pv.Name) 1272 if bindVolume { 1273 // bind pv to "pvc-$i" 1274 pv.Spec.ClaimRef = &v1.ObjectReference{ 1275 Kind: "PersistentVolumeClaim", 1276 Namespace: namespace, 1277 Name: pvcName, 1278 APIVersion: "v1", 1279 } 1280 pv.Status.Phase = v1.VolumeBound 1281 1282 // bind pvc to "pv-$i" 1283 pvc.Spec.VolumeName = pv.Name 1284 pvc.Status.Phase = v1.ClaimBound 1285 } else { 1286 pv.Status.Phase = v1.VolumeAvailable 1287 } 1288 1289 // Create PVC first as it's referenced by the PV when the `bindVolume` is true. 1290 if err := CreatePersistentVolumeClaimWithRetries(client, namespace, pvc); err != nil { 1291 lock.Lock() 1292 defer lock.Unlock() 1293 createError = fmt.Errorf("error creating PVC: %s", err) 1294 return 1295 } 1296 1297 // We need to update statuses separately, as creating pv/pvc resets status to the default one. 1298 if _, err := client.CoreV1().PersistentVolumeClaims(namespace).UpdateStatus(ctx, pvc, metav1.UpdateOptions{}); err != nil { 1299 lock.Lock() 1300 defer lock.Unlock() 1301 createError = fmt.Errorf("error updating PVC status: %s", err) 1302 return 1303 } 1304 1305 if err := CreatePersistentVolumeWithRetries(client, pv); err != nil { 1306 lock.Lock() 1307 defer lock.Unlock() 1308 createError = fmt.Errorf("error creating PV: %s", err) 1309 return 1310 } 1311 // We need to update statuses separately, as creating pv/pvc resets status to the default one. 1312 if _, err := client.CoreV1().PersistentVolumes().UpdateStatus(ctx, pv, metav1.UpdateOptions{}); err != nil { 1313 lock.Lock() 1314 defer lock.Unlock() 1315 createError = fmt.Errorf("error updating PV status: %s", err) 1316 return 1317 } 1318 1319 // pod 1320 pod := podTemplate.DeepCopy() 1321 pod.Spec.Volumes = []v1.Volume{ 1322 { 1323 Name: "vol", 1324 VolumeSource: v1.VolumeSource{ 1325 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 1326 ClaimName: pvcName, 1327 }, 1328 }, 1329 }, 1330 } 1331 if err := makeCreatePod(client, namespace, pod); err != nil { 1332 lock.Lock() 1333 defer lock.Unlock() 1334 createError = err 1335 return 1336 } 1337 } 1338 1339 if count < 30 { 1340 workqueue.ParallelizeUntil(ctx, count, count, createPodFunc) 1341 } else { 1342 workqueue.ParallelizeUntil(ctx, 30, count, createPodFunc) 1343 } 1344 return createError 1345 } 1346 1347 func NewCustomCreatePodStrategy(podTemplate *v1.Pod) TestPodCreateStrategy { 1348 return func(ctx context.Context, client clientset.Interface, namespace string, podCount int) error { 1349 return CreatePod(ctx, client, namespace, podCount, podTemplate) 1350 } 1351 } 1352 1353 // volumeFactory creates an unique PersistentVolume for given integer. 1354 type volumeFactory func(uniqueID int) *v1.PersistentVolume 1355 1356 func NewCreatePodWithPersistentVolumeStrategy(claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod) TestPodCreateStrategy { 1357 return func(ctx context.Context, client clientset.Interface, namespace string, podCount int) error { 1358 return CreatePodWithPersistentVolume(ctx, client, namespace, claimTemplate, factory, podTemplate, podCount, true /* bindVolume */) 1359 } 1360 } 1361 1362 // TODO: attach secrets using different possibilities: env vars, image pull secrets. 1363 func attachSecrets(template *v1.PodTemplateSpec, secretNames []string) { 1364 volumes := make([]v1.Volume, 0, len(secretNames)) 1365 mounts := make([]v1.VolumeMount, 0, len(secretNames)) 1366 for _, name := range secretNames { 1367 volumes = append(volumes, v1.Volume{ 1368 Name: name, 1369 VolumeSource: v1.VolumeSource{ 1370 Secret: &v1.SecretVolumeSource{ 1371 SecretName: name, 1372 }, 1373 }, 1374 }) 1375 mounts = append(mounts, v1.VolumeMount{ 1376 Name: name, 1377 MountPath: fmt.Sprintf("/%v", name), 1378 }) 1379 } 1380 1381 template.Spec.Volumes = volumes 1382 template.Spec.Containers[0].VolumeMounts = mounts 1383 } 1384 1385 // TODO: attach configmaps using different possibilities: env vars. 1386 func attachConfigMaps(template *v1.PodTemplateSpec, configMapNames []string) { 1387 volumes := make([]v1.Volume, 0, len(configMapNames)) 1388 mounts := make([]v1.VolumeMount, 0, len(configMapNames)) 1389 for _, name := range configMapNames { 1390 volumes = append(volumes, v1.Volume{ 1391 Name: name, 1392 VolumeSource: v1.VolumeSource{ 1393 ConfigMap: &v1.ConfigMapVolumeSource{ 1394 LocalObjectReference: v1.LocalObjectReference{ 1395 Name: name, 1396 }, 1397 }, 1398 }, 1399 }) 1400 mounts = append(mounts, v1.VolumeMount{ 1401 Name: name, 1402 MountPath: fmt.Sprintf("/%v", name), 1403 }) 1404 } 1405 1406 template.Spec.Volumes = volumes 1407 template.Spec.Containers[0].VolumeMounts = mounts 1408 } 1409 1410 func (config *RCConfig) getTerminationGracePeriodSeconds(defaultGrace *int64) *int64 { 1411 if config.TerminationGracePeriodSeconds == nil || *config.TerminationGracePeriodSeconds < 0 { 1412 return defaultGrace 1413 } 1414 return config.TerminationGracePeriodSeconds 1415 } 1416 1417 func attachServiceAccountTokenProjection(template *v1.PodTemplateSpec, name string) { 1418 template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts, 1419 v1.VolumeMount{ 1420 Name: name, 1421 MountPath: "/var/service-account-tokens/" + name, 1422 }) 1423 1424 template.Spec.Volumes = append(template.Spec.Volumes, 1425 v1.Volume{ 1426 Name: name, 1427 VolumeSource: v1.VolumeSource{ 1428 Projected: &v1.ProjectedVolumeSource{ 1429 Sources: []v1.VolumeProjection{ 1430 { 1431 ServiceAccountToken: &v1.ServiceAccountTokenProjection{ 1432 Path: "token", 1433 Audience: name, 1434 }, 1435 }, 1436 { 1437 ConfigMap: &v1.ConfigMapProjection{ 1438 LocalObjectReference: v1.LocalObjectReference{ 1439 Name: "kube-root-ca-crt", 1440 }, 1441 Items: []v1.KeyToPath{ 1442 { 1443 Key: "ca.crt", 1444 Path: "ca.crt", 1445 }, 1446 }, 1447 }, 1448 }, 1449 { 1450 DownwardAPI: &v1.DownwardAPIProjection{ 1451 Items: []v1.DownwardAPIVolumeFile{ 1452 { 1453 Path: "namespace", 1454 FieldRef: &v1.ObjectFieldSelector{ 1455 APIVersion: "v1", 1456 FieldPath: "metadata.namespace", 1457 }, 1458 }, 1459 }, 1460 }, 1461 }, 1462 }, 1463 }, 1464 }, 1465 }) 1466 } 1467 1468 type DaemonConfig struct { 1469 Client clientset.Interface 1470 Name string 1471 Namespace string 1472 Image string 1473 // If set this function will be used to print log lines instead of klog. 1474 LogFunc func(fmt string, args ...interface{}) 1475 // How long we wait for DaemonSet to become running. 1476 Timeout time.Duration 1477 } 1478 1479 func (config *DaemonConfig) Run(ctx context.Context) error { 1480 if config.Image == "" { 1481 config.Image = "registry.k8s.io/pause:3.9" 1482 } 1483 nameLabel := map[string]string{ 1484 "name": config.Name + "-daemon", 1485 } 1486 daemon := &apps.DaemonSet{ 1487 ObjectMeta: metav1.ObjectMeta{ 1488 Name: config.Name, 1489 }, 1490 Spec: apps.DaemonSetSpec{ 1491 Template: v1.PodTemplateSpec{ 1492 ObjectMeta: metav1.ObjectMeta{ 1493 Labels: nameLabel, 1494 }, 1495 Spec: v1.PodSpec{ 1496 Containers: []v1.Container{ 1497 { 1498 Name: config.Name, 1499 Image: config.Image, 1500 }, 1501 }, 1502 }, 1503 }, 1504 }, 1505 } 1506 1507 if err := CreateDaemonSetWithRetries(config.Client, config.Namespace, daemon); err != nil { 1508 return fmt.Errorf("error creating daemonset: %v", err) 1509 } 1510 1511 var nodes *v1.NodeList 1512 var err error 1513 for i := 0; i < retries; i++ { 1514 // Wait for all daemons to be running 1515 nodes, err = config.Client.CoreV1().Nodes().List(ctx, metav1.ListOptions{ResourceVersion: "0"}) 1516 if err == nil { 1517 break 1518 } else if i+1 == retries { 1519 return fmt.Errorf("error listing Nodes while waiting for DaemonSet %v: %v", config.Name, err) 1520 } 1521 } 1522 1523 timeout := config.Timeout 1524 if timeout <= 0 { 1525 timeout = 5 * time.Minute 1526 } 1527 1528 ps, err := NewPodStore(config.Client, config.Namespace, labels.SelectorFromSet(nameLabel), fields.Everything()) 1529 if err != nil { 1530 return err 1531 } 1532 defer ps.Stop() 1533 1534 err = wait.Poll(time.Second, timeout, func() (bool, error) { 1535 pods := ps.List() 1536 1537 nodeHasDaemon := sets.NewString() 1538 for _, pod := range pods { 1539 podReady, _ := PodRunningReady(pod) 1540 if pod.Spec.NodeName != "" && podReady { 1541 nodeHasDaemon.Insert(pod.Spec.NodeName) 1542 } 1543 } 1544 1545 running := len(nodeHasDaemon) 1546 config.LogFunc("Found %v/%v Daemons %v running", running, config.Name, len(nodes.Items)) 1547 return running == len(nodes.Items), nil 1548 }) 1549 if err != nil { 1550 config.LogFunc("Timed out while waiting for DaemonSet %v/%v to be running.", config.Namespace, config.Name) 1551 } else { 1552 config.LogFunc("Created Daemon %v/%v", config.Namespace, config.Name) 1553 } 1554 1555 return err 1556 }