diff --git a/internal/controller/operator/clusterpodplacementconfig_controller.go b/internal/controller/operator/clusterpodplacementconfig_controller.go index c79f93bc4..702e0b676 100644 --- a/internal/controller/operator/clusterpodplacementconfig_controller.go +++ b/internal/controller/operator/clusterpodplacementconfig_controller.go @@ -343,6 +343,31 @@ func (r *ClusterPodPlacementConfigReconciler) handleDelete(ctx context.Context, // We execute the update here because this function returns multiple times before the whole deletion process is completed. // Executing it here ensures that the conditions are updated throughout the deletion process. _ = r.updateStatus(ctx, clusterPodPlacementConfig) + + // Prevent deletion while PodPlacementConfig resources still exist + // Check this BEFORE tearing down webhook/operand resources to prevent orphaning PPCs + if controllerutil.ContainsFinalizer(clusterPodPlacementConfig, utils.CPPCNoPPCObjectFinalizer) { + log.V(1).Info("Checking for existing PodPlacementConfig resources before deletion") + ppcList := &multiarchv1beta1.PodPlacementConfigList{} + if err := r.List(ctx, ppcList); err != nil { + log.Error(err, "Unable to list PodPlacementConfigs during deletion") + return err + } + if len(ppcList.Items) > 0 { + // Block deletion - PPCs still exist + err := errors.New(RemainingPodPlacementConfig) + log.Error(err, "Deletion blocked due to existing PodPlacementConfig resources") + return err + } + // All PPCs are gone, remove the finalizer and continue with deletion + log.V(1).Info("No PodPlacementConfig resources found, removing no-pod-placement-config finalizer") + controllerutil.RemoveFinalizer(clusterPodPlacementConfig, utils.CPPCNoPPCObjectFinalizer) + if err := r.Update(ctx, clusterPodPlacementConfig); err != nil { + log.Error(err, "Unable to remove no-pod-placement-config finalizer from ClusterPodPlacementConfig") + return err + } + } + objsToDelete := []utils.ToDeleteRef{ { NamespacedTypedClient: r.ClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations(), @@ -384,29 +409,6 @@ func (r *ClusterPodPlacementConfigReconciler) handleDelete(ctx context.Context, return errors.New(waitingForWebhookSInterruptionError) } - // Prevent deletion while PodPlacementConfig resources still exist - if controllerutil.ContainsFinalizer(clusterPodPlacementConfig, utils.CPPCNoPPCObjectFinalizer) { - log.V(1).Info("Checking for existing PodPlacementConfig resources before deletion") - ppcList := &multiarchv1beta1.PodPlacementConfigList{} - if err := r.List(ctx, ppcList); err != nil { - log.Error(err, "Unable to list PodPlacementConfigs during deletion") - return err - } - if len(ppcList.Items) > 0 { - err := errors.New(RemainingPodPlacementConfig) - log.Error(err, "Deletion blocked due to existing PodPlacementConfig resources") - return err - } - // All PPCs are gone, remove the finalizer - log.V(1).Info("No PodPlacementConfig resources found, removing no-pod-placement-config finalizer") - controllerutil.RemoveFinalizer(clusterPodPlacementConfig, utils.CPPCNoPPCObjectFinalizer) - if err := r.Update(ctx, clusterPodPlacementConfig); err != nil { - log.Error(err, "Unable to remove no-pod-placement-config finalizer from ClusterPodPlacementConfig") - return err - } - return nil - } - log.Info("Looking for pods with the scheduling gate") // get pending pods as we cannot query for the scheduling gate pods, err := r.ClientSet.CoreV1().Pods("").List(ctx, metav1.ListOptions{ diff --git a/internal/controller/operator/clusterpodplacementconfig_controller_test.go b/internal/controller/operator/clusterpodplacementconfig_controller_test.go index 4d9c0ec14..8b1af9dc3 100644 --- a/internal/controller/operator/clusterpodplacementconfig_controller_test.go +++ b/internal/controller/operator/clusterpodplacementconfig_controller_test.go @@ -799,6 +799,20 @@ var _ = Describe("internal/Controller/ClusterPodPlacementConfig/ClusterPodPlacem By("Attempting to delete ClusterPodPlacementConfig") err = k8sClient.Delete(ctx, builder.NewClusterPodPlacementConfig().WithName(common.SingletonResourceObjectName).Build()) Expect(err).To(HaveOccurred(), "the ClusterPodPlacementConfig should not be able to deleted when local PodPlacementConfigs exist", err) + By("Verifying webhook resources still exist after blocked deletion") + Eventually(func(g Gomega) { + mwc := &admissionv1.MutatingWebhookConfiguration{} + err = k8sClient.Get(ctx, crclient.ObjectKey{Name: utils.PodMutatingWebhookConfigurationName}, mwc) + g.Expect(err).NotTo(HaveOccurred(), "webhook configuration should still exist") + + svc := &corev1.Service{} + err = k8sClient.Get(ctx, crclient.ObjectKey{Name: utils.PodPlacementWebhookName, Namespace: utils.Namespace()}, svc) + g.Expect(err).NotTo(HaveOccurred(), "webhook service should still exist") + + deployment := &appsv1.Deployment{} + err = k8sClient.Get(ctx, crclient.ObjectKey{Name: utils.PodPlacementWebhookName, Namespace: utils.Namespace()}, deployment) + g.Expect(err).NotTo(HaveOccurred(), "webhook deployment should still exist") + }).Should(Succeed(), "webhook resources should remain intact when deletion is blocked") By("Deleting the local PodPlacementConfig") err = k8sClient.Delete(ctx, builder.NewPodPlacementConfig(). WithName("test-ppc").