github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/reconciler/grpc.go (about) 1 package reconciler 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/google/go-cmp/cmp" 10 "github.com/operator-framework/api/pkg/operators/v1alpha1" 11 12 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 13 controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client" 14 hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash" 15 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" 16 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" 17 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 18 pkgerrors "github.com/pkg/errors" 19 "github.com/sirupsen/logrus" 20 corev1 "k8s.io/api/core/v1" 21 apierrors "k8s.io/apimachinery/pkg/api/errors" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/apimachinery/pkg/labels" 24 "k8s.io/apimachinery/pkg/util/intstr" 25 ) 26 27 const ( 28 CatalogSourceUpdateKey = "catalogsource.operators.coreos.com/update" 29 ServiceHashLabelKey = "olm.service-spec-hash" 30 CatalogPollingRequeuePeriod = 30 * time.Second 31 ) 32 33 // grpcCatalogSourceDecorator wraps CatalogSource to add additional methods 34 type grpcCatalogSourceDecorator struct { 35 *v1alpha1.CatalogSource 36 createPodAsUser int64 37 opmImage string 38 utilImage string 39 } 40 41 type UpdateNotReadyErr struct { 42 catalogName string 43 podName string 44 } 45 46 func (u UpdateNotReadyErr) Error() string { 47 return fmt.Sprintf("catalog polling: %s not ready for update: update pod %s has not yet reported ready", u.catalogName, u.podName) 48 } 49 50 func (s *grpcCatalogSourceDecorator) Selector() labels.Selector { 51 return labels.SelectorFromValidatedSet(map[string]string{ 52 CatalogSourceLabelKey: s.GetName(), 53 }) 54 } 55 56 func (s *grpcCatalogSourceDecorator) SelectorForUpdate() labels.Selector { 57 return labels.SelectorFromValidatedSet(map[string]string{ 58 CatalogSourceUpdateKey: s.GetName(), 59 }) 60 } 61 62 func (s *grpcCatalogSourceDecorator) Labels() map[string]string { 63 return map[string]string{ 64 CatalogSourceLabelKey: s.GetName(), 65 install.OLMManagedLabelKey: install.OLMManagedLabelValue, 66 } 67 } 68 69 func (s *grpcCatalogSourceDecorator) Annotations() map[string]string { 70 // TODO: Maybe something better than just a copy of all annotations would be to have a specific 'podMetadata' section in the CatalogSource? 71 return s.GetAnnotations() 72 } 73 74 func (s *grpcCatalogSourceDecorator) Service() (*corev1.Service, error) { 75 svc := &corev1.Service{ 76 ObjectMeta: metav1.ObjectMeta{ 77 Name: strings.ReplaceAll(s.GetName(), ".", "-"), 78 Namespace: s.GetNamespace(), 79 }, 80 Spec: corev1.ServiceSpec{ 81 Ports: []corev1.ServicePort{ 82 { 83 Name: "grpc", 84 Port: 50051, 85 TargetPort: intstr.FromInt(50051), 86 }, 87 }, 88 Selector: s.Labels(), 89 }, 90 } 91 92 labels := map[string]string{} 93 hash, err := hashutil.DeepHashObject(&svc.Spec) 94 if err != nil { 95 return nil, err 96 } 97 labels[ServiceHashLabelKey] = hash 98 labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue 99 svc.SetLabels(labels) 100 ownerutil.AddOwner(svc, s.CatalogSource, false, false) 101 return svc, nil 102 } 103 104 func (s *grpcCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount { 105 var secrets []corev1.LocalObjectReference 106 blockOwnerDeletion := true 107 isController := true 108 for _, secretName := range s.CatalogSource.Spec.Secrets { 109 if secretName == "" { 110 continue 111 } 112 secrets = append(secrets, corev1.LocalObjectReference{Name: secretName}) 113 } 114 return &corev1.ServiceAccount{ 115 ObjectMeta: metav1.ObjectMeta{ 116 Name: s.GetName(), 117 Namespace: s.GetNamespace(), 118 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 119 OwnerReferences: []metav1.OwnerReference{ 120 { 121 Name: s.GetName(), 122 Kind: v1alpha1.CatalogSourceKind, 123 APIVersion: v1alpha1.CatalogSourceCRDAPIVersion, 124 UID: s.GetUID(), 125 Controller: &isController, 126 BlockOwnerDeletion: &blockOwnerDeletion, 127 }, 128 }, 129 }, 130 ImagePullSecrets: secrets, 131 } 132 } 133 134 func (s *grpcCatalogSourceDecorator) Pod(serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) { 135 pod, err := Pod(s.CatalogSource, "registry-server", s.opmImage, s.utilImage, s.Spec.Image, serviceAccount, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser, defaultPodSecurityConfig) 136 if err != nil { 137 return nil, err 138 } 139 ownerutil.AddOwner(pod, s.CatalogSource, false, true) 140 return pod, nil 141 } 142 143 type GrpcRegistryReconciler struct { 144 now nowFunc 145 Lister operatorlister.OperatorLister 146 OpClient operatorclient.ClientInterface 147 SSAClient *controllerclient.ServerSideApplier 148 createPodAsUser int64 149 opmImage string 150 utilImage string 151 } 152 153 var _ RegistryReconciler = &GrpcRegistryReconciler{} 154 155 func (c *GrpcRegistryReconciler) currentService(source grpcCatalogSourceDecorator) (*corev1.Service, error) { 156 protoService, err := source.Service() 157 if err != nil { 158 return nil, err 159 } 160 serviceName := protoService.GetName() 161 service, err := c.Lister.CoreV1().ServiceLister().Services(source.GetNamespace()).Get(serviceName) 162 if err != nil { 163 logrus.WithField("service", serviceName).Debug("couldn't find service in cache") 164 return nil, nil 165 } 166 return service, nil 167 } 168 169 func (c *GrpcRegistryReconciler) currentServiceAccount(source grpcCatalogSourceDecorator) *corev1.ServiceAccount { 170 serviceAccountName := source.ServiceAccount().GetName() 171 serviceAccount, err := c.Lister.CoreV1().ServiceAccountLister().ServiceAccounts(source.GetNamespace()).Get(serviceAccountName) 172 if err != nil { 173 logrus.WithField("serviceAccount", serviceAccount).Debug("couldn't find serviceAccount in cache") 174 return nil 175 } 176 return serviceAccount 177 } 178 179 func (c *GrpcRegistryReconciler) currentPods(logger *logrus.Entry, source grpcCatalogSourceDecorator) []*corev1.Pod { 180 pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(source.Selector()) 181 if err != nil { 182 logger.WithError(err).Warn("couldn't find pod in cache") 183 return nil 184 } 185 if len(pods) > 1 { 186 logger.WithField("selector", source.Selector()).Info("multiple pods found for selector") 187 } 188 return pods 189 } 190 191 func (c *GrpcRegistryReconciler) currentUpdatePods(logger *logrus.Entry, source grpcCatalogSourceDecorator) []*corev1.Pod { 192 pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(source.SelectorForUpdate()) 193 if err != nil { 194 logger.WithError(err).Warn("couldn't find pod in cache") 195 return nil 196 } 197 if len(pods) > 1 { 198 logger.WithField("selector", source.Selector()).Info("multiple update pods found for selector") 199 } 200 return pods 201 } 202 203 func (c *GrpcRegistryReconciler) currentPodsWithCorrectImageAndSpec(logger *logrus.Entry, source grpcCatalogSourceDecorator, serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) ([]*corev1.Pod, error) { 204 logger.Info("searching for current pods") 205 pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(labels.SelectorFromValidatedSet(source.Labels())) 206 if err != nil { 207 logger.WithError(err).Warn("couldn't find pod in cache") 208 return nil, nil 209 } 210 found := []*corev1.Pod{} 211 newPod, err := source.Pod(serviceAccount, defaultPodSecurityConfig) 212 if err != nil { 213 return nil, err 214 } 215 for _, p := range pods { 216 images, hash := correctImages(source, p), podHashMatch(p, newPod) 217 logger = logger.WithFields(logrus.Fields{ 218 "current-pod.namespace": p.Namespace, "current-pod.name": p.Name, 219 "correctImages": images, "correctHash": hash, 220 }) 221 logger.Info("evaluating current pod") 222 if !hash { 223 logger.Infof("pod spec diff: %s", cmp.Diff(p.Spec, newPod.Spec)) 224 } 225 if correctImages(source, p) && podHashMatch(p, newPod) { 226 found = append(found, p) 227 } 228 } 229 logger.Infof("of %d pods matching label selector, %d have the correct images and matching hash", len(pods), len(found)) 230 return found, nil 231 } 232 233 func correctImages(source grpcCatalogSourceDecorator, pod *corev1.Pod) bool { 234 if source.CatalogSource.Spec.GrpcPodConfig != nil && source.CatalogSource.Spec.GrpcPodConfig.ExtractContent != nil { 235 if len(pod.Spec.InitContainers) != 2 { 236 return false 237 } 238 if len(pod.Spec.Containers) != 1 { 239 return false 240 } 241 return pod.Spec.InitContainers[0].Image == source.utilImage && 242 pod.Spec.InitContainers[1].Image == source.CatalogSource.Spec.Image && 243 pod.Spec.Containers[0].Image == source.opmImage 244 } 245 return pod.Spec.Containers[0].Image == source.CatalogSource.Spec.Image 246 } 247 248 // EnsureRegistryServer ensures that all components of registry server are up to date. 249 func (c *GrpcRegistryReconciler) EnsureRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) error { 250 source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage} 251 252 // if service status is nil, we force create every object to ensure they're created the first time 253 valid, err := isRegistryServiceStatusValid(&source) 254 if err != nil { 255 return err 256 } 257 overwrite := !valid 258 if overwrite { 259 logger.Info("registry service status invalid, need to overwrite") 260 } 261 262 //TODO: if any of these error out, we should write a status back (possibly set RegistryServiceStatus to nil so they get recreated) 263 sa, err := c.ensureSA(source) 264 if err != nil && !apierrors.IsAlreadyExists(err) { 265 return pkgerrors.Wrapf(err, "error ensuring service account: %s", source.GetName()) 266 } 267 268 sa, err = c.OpClient.GetServiceAccount(sa.GetNamespace(), sa.GetName()) 269 if err != nil { 270 return err 271 } 272 273 defaultPodSecurityConfig, err := getDefaultPodContextConfig(c.OpClient, catalogSource.GetNamespace()) 274 if err != nil { 275 return err 276 } 277 278 // recreate the pod if no existing pod is serving the latest image or correct spec 279 current, err := c.currentPodsWithCorrectImageAndSpec(logger, source, sa, defaultPodSecurityConfig) 280 if err != nil { 281 return err 282 } 283 overwritePod := overwrite || len(current) == 0 284 if overwritePod { 285 logger.Info("registry pods invalid, need to overwrite") 286 } 287 288 pod, err := source.Pod(sa, defaultPodSecurityConfig) 289 if err != nil { 290 return err 291 } 292 if err := c.ensurePod(logger, source, sa, defaultPodSecurityConfig, overwritePod); err != nil { 293 return pkgerrors.Wrapf(err, "error ensuring pod: %s", pod.GetName()) 294 } 295 if err := c.ensureUpdatePod(logger, sa, defaultPodSecurityConfig, source); err != nil { 296 if _, ok := err.(UpdateNotReadyErr); ok { 297 return err 298 } 299 return pkgerrors.Wrapf(err, "error ensuring updated catalog source pod: %s", pod.GetName()) 300 } 301 service, err := source.Service() 302 if err != nil { 303 return err 304 } 305 if err := c.ensureService(source, overwrite); err != nil { 306 return pkgerrors.Wrapf(err, "error ensuring service: %s", service.GetName()) 307 } 308 309 if overwritePod { 310 now := c.now() 311 service, err := source.Service() 312 if err != nil { 313 return err 314 } 315 catalogSource.Status.RegistryServiceStatus = &v1alpha1.RegistryServiceStatus{ 316 CreatedAt: now, 317 Protocol: "grpc", 318 ServiceName: service.GetName(), 319 ServiceNamespace: source.GetNamespace(), 320 Port: getPort(service), 321 } 322 } 323 return nil 324 } 325 326 func getPort(service *corev1.Service) string { 327 return fmt.Sprintf("%d", service.Spec.Ports[0].Port) 328 } 329 330 func isRegistryServiceStatusValid(source *grpcCatalogSourceDecorator) (bool, error) { 331 service, err := source.Service() 332 if err != nil { 333 return false, err 334 } 335 if source.Status.RegistryServiceStatus == nil || 336 source.Status.RegistryServiceStatus.ServiceName != service.GetName() || 337 source.Status.RegistryServiceStatus.ServiceNamespace != service.GetNamespace() || 338 source.Status.RegistryServiceStatus.Port != getPort(service) || 339 source.Status.RegistryServiceStatus.Protocol != "grpc" { 340 return false, nil 341 } 342 return true, nil 343 } 344 345 func (c *GrpcRegistryReconciler) ensurePod(logger *logrus.Entry, source grpcCatalogSourceDecorator, serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig, overwrite bool) error { 346 // currentPods refers to the current pod instances of the catalog source 347 currentPods := c.currentPods(logger, source) 348 if len(currentPods) > 0 { 349 if !overwrite { 350 return nil 351 } 352 for _, p := range currentPods { 353 logger.WithFields(logrus.Fields{"pod.namespace": source.GetNamespace(), "pod.name": p.GetName()}).Info("deleting current pod") 354 if err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) { 355 return pkgerrors.Wrapf(err, "error deleting old pod: %s", p.GetName()) 356 } 357 } 358 } 359 desiredPod, err := source.Pod(serviceAccount, defaultPodSecurityConfig) 360 if err != nil { 361 return err 362 } 363 logger.WithFields(logrus.Fields{"pod.namespace": desiredPod.GetNamespace(), "pod.name": desiredPod.GetName()}).Info("creating desired pod") 364 _, err = c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), desiredPod, metav1.CreateOptions{}) 365 if err != nil { 366 return pkgerrors.Wrapf(err, "error creating new pod: %s", desiredPod.GetGenerateName()) 367 } 368 369 return nil 370 } 371 372 // ensureUpdatePod checks that for the same catalog source version the same container imageID is running 373 func (c *GrpcRegistryReconciler) ensureUpdatePod(logger *logrus.Entry, serviceAccount *corev1.ServiceAccount, podSecurityConfig v1alpha1.SecurityConfig, source grpcCatalogSourceDecorator) error { 374 if !source.Poll() { 375 logger.Info("polling not enabled, no update pod will be created") 376 return nil 377 } 378 379 currentLivePods := c.currentPods(logger, source) 380 currentUpdatePods := c.currentUpdatePods(logger, source) 381 382 if source.Update() && len(currentUpdatePods) == 0 { 383 logger.Infof("catalog update required at %s", time.Now().String()) 384 pod, err := c.createUpdatePod(source, serviceAccount, podSecurityConfig) 385 if err != nil { 386 return pkgerrors.Wrapf(err, "creating update catalog source pod") 387 } 388 source.SetLastUpdateTime() 389 return UpdateNotReadyErr{catalogName: source.GetName(), podName: pod.GetName()} 390 } 391 392 // check if update pod is ready - if not requeue the sync 393 // if update pod failed (potentially due to a bad catalog image) delete it 394 for _, p := range currentUpdatePods { 395 fail, err := c.podFailed(p) 396 if err != nil { 397 return err 398 } 399 if fail { 400 return fmt.Errorf("update pod %s in a %s state: deleted update pod", p.GetName(), p.Status.Phase) 401 } 402 if !podReady(p) { 403 return UpdateNotReadyErr{catalogName: source.GetName(), podName: p.GetName()} 404 } 405 } 406 407 for _, updatePod := range currentUpdatePods { 408 // if container imageID IDs are different, switch the serving pods 409 if imageChanged(logger, updatePod, currentLivePods) { 410 err := c.promoteCatalog(updatePod, source.GetName()) 411 if err != nil { 412 return fmt.Errorf("detected imageID change: error during update: %s", err) 413 } 414 // remove old catalog source pod 415 for _, p := range currentLivePods { 416 logger.WithFields(logrus.Fields{"live-pod.namespace": source.GetNamespace(), "live-pod.name": p.Name}).Info("deleting current live pods") 417 if err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) { 418 return pkgerrors.Wrapf(pkgerrors.Wrapf(err, "error deleting pod: %s", p.GetName()), "detected imageID change: error deleting old catalog source pod") 419 } 420 } 421 // done syncing 422 logger.Infof("detected imageID change: catalogsource pod updated at %s", time.Now().String()) 423 return nil 424 } 425 // delete update pod right away, since the digest match, to prevent long-lived duplicate catalog pods 426 logger.WithFields(logrus.Fields{"update-pod.namespace": updatePod.Namespace, "update-pod.name": updatePod.Name}).Debug("catalog polling result: no update; removing duplicate update pod") 427 if err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Delete(context.TODO(), updatePod.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) { 428 return pkgerrors.Wrapf(pkgerrors.Wrapf(err, "error deleting pod: %s", updatePod.GetName()), "duplicate catalog polling pod") 429 } 430 } 431 432 return nil 433 } 434 435 func (c *GrpcRegistryReconciler) ensureService(source grpcCatalogSourceDecorator, overwrite bool) error { 436 service, err := source.Service() 437 if err != nil { 438 return err 439 } 440 svc, err := c.currentService(source) 441 if err != nil { 442 return err 443 } 444 if svc != nil { 445 if !overwrite && ServiceHashMatch(svc, service) { 446 return nil 447 } 448 // TODO(tflannag): Do we care about force deleting services? 449 if err := c.OpClient.DeleteService(service.GetNamespace(), service.GetName(), metav1.NewDeleteOptions(0)); err != nil && !apierrors.IsNotFound(err) { 450 return err 451 } 452 } 453 _, err = c.OpClient.CreateService(service) 454 return err 455 } 456 457 func (c *GrpcRegistryReconciler) ensureSA(source grpcCatalogSourceDecorator) (*corev1.ServiceAccount, error) { 458 sa := source.ServiceAccount() 459 if _, err := c.OpClient.CreateServiceAccount(sa); err != nil { 460 return sa, err 461 } 462 return sa, nil 463 } 464 465 // ServiceHashMatch will check the hash info in existing Service to ensure its 466 // hash info matches the desired Service's hash. 467 func ServiceHashMatch(existing, new *corev1.Service) bool { 468 labels := existing.GetLabels() 469 newLabels := new.GetLabels() 470 if len(labels) == 0 || len(newLabels) == 0 { 471 return false 472 } 473 474 existingSvcSpecHash, ok := labels[ServiceHashLabelKey] 475 if !ok { 476 return false 477 } 478 479 newSvcSpecHash, ok := newLabels[ServiceHashLabelKey] 480 if !ok { 481 return false 482 } 483 484 if existingSvcSpecHash != newSvcSpecHash { 485 return false 486 } 487 488 return true 489 } 490 491 // createUpdatePod is an internal method that creates a pod using the latest catalog source. 492 func (c *GrpcRegistryReconciler) createUpdatePod(source grpcCatalogSourceDecorator, serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) { 493 // remove label from pod to ensure service does not accidentally route traffic to the pod 494 p, err := source.Pod(serviceAccount, defaultPodSecurityConfig) 495 if err != nil { 496 return nil, err 497 } 498 p = swapLabels(p, "", source.Name) 499 500 pod, err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), p, metav1.CreateOptions{}) 501 if err != nil { 502 logrus.WithField("pod", p.GetName()).Warn("couldn't create new catalogsource pod") 503 return nil, err 504 } 505 506 return pod, nil 507 } 508 509 // checkUpdatePodDigest checks update pod to get Image ID and see if it matches the serving (live) pod ImageID 510 func imageChanged(logger *logrus.Entry, updatePod *corev1.Pod, servingPods []*corev1.Pod) bool { 511 updatedCatalogSourcePodImageID := imageID(updatePod) 512 if updatedCatalogSourcePodImageID == "" { 513 logger.WithField("update-pod.name", updatePod.GetName()).Warn("pod status unknown, cannot get the updated pod's imageID") 514 return false 515 } 516 for _, servingPod := range servingPods { 517 servingCatalogSourcePodImageID := imageID(servingPod) 518 if servingCatalogSourcePodImageID == "" { 519 logger.WithField("serving-pod.name", servingPod.GetName()).Warn("pod status unknown, cannot get the current pod's imageID") 520 return false 521 } 522 if updatedCatalogSourcePodImageID != servingCatalogSourcePodImageID { 523 logger.WithField("serving-pod.name", servingPod.GetName()).Infof("catalog image changed: serving pod %s update pod %s", servingCatalogSourcePodImageID, updatedCatalogSourcePodImageID) 524 return true 525 } 526 } 527 528 return false 529 } 530 531 func isPodDead(pod *corev1.Pod) bool { 532 for _, check := range []func(*corev1.Pod) bool{ 533 isPodDeletedByTaintManager, 534 } { 535 if check(pod) { 536 return true 537 } 538 } 539 return false 540 } 541 542 func isPodDeletedByTaintManager(pod *corev1.Pod) bool { 543 if pod.DeletionTimestamp == nil { 544 return false 545 } 546 for _, condition := range pod.Status.Conditions { 547 if condition.Type == corev1.DisruptionTarget && condition.Reason == "DeletionByTaintManager" && condition.Status == corev1.ConditionTrue { 548 return true 549 } 550 } 551 return false 552 } 553 554 // imageID returns the ImageID of the primary catalog source container or an empty string if the image ID isn't available yet. 555 // Note: the pod must be running and the container in a ready status to return a valid ImageID. 556 func imageID(pod *corev1.Pod) string { 557 if len(pod.Status.InitContainerStatuses) == 2 && len(pod.Status.ContainerStatuses) == 1 { 558 // spec.grpcPodConfig.extractContent mode was used for this pod 559 return pod.Status.InitContainerStatuses[1].ImageID 560 } 561 if len(pod.Status.InitContainerStatuses) == 0 && len(pod.Status.ContainerStatuses) == 1 { 562 // spec.grpcPodConfig.extractContent mode was NOT used for this pod (i.e. we're just running the catalog image directly) 563 return pod.Status.ContainerStatuses[0].ImageID 564 } 565 if len(pod.Status.InitContainerStatuses) == 0 && len(pod.Status.ContainerStatuses) == 0 { 566 logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod has not yet populated initContainer and container status") 567 } else { 568 logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod contains unexpected initContainer and container configuration") 569 } 570 return "" 571 } 572 573 func (c *GrpcRegistryReconciler) removePods(pods []*corev1.Pod, namespace string) error { 574 for _, p := range pods { 575 if err := c.OpClient.KubernetesInterface().CoreV1().Pods(namespace).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) { 576 return pkgerrors.Wrapf(err, "error deleting pod: %s", p.GetName()) 577 } 578 } 579 return nil 580 } 581 582 // CheckRegistryServer returns true if the given CatalogSource is considered healthy; false otherwise. 583 func (c *GrpcRegistryReconciler) CheckRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) (bool, error) { 584 source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage} 585 586 // The CheckRegistryServer function is called by the CatalogSoruce controller before the registry resources are created, 587 // returning a IsNotFound error will cause the controller to exit and never create the resources, so we should 588 // only return an error if it is something other than a NotFound error. 589 serviceAccount := source.ServiceAccount() 590 serviceAccount, err := c.OpClient.GetServiceAccount(serviceAccount.GetNamespace(), serviceAccount.GetName()) 591 if err != nil { 592 if !apierrors.IsNotFound(err) { 593 return false, err 594 } 595 return false, nil 596 } 597 598 registryPodSecurityConfig, err := getDefaultPodContextConfig(c.OpClient, catalogSource.GetNamespace()) 599 if err != nil { 600 return false, err 601 } 602 603 // Check on registry resources 604 // TODO: add gRPC health check 605 service, err := c.currentService(source) 606 if err != nil { 607 return false, err 608 } 609 currentPods, err := c.currentPodsWithCorrectImageAndSpec(logger, source, serviceAccount, registryPodSecurityConfig) 610 if err != nil { 611 return false, err 612 } 613 if len(currentPods) < 1 || 614 service == nil || c.currentServiceAccount(source) == nil { 615 return false, nil 616 } 617 podsAreLive, e := detectAndDeleteDeadPods(logger, c.OpClient, currentPods, source.GetNamespace()) 618 if e != nil { 619 return false, fmt.Errorf("error deleting dead pods: %v", e) 620 } 621 return podsAreLive, nil 622 } 623 624 // promoteCatalog swaps the labels on the update pod so that the update pod is now reachable by the catalog service. 625 // By updating the catalog on cluster it promotes the update pod to act as the new version of the catalog on-cluster. 626 func (c *GrpcRegistryReconciler) promoteCatalog(updatePod *corev1.Pod, key string) error { 627 // Update the update pod to promote it to serving pod via the SSA client 628 err := c.SSAClient.Apply(context.TODO(), updatePod, func(p *corev1.Pod) error { 629 p.Labels[CatalogSourceLabelKey] = key 630 p.Labels[CatalogSourceUpdateKey] = "" 631 return nil 632 })() 633 634 return err 635 } 636 637 // podReady returns true if the given Pod has a ready status condition. 638 func podReady(pod *corev1.Pod) bool { 639 if pod.Status.Conditions == nil { 640 return false 641 } 642 for _, cond := range pod.Status.Conditions { 643 if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue { 644 return true 645 } 646 } 647 return false 648 } 649 650 func swapLabels(pod *corev1.Pod, labelKey, updateKey string) *corev1.Pod { 651 pod.Labels[CatalogSourceLabelKey] = labelKey 652 pod.Labels[CatalogSourceUpdateKey] = updateKey 653 return pod 654 } 655 656 // podFailed checks whether the pod status is in a failed or unknown state, and deletes the pod if so. 657 func (c *GrpcRegistryReconciler) podFailed(pod *corev1.Pod) (bool, error) { 658 if pod.Status.Phase == corev1.PodFailed || pod.Status.Phase == corev1.PodUnknown { 659 logrus.WithField("UpdatePod", pod.GetName()).Infof("catalog polling result: update pod %s failed to start", pod.GetName()) 660 err := c.removePods([]*corev1.Pod{pod}, pod.GetNamespace()) 661 if err != nil { 662 return true, pkgerrors.Wrapf(err, "error deleting failed catalog polling pod: %s", pod.GetName()) 663 } 664 return true, nil 665 } 666 return false, nil 667 } 668 669 // podHashMatch will check the hash info in existing pod to ensure its 670 // hash info matches the desired Service's hash. 671 func podHashMatch(existing, new *corev1.Pod) bool { 672 labels := existing.GetLabels() 673 newLabels := new.GetLabels() 674 // If both new & existing pods don't have labels, consider it not matched 675 if len(labels) == 0 || len(newLabels) == 0 { 676 return false 677 } 678 679 existingPodSpecHash, ok := labels[PodHashLabelKey] 680 if !ok { 681 return false 682 } 683 684 newPodSpecHash, ok := newLabels[PodHashLabelKey] 685 if !ok { 686 return false 687 } 688 689 if existingPodSpecHash != newPodSpecHash { 690 return false 691 } 692 693 return true 694 }