k8s.io/kubernetes@v1.29.3/pkg/controller/namespace/deletion/status_condition_utils.go (about) 1 /* 2 Copyright 2019 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 deletion 18 19 import ( 20 "fmt" 21 "sort" 22 "strings" 23 24 v1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/client-go/discovery" 27 ) 28 29 // NamespaceConditionUpdater interface that translates namespace deleter errors 30 // into namespace status conditions. 31 type NamespaceConditionUpdater interface { 32 ProcessDiscoverResourcesErr(e error) 33 ProcessGroupVersionErr(e error) 34 ProcessDeleteContentErr(e error) 35 Update(*v1.Namespace) bool 36 } 37 38 type namespaceConditionUpdater struct { 39 newConditions []v1.NamespaceCondition 40 deleteContentErrors []error 41 } 42 43 var _ NamespaceConditionUpdater = &namespaceConditionUpdater{} 44 45 var ( 46 // conditionTypes Namespace condition types that are maintained by namespace_deleter controller. 47 conditionTypes = []v1.NamespaceConditionType{ 48 v1.NamespaceDeletionDiscoveryFailure, 49 v1.NamespaceDeletionGVParsingFailure, 50 v1.NamespaceDeletionContentFailure, 51 v1.NamespaceContentRemaining, 52 v1.NamespaceFinalizersRemaining, 53 } 54 okMessages = map[v1.NamespaceConditionType]string{ 55 v1.NamespaceDeletionDiscoveryFailure: "All resources successfully discovered", 56 v1.NamespaceDeletionGVParsingFailure: "All legacy kube types successfully parsed", 57 v1.NamespaceDeletionContentFailure: "All content successfully deleted, may be waiting on finalization", 58 v1.NamespaceContentRemaining: "All content successfully removed", 59 v1.NamespaceFinalizersRemaining: "All content-preserving finalizers finished", 60 } 61 okReasons = map[v1.NamespaceConditionType]string{ 62 v1.NamespaceDeletionDiscoveryFailure: "ResourcesDiscovered", 63 v1.NamespaceDeletionGVParsingFailure: "ParsedGroupVersions", 64 v1.NamespaceDeletionContentFailure: "ContentDeleted", 65 v1.NamespaceContentRemaining: "ContentRemoved", 66 v1.NamespaceFinalizersRemaining: "ContentHasNoFinalizers", 67 } 68 ) 69 70 // ProcessGroupVersionErr creates error condition if parsing GroupVersion of resources fails. 71 func (u *namespaceConditionUpdater) ProcessGroupVersionErr(err error) { 72 d := v1.NamespaceCondition{ 73 Type: v1.NamespaceDeletionGVParsingFailure, 74 Status: v1.ConditionTrue, 75 LastTransitionTime: metav1.Now(), 76 Reason: "GroupVersionParsingFailed", 77 Message: err.Error(), 78 } 79 u.newConditions = append(u.newConditions, d) 80 } 81 82 // ProcessDiscoverResourcesErr creates error condition from ErrGroupDiscoveryFailed. 83 func (u *namespaceConditionUpdater) ProcessDiscoverResourcesErr(err error) { 84 var msg string 85 if derr, ok := err.(*discovery.ErrGroupDiscoveryFailed); ok { 86 msg = fmt.Sprintf("Discovery failed for some groups, %d failing: %v", len(derr.Groups), err) 87 } else { 88 msg = err.Error() 89 } 90 d := v1.NamespaceCondition{ 91 Type: v1.NamespaceDeletionDiscoveryFailure, 92 Status: v1.ConditionTrue, 93 LastTransitionTime: metav1.Now(), 94 Reason: "DiscoveryFailed", 95 Message: msg, 96 } 97 u.newConditions = append(u.newConditions, d) 98 99 } 100 101 // ProcessContentTotals may create conditions for NamespaceContentRemaining and NamespaceFinalizersRemaining. 102 func (u *namespaceConditionUpdater) ProcessContentTotals(contentTotals allGVRDeletionMetadata) { 103 if len(contentTotals.gvrToNumRemaining) != 0 { 104 remainingResources := []string{} 105 for gvr, numRemaining := range contentTotals.gvrToNumRemaining { 106 if numRemaining == 0 { 107 continue 108 } 109 remainingResources = append(remainingResources, fmt.Sprintf("%s.%s has %d resource instances", gvr.Resource, gvr.Group, numRemaining)) 110 } 111 // sort for stable updates 112 sort.Strings(remainingResources) 113 u.newConditions = append(u.newConditions, v1.NamespaceCondition{ 114 Type: v1.NamespaceContentRemaining, 115 Status: v1.ConditionTrue, 116 LastTransitionTime: metav1.Now(), 117 Reason: "SomeResourcesRemain", 118 Message: fmt.Sprintf("Some resources are remaining: %s", strings.Join(remainingResources, ", ")), 119 }) 120 } 121 122 if len(contentTotals.finalizersToNumRemaining) != 0 { 123 remainingByFinalizer := []string{} 124 for finalizer, numRemaining := range contentTotals.finalizersToNumRemaining { 125 if numRemaining == 0 { 126 continue 127 } 128 remainingByFinalizer = append(remainingByFinalizer, fmt.Sprintf("%s in %d resource instances", finalizer, numRemaining)) 129 } 130 // sort for stable updates 131 sort.Strings(remainingByFinalizer) 132 u.newConditions = append(u.newConditions, v1.NamespaceCondition{ 133 Type: v1.NamespaceFinalizersRemaining, 134 Status: v1.ConditionTrue, 135 LastTransitionTime: metav1.Now(), 136 Reason: "SomeFinalizersRemain", 137 Message: fmt.Sprintf("Some content in the namespace has finalizers remaining: %s", strings.Join(remainingByFinalizer, ", ")), 138 }) 139 } 140 } 141 142 // ProcessDeleteContentErr creates error condition from multiple delete content errors. 143 func (u *namespaceConditionUpdater) ProcessDeleteContentErr(err error) { 144 u.deleteContentErrors = append(u.deleteContentErrors, err) 145 } 146 147 // Update compiles processed errors from namespace deletion into status conditions. 148 func (u *namespaceConditionUpdater) Update(ns *v1.Namespace) bool { 149 if c := getCondition(u.newConditions, v1.NamespaceDeletionContentFailure); c == nil { 150 if c := makeDeleteContentCondition(u.deleteContentErrors); c != nil { 151 u.newConditions = append(u.newConditions, *c) 152 } 153 } 154 return updateConditions(&ns.Status, u.newConditions) 155 } 156 157 func makeDeleteContentCondition(err []error) *v1.NamespaceCondition { 158 if len(err) == 0 { 159 return nil 160 } 161 msgs := make([]string, 0, len(err)) 162 for _, e := range err { 163 msgs = append(msgs, e.Error()) 164 } 165 sort.Strings(msgs) 166 return &v1.NamespaceCondition{ 167 Type: v1.NamespaceDeletionContentFailure, 168 Status: v1.ConditionTrue, 169 LastTransitionTime: metav1.Now(), 170 Reason: "ContentDeletionFailed", 171 Message: fmt.Sprintf("Failed to delete all resource types, %d remaining: %v", len(err), strings.Join(msgs, ", ")), 172 } 173 } 174 175 func updateConditions(status *v1.NamespaceStatus, newConditions []v1.NamespaceCondition) (hasChanged bool) { 176 for _, conditionType := range conditionTypes { 177 newCondition := getCondition(newConditions, conditionType) 178 // if we weren't failing, then this returned nil. We should set the "ok" variant of the condition 179 if newCondition == nil { 180 newCondition = newSuccessfulCondition(conditionType) 181 } 182 oldCondition := getCondition(status.Conditions, conditionType) 183 184 // only new condition of this type exists, add to the list 185 if oldCondition == nil { 186 status.Conditions = append(status.Conditions, *newCondition) 187 hasChanged = true 188 189 } else if oldCondition.Status != newCondition.Status || oldCondition.Message != newCondition.Message || oldCondition.Reason != newCondition.Reason { 190 // old condition needs to be updated 191 if oldCondition.Status != newCondition.Status { 192 oldCondition.LastTransitionTime = metav1.Now() 193 } 194 oldCondition.Type = newCondition.Type 195 oldCondition.Status = newCondition.Status 196 oldCondition.Reason = newCondition.Reason 197 oldCondition.Message = newCondition.Message 198 hasChanged = true 199 } 200 } 201 return 202 } 203 204 func newSuccessfulCondition(conditionType v1.NamespaceConditionType) *v1.NamespaceCondition { 205 return &v1.NamespaceCondition{ 206 Type: conditionType, 207 Status: v1.ConditionFalse, 208 LastTransitionTime: metav1.Now(), 209 Reason: okReasons[conditionType], 210 Message: okMessages[conditionType], 211 } 212 } 213 214 func getCondition(conditions []v1.NamespaceCondition, conditionType v1.NamespaceConditionType) *v1.NamespaceCondition { 215 for i := range conditions { 216 if conditions[i].Type == conditionType { 217 return &(conditions[i]) 218 } 219 } 220 return nil 221 }