1
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
30
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
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
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
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
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
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
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
143 func (u *namespaceConditionUpdater) ProcessDeleteContentErr(err error) {
144 u.deleteContentErrors = append(u.deleteContentErrors, err)
145 }
146
147
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
179 if newCondition == nil {
180 newCondition = newSuccessfulCondition(conditionType)
181 }
182 oldCondition := getCondition(status.Conditions, conditionType)
183
184
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
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 }
222
View as plain text