github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/reconciler/reconciler_test.go (about) 1 package reconciler 2 3 import ( 4 "testing" 5 6 "k8s.io/apimachinery/pkg/runtime" 7 8 "github.com/google/go-cmp/cmp" 9 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/image" 10 "github.com/stretchr/testify/require" 11 corev1 "k8s.io/api/core/v1" 12 "k8s.io/apimachinery/pkg/api/resource" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/utils/ptr" 15 16 "github.com/operator-framework/api/pkg/operators/v1alpha1" 17 ) 18 19 const workloadUserID = 1001 20 const defaultPodSecurityConfig = v1alpha1.Restricted 21 22 func TestPodMemoryTarget(t *testing.T) { 23 q := resource.MustParse("5Mi") 24 var testCases = []struct { 25 name string 26 input *v1alpha1.CatalogSource 27 expected *corev1.Pod 28 }{ 29 { 30 name: "no memory target set", 31 input: &v1alpha1.CatalogSource{ 32 ObjectMeta: metav1.ObjectMeta{ 33 Name: "test", 34 Namespace: "testns", 35 }, 36 }, 37 expected: &corev1.Pod{ 38 ObjectMeta: metav1.ObjectMeta{ 39 GenerateName: "test-", 40 Namespace: "testns", 41 Labels: map[string]string{"olm.pod-spec-hash": "8SbHWyYfjbRT8lLcfdZ5ofXNdC1GE6ayztILTF", "olm.managed": "true"}, 42 Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, 43 }, 44 Spec: corev1.PodSpec{ 45 Containers: []corev1.Container{ 46 { 47 Name: "name", 48 Image: "image", 49 Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, 50 ReadinessProbe: &corev1.Probe{ 51 ProbeHandler: corev1.ProbeHandler{ 52 Exec: &corev1.ExecAction{ 53 Command: []string{"grpc_health_probe", "-addr=:50051"}, 54 }, 55 }, 56 InitialDelaySeconds: 0, 57 TimeoutSeconds: 5, 58 }, 59 LivenessProbe: &corev1.Probe{ 60 ProbeHandler: corev1.ProbeHandler{ 61 Exec: &corev1.ExecAction{ 62 Command: []string{"grpc_health_probe", "-addr=:50051"}, 63 }, 64 }, 65 InitialDelaySeconds: 0, 66 TimeoutSeconds: 5, 67 }, 68 StartupProbe: &corev1.Probe{ 69 ProbeHandler: corev1.ProbeHandler{ 70 Exec: &corev1.ExecAction{ 71 Command: []string{"grpc_health_probe", "-addr=:50051"}, 72 }, 73 }, 74 FailureThreshold: 10, 75 PeriodSeconds: 10, 76 TimeoutSeconds: 5, 77 }, 78 Resources: corev1.ResourceRequirements{ 79 Requests: corev1.ResourceList{ 80 corev1.ResourceCPU: resource.MustParse("10m"), 81 corev1.ResourceMemory: resource.MustParse("50Mi"), 82 }, 83 }, 84 SecurityContext: &corev1.SecurityContext{ 85 ReadOnlyRootFilesystem: ptr.To(false), 86 }, 87 ImagePullPolicy: image.InferImagePullPolicy("image"), 88 TerminationMessagePolicy: "FallbackToLogsOnError", 89 }, 90 }, 91 NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, 92 ServiceAccountName: "service-account", 93 }, 94 }, 95 }, 96 { 97 name: "memory target set", 98 input: &v1alpha1.CatalogSource{ 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: "test", 101 Namespace: "testns", 102 }, 103 Spec: v1alpha1.CatalogSourceSpec{ 104 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 105 MemoryTarget: &q, 106 }, 107 }, 108 }, 109 expected: &corev1.Pod{ 110 ObjectMeta: metav1.ObjectMeta{ 111 GenerateName: "test-", 112 Namespace: "testns", 113 Labels: map[string]string{"olm.pod-spec-hash": "3DSBhZZIiOl5YIjTsZy9aRyFIXeDR8mZCGAcYA", "olm.managed": "true"}, 114 Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, 115 }, 116 Spec: corev1.PodSpec{ 117 Containers: []corev1.Container{ 118 { 119 Name: "name", 120 Image: "image", 121 Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, 122 Env: []corev1.EnvVar{{Name: "GOMEMLIMIT", Value: "5MiB"}}, 123 ReadinessProbe: &corev1.Probe{ 124 ProbeHandler: corev1.ProbeHandler{ 125 Exec: &corev1.ExecAction{ 126 Command: []string{"grpc_health_probe", "-addr=:50051"}, 127 }, 128 }, 129 InitialDelaySeconds: 0, 130 TimeoutSeconds: 5, 131 }, 132 LivenessProbe: &corev1.Probe{ 133 ProbeHandler: corev1.ProbeHandler{ 134 Exec: &corev1.ExecAction{ 135 Command: []string{"grpc_health_probe", "-addr=:50051"}, 136 }, 137 }, 138 InitialDelaySeconds: 0, 139 TimeoutSeconds: 5, 140 }, 141 StartupProbe: &corev1.Probe{ 142 ProbeHandler: corev1.ProbeHandler{ 143 Exec: &corev1.ExecAction{ 144 Command: []string{"grpc_health_probe", "-addr=:50051"}, 145 }, 146 }, 147 FailureThreshold: 10, 148 PeriodSeconds: 10, 149 TimeoutSeconds: 5, 150 }, 151 Resources: corev1.ResourceRequirements{ 152 Requests: corev1.ResourceList{ 153 corev1.ResourceCPU: resource.MustParse("10m"), 154 corev1.ResourceMemory: resource.MustParse("5Mi"), 155 }, 156 Limits: corev1.ResourceList{}, 157 }, 158 SecurityContext: &corev1.SecurityContext{ 159 ReadOnlyRootFilesystem: ptr.To(false), 160 }, 161 ImagePullPolicy: image.InferImagePullPolicy("image"), 162 TerminationMessagePolicy: "FallbackToLogsOnError", 163 }, 164 }, 165 NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, 166 ServiceAccountName: "service-account", 167 }, 168 }, 169 }, 170 } 171 172 for _, testCase := range testCases { 173 t.Run(testCase.name, func(t *testing.T) { 174 pod, err := Pod(testCase.input, "name", "opmImage", "utilImage", "image", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) 175 require.NoError(t, err) 176 if diff := cmp.Diff(pod, testCase.expected); diff != "" { 177 t.Errorf("got incorrect pod: %v", diff) 178 } 179 }) 180 } 181 } 182 183 func serviceAccount(namespace, name string) *corev1.ServiceAccount { 184 return &corev1.ServiceAccount{ 185 ObjectMeta: metav1.ObjectMeta{ 186 Namespace: namespace, 187 Name: name, 188 }, 189 } 190 } 191 192 func TestPodExtractContent(t *testing.T) { 193 var testCases = []struct { 194 name string 195 input *v1alpha1.CatalogSource 196 securityContextConfig v1alpha1.SecurityConfig 197 expected *corev1.Pod 198 }{ 199 { 200 name: "content extraction not requested - legacy security context config", 201 input: &v1alpha1.CatalogSource{ 202 ObjectMeta: metav1.ObjectMeta{ 203 Name: "test", 204 Namespace: "testns", 205 }, 206 }, 207 securityContextConfig: v1alpha1.Legacy, 208 expected: &corev1.Pod{ 209 ObjectMeta: metav1.ObjectMeta{ 210 GenerateName: "test-", 211 Namespace: "testns", 212 Labels: map[string]string{"olm.pod-spec-hash": "8SbHWyYfjbRT8lLcfdZ5ofXNdC1GE6ayztILTF", "olm.managed": "true"}, 213 Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, 214 }, 215 Spec: corev1.PodSpec{ 216 Containers: []corev1.Container{ 217 { 218 Name: "name", 219 Image: "image", 220 Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, 221 ReadinessProbe: &corev1.Probe{ 222 ProbeHandler: corev1.ProbeHandler{ 223 Exec: &corev1.ExecAction{ 224 Command: []string{"grpc_health_probe", "-addr=:50051"}, 225 }, 226 }, 227 InitialDelaySeconds: 0, 228 TimeoutSeconds: 5, 229 }, 230 LivenessProbe: &corev1.Probe{ 231 ProbeHandler: corev1.ProbeHandler{ 232 Exec: &corev1.ExecAction{ 233 Command: []string{"grpc_health_probe", "-addr=:50051"}, 234 }, 235 }, 236 InitialDelaySeconds: 0, 237 TimeoutSeconds: 5, 238 }, 239 StartupProbe: &corev1.Probe{ 240 ProbeHandler: corev1.ProbeHandler{ 241 Exec: &corev1.ExecAction{ 242 Command: []string{"grpc_health_probe", "-addr=:50051"}, 243 }, 244 }, 245 FailureThreshold: 10, 246 PeriodSeconds: 10, 247 TimeoutSeconds: 5, 248 }, 249 Resources: corev1.ResourceRequirements{ 250 Requests: corev1.ResourceList{ 251 corev1.ResourceCPU: resource.MustParse("10m"), 252 corev1.ResourceMemory: resource.MustParse("50Mi"), 253 }, 254 }, 255 SecurityContext: &corev1.SecurityContext{ 256 ReadOnlyRootFilesystem: ptr.To(false), 257 }, 258 ImagePullPolicy: image.InferImagePullPolicy("image"), 259 TerminationMessagePolicy: "FallbackToLogsOnError", 260 }, 261 }, 262 NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, 263 ServiceAccountName: "service-account", 264 }, 265 }, 266 }, 267 { 268 name: "content extraction expected - legacy security context config", 269 input: &v1alpha1.CatalogSource{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Name: "test", 272 Namespace: "testns", 273 }, 274 Spec: v1alpha1.CatalogSourceSpec{ 275 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 276 ExtractContent: &v1alpha1.ExtractContentConfig{ 277 CacheDir: "/tmp/cache", 278 CatalogDir: "/catalog", 279 }, 280 }, 281 }, 282 }, 283 securityContextConfig: v1alpha1.Legacy, 284 expected: &corev1.Pod{ 285 ObjectMeta: metav1.ObjectMeta{ 286 GenerateName: "test-", 287 Namespace: "testns", 288 Labels: map[string]string{"olm.pod-spec-hash": "5MSUJs07MqD3fl9supmPaRNxD9N6tK8Bjo4OFl", "olm.managed": "true"}, 289 Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, 290 }, 291 Spec: corev1.PodSpec{ 292 Volumes: []corev1.Volume{ 293 { 294 Name: "utilities", 295 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, 296 }, 297 { 298 Name: "catalog-content", 299 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, 300 }, 301 }, 302 InitContainers: []corev1.Container{ 303 { 304 Name: "extract-utilities", 305 Image: "utilImage", 306 Command: []string{"cp"}, 307 Args: []string{"/bin/copy-content", "/utilities/copy-content"}, 308 VolumeMounts: []corev1.VolumeMount{{Name: "utilities", MountPath: "/utilities"}}, 309 TerminationMessagePolicy: "FallbackToLogsOnError", 310 }, 311 { 312 Name: "extract-content", 313 Image: "image", 314 ImagePullPolicy: image.InferImagePullPolicy("image"), 315 Command: []string{"/utilities/copy-content"}, 316 Args: []string{ 317 "--catalog.from=/catalog", 318 "--catalog.to=/extracted-catalog/catalog", 319 "--cache.from=/tmp/cache", 320 "--cache.to=/extracted-catalog/cache", 321 }, 322 VolumeMounts: []corev1.VolumeMount{ 323 {Name: "utilities", MountPath: "/utilities"}, 324 {Name: "catalog-content", MountPath: "/extracted-catalog"}, 325 }, 326 TerminationMessagePolicy: "FallbackToLogsOnError", 327 }, 328 }, 329 Containers: []corev1.Container{ 330 { 331 Name: "name", 332 Image: "opmImage", 333 Command: []string{"/bin/opm"}, 334 Args: []string{"serve", "/extracted-catalog/catalog", "--cache-dir=/extracted-catalog/cache"}, 335 Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, 336 ReadinessProbe: &corev1.Probe{ 337 ProbeHandler: corev1.ProbeHandler{ 338 Exec: &corev1.ExecAction{ 339 Command: []string{"grpc_health_probe", "-addr=:50051"}, 340 }, 341 }, 342 InitialDelaySeconds: 0, 343 TimeoutSeconds: 5, 344 }, 345 LivenessProbe: &corev1.Probe{ 346 ProbeHandler: corev1.ProbeHandler{ 347 Exec: &corev1.ExecAction{ 348 Command: []string{"grpc_health_probe", "-addr=:50051"}, 349 }, 350 }, 351 InitialDelaySeconds: 0, 352 TimeoutSeconds: 5, 353 }, 354 StartupProbe: &corev1.Probe{ 355 ProbeHandler: corev1.ProbeHandler{ 356 Exec: &corev1.ExecAction{ 357 Command: []string{"grpc_health_probe", "-addr=:50051"}, 358 }, 359 }, 360 FailureThreshold: 10, 361 PeriodSeconds: 10, 362 TimeoutSeconds: 5, 363 }, 364 Resources: corev1.ResourceRequirements{ 365 Requests: corev1.ResourceList{ 366 corev1.ResourceCPU: resource.MustParse("10m"), 367 corev1.ResourceMemory: resource.MustParse("50Mi"), 368 }, 369 }, 370 SecurityContext: &corev1.SecurityContext{ 371 ReadOnlyRootFilesystem: ptr.To(false), 372 }, 373 ImagePullPolicy: image.InferImagePullPolicy("image"), 374 TerminationMessagePolicy: "FallbackToLogsOnError", 375 VolumeMounts: []corev1.VolumeMount{{Name: "catalog-content", MountPath: "/extracted-catalog"}}, 376 }, 377 }, 378 NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, 379 ServiceAccountName: "service-account", 380 }, 381 }, 382 }, 383 { 384 name: "content extraction not requested - restricted security context config", 385 input: &v1alpha1.CatalogSource{ 386 ObjectMeta: metav1.ObjectMeta{ 387 Name: "test", 388 Namespace: "testns", 389 }, 390 }, 391 securityContextConfig: v1alpha1.Restricted, 392 expected: &corev1.Pod{ 393 ObjectMeta: metav1.ObjectMeta{ 394 GenerateName: "test-", 395 Namespace: "testns", 396 Labels: map[string]string{"olm.pod-spec-hash": "3sDLk8MMNptrqUfdnruY2gUi1g8O4wpMWC6Q52", "olm.managed": "true"}, 397 Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, 398 }, 399 Spec: corev1.PodSpec{ 400 Containers: []corev1.Container{ 401 { 402 Name: "name", 403 Image: "image", 404 Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, 405 ReadinessProbe: &corev1.Probe{ 406 ProbeHandler: corev1.ProbeHandler{ 407 Exec: &corev1.ExecAction{ 408 Command: []string{"grpc_health_probe", "-addr=:50051"}, 409 }, 410 }, 411 InitialDelaySeconds: 0, 412 TimeoutSeconds: 5, 413 }, 414 LivenessProbe: &corev1.Probe{ 415 ProbeHandler: corev1.ProbeHandler{ 416 Exec: &corev1.ExecAction{ 417 Command: []string{"grpc_health_probe", "-addr=:50051"}, 418 }, 419 }, 420 InitialDelaySeconds: 0, 421 TimeoutSeconds: 5, 422 }, 423 StartupProbe: &corev1.Probe{ 424 ProbeHandler: corev1.ProbeHandler{ 425 Exec: &corev1.ExecAction{ 426 Command: []string{"grpc_health_probe", "-addr=:50051"}, 427 }, 428 }, 429 FailureThreshold: 10, 430 PeriodSeconds: 10, 431 TimeoutSeconds: 5, 432 }, 433 Resources: corev1.ResourceRequirements{ 434 Requests: corev1.ResourceList{ 435 corev1.ResourceCPU: resource.MustParse("10m"), 436 corev1.ResourceMemory: resource.MustParse("50Mi"), 437 }, 438 }, 439 ImagePullPolicy: image.InferImagePullPolicy("image"), 440 SecurityContext: &corev1.SecurityContext{ 441 Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, 442 AllowPrivilegeEscalation: ptr.To(false), 443 ReadOnlyRootFilesystem: ptr.To(false), 444 }, 445 TerminationMessagePolicy: "FallbackToLogsOnError", 446 }, 447 }, 448 NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, 449 SecurityContext: &corev1.PodSecurityContext{ 450 RunAsUser: ptr.To(int64(workloadUserID)), 451 RunAsNonRoot: ptr.To(true), 452 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 453 }, 454 ServiceAccountName: "service-account", 455 }, 456 }, 457 }, 458 { 459 name: "content extraction expected - restricted security context config", 460 input: &v1alpha1.CatalogSource{ 461 ObjectMeta: metav1.ObjectMeta{ 462 Name: "test", 463 Namespace: "testns", 464 }, 465 Spec: v1alpha1.CatalogSourceSpec{ 466 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 467 ExtractContent: &v1alpha1.ExtractContentConfig{ 468 CacheDir: "/tmp/cache", 469 CatalogDir: "/catalog", 470 }, 471 }, 472 }, 473 }, 474 securityContextConfig: v1alpha1.Restricted, 475 expected: &corev1.Pod{ 476 ObjectMeta: metav1.ObjectMeta{ 477 GenerateName: "test-", 478 Namespace: "testns", 479 Labels: map[string]string{"olm.pod-spec-hash": "1X4YqbfXuc9SB9ztW03WNOyanr9aIhKfijeBHH", "olm.managed": "true"}, 480 Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, 481 }, 482 Spec: corev1.PodSpec{ 483 Volumes: []corev1.Volume{ 484 { 485 Name: "utilities", 486 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, 487 }, 488 { 489 Name: "catalog-content", 490 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, 491 }, 492 }, 493 InitContainers: []corev1.Container{ 494 { 495 Name: "extract-utilities", 496 Image: "utilImage", 497 Command: []string{"cp"}, 498 Args: []string{"/bin/copy-content", "/utilities/copy-content"}, 499 SecurityContext: &corev1.SecurityContext{ 500 Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, 501 AllowPrivilegeEscalation: ptr.To(false), 502 }, 503 VolumeMounts: []corev1.VolumeMount{{Name: "utilities", MountPath: "/utilities"}}, 504 TerminationMessagePolicy: "FallbackToLogsOnError", 505 }, 506 { 507 Name: "extract-content", 508 Image: "image", 509 ImagePullPolicy: image.InferImagePullPolicy("image"), 510 Command: []string{"/utilities/copy-content"}, 511 Args: []string{ 512 "--catalog.from=/catalog", 513 "--catalog.to=/extracted-catalog/catalog", 514 "--cache.from=/tmp/cache", 515 "--cache.to=/extracted-catalog/cache", 516 }, 517 SecurityContext: &corev1.SecurityContext{ 518 Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, 519 AllowPrivilegeEscalation: ptr.To(false), 520 }, 521 VolumeMounts: []corev1.VolumeMount{ 522 {Name: "utilities", MountPath: "/utilities"}, 523 {Name: "catalog-content", MountPath: "/extracted-catalog"}, 524 }, 525 TerminationMessagePolicy: "FallbackToLogsOnError", 526 }, 527 }, 528 Containers: []corev1.Container{ 529 { 530 Name: "name", 531 Image: "opmImage", 532 Command: []string{"/bin/opm"}, 533 Args: []string{"serve", "/extracted-catalog/catalog", "--cache-dir=/extracted-catalog/cache"}, 534 Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, 535 ReadinessProbe: &corev1.Probe{ 536 ProbeHandler: corev1.ProbeHandler{ 537 Exec: &corev1.ExecAction{ 538 Command: []string{"grpc_health_probe", "-addr=:50051"}, 539 }, 540 }, 541 InitialDelaySeconds: 0, 542 TimeoutSeconds: 5, 543 }, 544 LivenessProbe: &corev1.Probe{ 545 ProbeHandler: corev1.ProbeHandler{ 546 Exec: &corev1.ExecAction{ 547 Command: []string{"grpc_health_probe", "-addr=:50051"}, 548 }, 549 }, 550 InitialDelaySeconds: 0, 551 TimeoutSeconds: 5, 552 }, 553 StartupProbe: &corev1.Probe{ 554 ProbeHandler: corev1.ProbeHandler{ 555 Exec: &corev1.ExecAction{ 556 Command: []string{"grpc_health_probe", "-addr=:50051"}, 557 }, 558 }, 559 FailureThreshold: 10, 560 PeriodSeconds: 10, 561 TimeoutSeconds: 5, 562 }, 563 Resources: corev1.ResourceRequirements{ 564 Requests: corev1.ResourceList{ 565 corev1.ResourceCPU: resource.MustParse("10m"), 566 corev1.ResourceMemory: resource.MustParse("50Mi"), 567 }, 568 }, 569 ImagePullPolicy: image.InferImagePullPolicy("image"), 570 SecurityContext: &corev1.SecurityContext{ 571 Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, 572 AllowPrivilegeEscalation: ptr.To(false), 573 ReadOnlyRootFilesystem: ptr.To(false), 574 }, 575 TerminationMessagePolicy: "FallbackToLogsOnError", 576 VolumeMounts: []corev1.VolumeMount{{Name: "catalog-content", MountPath: "/extracted-catalog"}}, 577 }, 578 }, 579 NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, 580 SecurityContext: &corev1.PodSecurityContext{ 581 RunAsUser: ptr.To(int64(workloadUserID)), 582 RunAsNonRoot: ptr.To(true), 583 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 584 }, 585 ServiceAccountName: "service-account", 586 }, 587 }, 588 }, 589 } 590 591 for _, testCase := range testCases { 592 t.Run(testCase.name, func(t *testing.T) { 593 pod, err := Pod(testCase.input, "name", "opmImage", "utilImage", "image", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), testCase.securityContextConfig) 594 require.NoError(t, err) 595 if diff := cmp.Diff(testCase.expected, pod); diff != "" { 596 t.Errorf("got incorrect pod: %v", diff) 597 } 598 }) 599 } 600 } 601 602 func TestPodServiceAccountImagePullSecrets(t *testing.T) { 603 var testCases = []struct { 604 name string 605 catalogSource *v1alpha1.CatalogSource 606 serviceAccount *corev1.ServiceAccount 607 }{ 608 { 609 name: "ServiceAccount has no imagePullSecret", 610 serviceAccount: &corev1.ServiceAccount{ 611 ObjectMeta: metav1.ObjectMeta{ 612 Namespace: "", 613 Name: "service-account", 614 }, 615 }, 616 }, 617 { 618 name: "ServiceAccount has one imagePullSecret", 619 serviceAccount: &corev1.ServiceAccount{ 620 ObjectMeta: metav1.ObjectMeta{ 621 Namespace: "", 622 Name: "service-account", 623 }, 624 ImagePullSecrets: []corev1.LocalObjectReference{{Name: "foo"}}, 625 }, 626 }, 627 } 628 629 catalogSource := &v1alpha1.CatalogSource{ 630 ObjectMeta: metav1.ObjectMeta{ 631 Name: "test", 632 Namespace: "testns", 633 }, 634 Spec: v1alpha1.CatalogSourceSpec{ 635 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 636 ExtractContent: &v1alpha1.ExtractContentConfig{ 637 CacheDir: "/tmp/cache", 638 CatalogDir: "/catalog", 639 }, 640 }, 641 }, 642 } 643 644 for _, testCase := range testCases { 645 pod, err := Pod(catalogSource, "name", "opmImage", "utilImage", "image", testCase.serviceAccount, map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) 646 require.NoError(t, err) 647 if diff := cmp.Diff(testCase.serviceAccount.ImagePullSecrets, pod.Spec.ImagePullSecrets); diff != "" { 648 t.Errorf("got incorrect pod: %v", diff) 649 } 650 } 651 } 652 653 func TestPodNodeSelector(t *testing.T) { 654 catsrc := &v1alpha1.CatalogSource{ 655 ObjectMeta: metav1.ObjectMeta{ 656 Name: "test", 657 Namespace: "testns", 658 }, 659 } 660 661 key := "kubernetes.io/os" 662 value := "linux" 663 664 gotCatSrcPod, err := Pod(catsrc, "hello", "utilImage", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) 665 require.NoError(t, err) 666 gotCatSrcPodSelector := gotCatSrcPod.Spec.NodeSelector 667 668 if gotCatSrcPodSelector[key] != value { 669 t.Errorf("expected %s value for node selector key %s, received %s value instead", value, key, 670 gotCatSrcPodSelector[key]) 671 } 672 } 673 674 func TestPullPolicy(t *testing.T) { 675 var table = []struct { 676 image string 677 policy corev1.PullPolicy 678 }{ 679 { 680 image: "quay.io/operator-framework/olm@sha256:b9d011c0fbfb65b387904f8fafc47ee1a9479d28d395473341288ee126ed993b", 681 policy: corev1.PullIfNotPresent, 682 }, 683 { 684 image: "gcc@sha256:06a6f170d7fff592e44b089c0d2e68d870573eb9a23d9c66d4b6ea11f8fad18b", 685 policy: corev1.PullIfNotPresent, 686 }, 687 { 688 image: "myimage:1.0", 689 policy: corev1.PullAlways, 690 }, 691 { 692 image: "busybox", 693 policy: corev1.PullAlways, 694 }, 695 { 696 image: "gcc@sha256:06a6f170d7fff592e44b089c0d2e68", 697 policy: corev1.PullIfNotPresent, 698 }, 699 { 700 image: "hello@md5:b1946ac92492d2347c6235b4d2611184", 701 policy: corev1.PullIfNotPresent, 702 }, 703 } 704 705 source := &v1alpha1.CatalogSource{ 706 ObjectMeta: metav1.ObjectMeta{ 707 Name: "test", 708 Namespace: "test-ns", 709 }, 710 } 711 712 for _, tt := range table { 713 p, err := Pod(source, "catalog", "opmImage", "utilImage", tt.image, serviceAccount("", "service-account"), nil, nil, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) 714 require.NoError(t, err) 715 policy := p.Spec.Containers[0].ImagePullPolicy 716 if policy != tt.policy { 717 t.Fatalf("expected pull policy %s for image %s", tt.policy, tt.image) 718 } 719 } 720 } 721 722 func TestPodContainerSecurityContext(t *testing.T) { 723 testcases := []struct { 724 title string 725 inputCatsrc *v1alpha1.CatalogSource 726 namespacePodSecurityConfig v1alpha1.SecurityConfig 727 expectedSecurityContext *corev1.PodSecurityContext 728 expectedContainerSecurityContext *corev1.SecurityContext 729 }{ 730 { 731 title: "NoSpecDefined/NamespaceRestricted/UseRestricted", 732 inputCatsrc: &v1alpha1.CatalogSource{ 733 ObjectMeta: metav1.ObjectMeta{ 734 Name: "test", 735 Namespace: testNamespace, 736 }, 737 }, 738 namespacePodSecurityConfig: v1alpha1.Restricted, 739 expectedContainerSecurityContext: &corev1.SecurityContext{ 740 AllowPrivilegeEscalation: ptr.To(false), 741 Capabilities: &corev1.Capabilities{ 742 Drop: []corev1.Capability{"ALL"}, 743 }, 744 ReadOnlyRootFilesystem: ptr.To(false), // Reflecting expected 'restricted' settings 745 }, 746 expectedSecurityContext: &corev1.PodSecurityContext{ 747 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 748 RunAsNonRoot: ptr.To(true), 749 RunAsUser: ptr.To(int64(workloadUserID)), 750 }, 751 }, 752 { 753 title: "NoSpecDefined/NamespaceNotRestricted/UseLegacy", 754 namespacePodSecurityConfig: v1alpha1.Legacy, 755 inputCatsrc: &v1alpha1.CatalogSource{ 756 ObjectMeta: metav1.ObjectMeta{ 757 Name: "test", 758 Namespace: testNamespace, 759 }, 760 }, 761 expectedContainerSecurityContext: &corev1.SecurityContext{ReadOnlyRootFilesystem: ptr.To(false)}, 762 expectedSecurityContext: nil, 763 }, 764 { 765 title: "SpecDefined/NoGRPCPodConfig/NamespaceRestricted/UseRestricted", 766 inputCatsrc: &v1alpha1.CatalogSource{ 767 ObjectMeta: metav1.ObjectMeta{ 768 Name: "test", 769 Namespace: testNamespace, 770 }, 771 Spec: v1alpha1.CatalogSourceSpec{}, 772 }, 773 namespacePodSecurityConfig: v1alpha1.Restricted, 774 expectedContainerSecurityContext: &corev1.SecurityContext{ 775 AllowPrivilegeEscalation: ptr.To(false), 776 Capabilities: &corev1.Capabilities{ 777 Drop: []corev1.Capability{"ALL"}, 778 }, 779 ReadOnlyRootFilesystem: ptr.To(false), 780 }, 781 expectedSecurityContext: &corev1.PodSecurityContext{ 782 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 783 RunAsNonRoot: ptr.To(true), 784 RunAsUser: ptr.To(int64(workloadUserID)), 785 }, 786 }, 787 { 788 title: "SpecDefined/NoGRPCPodConfig/NamespaceNotRestricted/UseLegacy", 789 inputCatsrc: &v1alpha1.CatalogSource{ 790 ObjectMeta: metav1.ObjectMeta{ 791 Name: "test", 792 Namespace: testNamespace, 793 }, 794 Spec: v1alpha1.CatalogSourceSpec{ 795 GrpcPodConfig: &v1alpha1.GrpcPodConfig{}, 796 }, 797 }, 798 namespacePodSecurityConfig: v1alpha1.Legacy, 799 expectedContainerSecurityContext: &corev1.SecurityContext{ReadOnlyRootFilesystem: ptr.To(false)}, 800 expectedSecurityContext: nil, 801 }, 802 { 803 title: "SpecDefined/SecurityContextConfig:Legacy/NoChangeExpected", 804 inputCatsrc: &v1alpha1.CatalogSource{ 805 ObjectMeta: metav1.ObjectMeta{ 806 Name: "test", 807 Namespace: testNamespace, 808 }, 809 Spec: v1alpha1.CatalogSourceSpec{ 810 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 811 SecurityContextConfig: v1alpha1.Legacy, 812 }, 813 }, 814 }, 815 namespacePodSecurityConfig: v1alpha1.Restricted, // set to the opposite of the config to catch possible errors 816 expectedContainerSecurityContext: &corev1.SecurityContext{ReadOnlyRootFilesystem: ptr.To(false)}, 817 expectedSecurityContext: nil, 818 }, 819 { 820 title: "SpecDefined/SecurityContextConfig:Restricted/RestrictedSecurityConfigApplied", 821 inputCatsrc: &v1alpha1.CatalogSource{ 822 ObjectMeta: metav1.ObjectMeta{ 823 Name: "test", 824 Namespace: testNamespace, 825 }, 826 Spec: v1alpha1.CatalogSourceSpec{ 827 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 828 SecurityContextConfig: v1alpha1.Restricted, 829 }, 830 }, 831 }, 832 namespacePodSecurityConfig: v1alpha1.Legacy, // set to the opposite of the config to catch possible errors 833 expectedContainerSecurityContext: &corev1.SecurityContext{ 834 ReadOnlyRootFilesystem: ptr.To(false), 835 AllowPrivilegeEscalation: ptr.To(false), 836 Capabilities: &corev1.Capabilities{ 837 Drop: []corev1.Capability{"ALL"}, 838 }, 839 }, 840 expectedSecurityContext: &corev1.PodSecurityContext{ 841 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 842 RunAsNonRoot: ptr.To(true), 843 RunAsUser: ptr.To(int64(workloadUserID)), 844 }, 845 }, 846 } 847 848 for _, testcase := range testcases { 849 t.Run(testcase.title, func(t *testing.T) { 850 outputPod, err := Pod(testcase.inputCatsrc, "hello", "utilImage", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), workloadUserID, testcase.namespacePodSecurityConfig) 851 require.NoError(t, err) 852 853 // Assert PodSecurityContext 854 require.Equal(t, testcase.expectedSecurityContext, outputPod.Spec.SecurityContext) 855 856 // Assert ContainerSecurityContext 857 require.Equal(t, testcase.expectedContainerSecurityContext, outputPod.Spec.Containers[0].SecurityContext) 858 }) 859 } 860 } 861 862 // TestPodAvoidsConcurrentWrite is a regression test for 863 // https://bugzilla.redhat.com/show_bug.cgi?id=2101357 864 // we were mutating the input annotations and labels parameters causing 865 // concurrent write issues 866 func TestPodAvoidsConcurrentWrite(t *testing.T) { 867 catsrc := &v1alpha1.CatalogSource{ 868 ObjectMeta: metav1.ObjectMeta{ 869 Name: "test", 870 Namespace: "testns", 871 }, 872 } 873 874 labels := map[string]string{ 875 "label": "something", 876 } 877 878 annotations := map[string]string{ 879 "annotation": "somethingelse", 880 } 881 882 gotPod, err := Pod(catsrc, "hello", "opmImage", "utilImage", "busybox", serviceAccount("", "service-account"), labels, annotations, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) 883 require.NoError(t, err) 884 885 // check labels and annotations point to different addresses between parameters and what's in the pod 886 require.NotEqual(t, &labels, &gotPod.Labels) 887 require.NotEqual(t, &annotations, &gotPod.Annotations) 888 889 // check that labels and annotations from the parameters were copied down to the pod's 890 require.Equal(t, labels["label"], gotPod.Labels["label"]) 891 require.Equal(t, annotations["annotation"], gotPod.Annotations["annotation"]) 892 } 893 894 func TestPodSchedulingOverrides(t *testing.T) { 895 // This test ensures that any overriding pod scheduling configuration elements 896 // defined in spec.grpcPodConfig are applied to the catalog source pod created 897 // when spec.sourceType = 'grpc' and spec.image is set. 898 var tolerationSeconds int64 = 120 899 var overriddenPriorityClassName = "some-prio-class" 900 var overriddenNodeSelectors = map[string]string{ 901 "label": "value", 902 "label2": "value2", 903 } 904 var defaultNodeSelectors = map[string]string{ 905 "kubernetes.io/os": "linux", 906 } 907 var defaultPriorityClassName = "" 908 909 var overriddenTolerations = []corev1.Toleration{ 910 { 911 Key: "some/key", 912 Operator: corev1.TolerationOpExists, 913 Effect: corev1.TaintEffectNoExecute, 914 TolerationSeconds: &tolerationSeconds, 915 }, 916 { 917 Key: "someother/key", 918 Operator: corev1.TolerationOpEqual, 919 Effect: corev1.TaintEffectNoSchedule, 920 }, 921 } 922 923 var overriddenAffinity = &corev1.Affinity{ 924 NodeAffinity: &corev1.NodeAffinity{ 925 RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ 926 NodeSelectorTerms: []corev1.NodeSelectorTerm{ 927 { 928 MatchExpressions: []corev1.NodeSelectorRequirement{ 929 { 930 Key: "kubernetes.io/arch", 931 Operator: corev1.NodeSelectorOpIn, 932 Values: []string{ 933 "amd64", 934 "arm", 935 }, 936 }, 937 }, 938 }, 939 }, 940 }, 941 }, 942 } 943 944 testCases := []struct { 945 title string 946 catalogSource *v1alpha1.CatalogSource 947 expectedNodeSelectors map[string]string 948 expectedTolerations []corev1.Toleration 949 expectedAffinity *corev1.Affinity 950 expectedPriorityClassName string 951 annotations map[string]string 952 }{ 953 { 954 title: "no overrides", 955 catalogSource: &v1alpha1.CatalogSource{ 956 ObjectMeta: metav1.ObjectMeta{ 957 Name: "test", 958 Namespace: "testns", 959 }, 960 Spec: v1alpha1.CatalogSourceSpec{ 961 SourceType: v1alpha1.SourceTypeGrpc, 962 Image: "repo/image:tag", 963 }, 964 }, 965 expectedTolerations: nil, 966 expectedAffinity: nil, 967 expectedPriorityClassName: defaultPriorityClassName, 968 expectedNodeSelectors: defaultNodeSelectors, 969 }, { 970 title: "override node selectors", 971 catalogSource: &v1alpha1.CatalogSource{ 972 ObjectMeta: metav1.ObjectMeta{ 973 Name: "test", 974 Namespace: "testns", 975 }, 976 Spec: v1alpha1.CatalogSourceSpec{ 977 SourceType: v1alpha1.SourceTypeGrpc, 978 Image: "repo/image:tag", 979 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 980 NodeSelector: overriddenNodeSelectors, 981 }, 982 }, 983 }, 984 expectedTolerations: nil, 985 expectedAffinity: nil, 986 expectedPriorityClassName: defaultPriorityClassName, 987 expectedNodeSelectors: overriddenNodeSelectors, 988 }, { 989 title: "override priority class name", 990 catalogSource: &v1alpha1.CatalogSource{ 991 ObjectMeta: metav1.ObjectMeta{ 992 Name: "test", 993 Namespace: "testns", 994 }, 995 Spec: v1alpha1.CatalogSourceSpec{ 996 SourceType: v1alpha1.SourceTypeGrpc, 997 Image: "repo/image:tag", 998 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 999 PriorityClassName: &overriddenPriorityClassName, 1000 }, 1001 }, 1002 }, 1003 expectedTolerations: nil, 1004 expectedAffinity: nil, 1005 expectedPriorityClassName: overriddenPriorityClassName, 1006 expectedNodeSelectors: defaultNodeSelectors, 1007 }, { 1008 title: "doesn't override priority class name when its nil", 1009 catalogSource: &v1alpha1.CatalogSource{ 1010 ObjectMeta: metav1.ObjectMeta{ 1011 Name: "test", 1012 Namespace: "testns", 1013 }, 1014 Spec: v1alpha1.CatalogSourceSpec{ 1015 SourceType: v1alpha1.SourceTypeGrpc, 1016 Image: "repo/image:tag", 1017 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 1018 PriorityClassName: nil, 1019 }, 1020 }, 1021 }, 1022 expectedTolerations: nil, 1023 expectedAffinity: nil, 1024 expectedPriorityClassName: defaultPriorityClassName, 1025 expectedNodeSelectors: defaultNodeSelectors, 1026 }, { 1027 title: "Override node tolerations", 1028 catalogSource: &v1alpha1.CatalogSource{ 1029 ObjectMeta: metav1.ObjectMeta{ 1030 Name: "test", 1031 Namespace: "testns", 1032 }, 1033 Spec: v1alpha1.CatalogSourceSpec{ 1034 SourceType: v1alpha1.SourceTypeGrpc, 1035 Image: "repo/image:tag", 1036 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 1037 Tolerations: overriddenTolerations, 1038 }, 1039 }, 1040 }, 1041 expectedTolerations: overriddenTolerations, 1042 expectedAffinity: nil, 1043 expectedPriorityClassName: defaultPriorityClassName, 1044 expectedNodeSelectors: defaultNodeSelectors, 1045 }, { 1046 title: "Override affinity", 1047 catalogSource: &v1alpha1.CatalogSource{ 1048 ObjectMeta: metav1.ObjectMeta{ 1049 Name: "test", 1050 Namespace: "testns", 1051 }, 1052 Spec: v1alpha1.CatalogSourceSpec{ 1053 SourceType: v1alpha1.SourceTypeGrpc, 1054 Image: "repo/image:tag", 1055 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 1056 Affinity: overriddenAffinity, 1057 }, 1058 }, 1059 }, 1060 expectedTolerations: nil, 1061 expectedAffinity: overriddenAffinity, 1062 expectedPriorityClassName: defaultPriorityClassName, 1063 expectedNodeSelectors: defaultNodeSelectors, 1064 }, { 1065 title: "Override all the things", 1066 catalogSource: &v1alpha1.CatalogSource{ 1067 ObjectMeta: metav1.ObjectMeta{ 1068 Name: "test", 1069 Namespace: "testns", 1070 }, 1071 Spec: v1alpha1.CatalogSourceSpec{ 1072 SourceType: v1alpha1.SourceTypeGrpc, 1073 Image: "repo/image:tag", 1074 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 1075 NodeSelector: overriddenNodeSelectors, 1076 PriorityClassName: &overriddenPriorityClassName, 1077 Tolerations: overriddenTolerations, 1078 Affinity: overriddenAffinity, 1079 }, 1080 }, 1081 }, 1082 expectedTolerations: overriddenTolerations, 1083 expectedAffinity: overriddenAffinity, 1084 expectedPriorityClassName: overriddenPriorityClassName, 1085 expectedNodeSelectors: overriddenNodeSelectors, 1086 }, { 1087 title: "priorityClassName annotation takes precedence", 1088 catalogSource: &v1alpha1.CatalogSource{ 1089 ObjectMeta: metav1.ObjectMeta{ 1090 Name: "test", 1091 Namespace: "testns", 1092 }, 1093 Spec: v1alpha1.CatalogSourceSpec{ 1094 SourceType: v1alpha1.SourceTypeGrpc, 1095 Image: "repo/image:tag", 1096 GrpcPodConfig: &v1alpha1.GrpcPodConfig{ 1097 PriorityClassName: &overriddenPriorityClassName, 1098 }, 1099 }, 1100 }, 1101 expectedTolerations: nil, 1102 expectedAffinity: nil, 1103 annotations: map[string]string{ 1104 CatalogPriorityClassKey: "some-OTHER-prio-class", 1105 }, 1106 expectedPriorityClassName: "some-OTHER-prio-class", 1107 expectedNodeSelectors: defaultNodeSelectors, 1108 }, 1109 } 1110 1111 for _, testCase := range testCases { 1112 pod, err := Pod(testCase.catalogSource, "hello", "opmImage", "utilImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, testCase.annotations, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) 1113 require.NoError(t, err) 1114 require.Equal(t, testCase.expectedNodeSelectors, pod.Spec.NodeSelector) 1115 require.Equal(t, testCase.expectedPriorityClassName, pod.Spec.PriorityClassName) 1116 require.Equal(t, testCase.expectedTolerations, pod.Spec.Tolerations) 1117 require.Equal(t, testCase.expectedAffinity, pod.Spec.Affinity) 1118 } 1119 } 1120 1121 // baseClusterState returns a list of runtime objects that are required for the tests to run including the 1122 // target namespace with the assumed default configuration 1123 func baseClusterState() []runtime.Object { 1124 return []runtime.Object{ 1125 defaultNamespace(), 1126 } 1127 } 1128 1129 // defaultNamespace returns a kubernetes namespace with the assumes default settings, 1130 // e.g. Pod Security Admission security policy label 1131 func defaultNamespace() *corev1.Namespace { 1132 return &corev1.Namespace{ 1133 ObjectMeta: metav1.ObjectMeta{ 1134 Name: testNamespace, 1135 Labels: map[string]string{ 1136 // catalogsource pod security configuration depends on the defaultNamespace psa configuration 1137 // adding restricted PSA label as this is the default 1138 "pod-security.kubernetes.io/enforce": "restricted", 1139 }, 1140 }, 1141 } 1142 }