1
16
17 package drain
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23 "time"
24
25 appsv1 "k8s.io/api/apps/v1"
26 corev1 "k8s.io/api/core/v1"
27 apierrors "k8s.io/apimachinery/pkg/api/errors"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 )
30
31 const (
32 daemonSetFatal = "DaemonSet-managed Pods (use --ignore-daemonsets to ignore)"
33 daemonSetWarning = "ignoring DaemonSet-managed Pods"
34 localStorageFatal = "Pods with local storage (use --delete-emptydir-data to override)"
35 localStorageWarning = "deleting Pods with local storage"
36 unmanagedFatal = "cannot delete Pods that declare no controller (use --force to override)"
37 unmanagedWarning = "deleting Pods that declare no controller"
38 )
39
40
41 type PodDelete struct {
42 Pod corev1.Pod
43 Status PodDeleteStatus
44 }
45
46
47 type PodDeleteList struct {
48 items []PodDelete
49 }
50
51
52 func (l *PodDeleteList) Pods() []corev1.Pod {
53 pods := []corev1.Pod{}
54 for _, i := range l.items {
55 if i.Status.Delete {
56 pods = append(pods, i.Pod)
57 }
58 }
59 return pods
60 }
61
62
63 func (l *PodDeleteList) Warnings() string {
64 ps := make(map[string][]string)
65 for _, i := range l.items {
66 if i.Status.Reason == PodDeleteStatusTypeWarning {
67 ps[i.Status.Message] = append(ps[i.Status.Message], fmt.Sprintf("%s/%s", i.Pod.Namespace, i.Pod.Name))
68 }
69 }
70
71 msgs := []string{}
72 for key, pods := range ps {
73 msgs = append(msgs, fmt.Sprintf("%s: %s", key, strings.Join(pods, ", ")))
74 }
75 return strings.Join(msgs, "; ")
76 }
77
78 func (l *PodDeleteList) errors() []error {
79 failedPods := make(map[string][]string)
80 for _, i := range l.items {
81 if i.Status.Reason == PodDeleteStatusTypeError {
82 msg := i.Status.Message
83 if msg == "" {
84 msg = "unexpected error"
85 }
86 failedPods[msg] = append(failedPods[msg], fmt.Sprintf("%s/%s", i.Pod.Namespace, i.Pod.Name))
87 }
88 }
89 errs := make([]error, 0, len(failedPods))
90 for msg, pods := range failedPods {
91 errs = append(errs, fmt.Errorf("cannot delete %s: %s", msg, strings.Join(pods, ", ")))
92 }
93 return errs
94 }
95
96
97 type PodDeleteStatus struct {
98 Delete bool
99 Reason string
100 Message string
101 }
102
103
104 type PodFilter func(corev1.Pod) PodDeleteStatus
105
106 const (
107
108 PodDeleteStatusTypeOkay = "Okay"
109
110 PodDeleteStatusTypeSkip = "Skip"
111
112 PodDeleteStatusTypeWarning = "Warning"
113
114 PodDeleteStatusTypeError = "Error"
115 )
116
117
118 func MakePodDeleteStatusOkay() PodDeleteStatus {
119 return PodDeleteStatus{
120 Delete: true,
121 Reason: PodDeleteStatusTypeOkay,
122 }
123 }
124
125
126 func MakePodDeleteStatusSkip() PodDeleteStatus {
127 return PodDeleteStatus{
128 Delete: false,
129 Reason: PodDeleteStatusTypeSkip,
130 }
131 }
132
133
134 func MakePodDeleteStatusWithWarning(delete bool, message string) PodDeleteStatus {
135 return PodDeleteStatus{
136 Delete: delete,
137 Reason: PodDeleteStatusTypeWarning,
138 Message: message,
139 }
140 }
141
142
143 func MakePodDeleteStatusWithError(message string) PodDeleteStatus {
144 return PodDeleteStatus{
145 Delete: false,
146 Reason: PodDeleteStatusTypeError,
147 Message: message,
148 }
149 }
150
151
152
153 func (d *Helper) makeFilters() []PodFilter {
154 baseFilters := []PodFilter{
155 d.skipDeletedFilter,
156 d.daemonSetFilter,
157 d.mirrorPodFilter,
158 d.localStorageFilter,
159 d.unreplicatedFilter,
160 }
161 return append(baseFilters, d.AdditionalFilters...)
162 }
163
164 func hasLocalStorage(pod corev1.Pod) bool {
165 for _, volume := range pod.Spec.Volumes {
166 if volume.EmptyDir != nil {
167 return true
168 }
169 }
170
171 return false
172 }
173
174 func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
175
176
177
178
179
180
181 controllerRef := metav1.GetControllerOf(&pod)
182 if controllerRef == nil || controllerRef.Kind != appsv1.SchemeGroupVersion.WithKind("DaemonSet").Kind {
183 return MakePodDeleteStatusOkay()
184 }
185
186 if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
187 return MakePodDeleteStatusOkay()
188 }
189
190 if _, err := d.Client.AppsV1().DaemonSets(pod.Namespace).Get(context.TODO(), controllerRef.Name, metav1.GetOptions{}); err != nil {
191
192 if apierrors.IsNotFound(err) && d.Force {
193 return MakePodDeleteStatusWithWarning(true, err.Error())
194 }
195
196 return MakePodDeleteStatusWithError(err.Error())
197 }
198
199 if !d.IgnoreAllDaemonSets {
200 return MakePodDeleteStatusWithError(daemonSetFatal)
201 }
202
203 return MakePodDeleteStatusWithWarning(false, daemonSetWarning)
204 }
205
206 func (d *Helper) mirrorPodFilter(pod corev1.Pod) PodDeleteStatus {
207 if _, found := pod.ObjectMeta.Annotations[corev1.MirrorPodAnnotationKey]; found {
208 return MakePodDeleteStatusSkip()
209 }
210 return MakePodDeleteStatusOkay()
211 }
212
213 func (d *Helper) localStorageFilter(pod corev1.Pod) PodDeleteStatus {
214 if !hasLocalStorage(pod) {
215 return MakePodDeleteStatusOkay()
216 }
217
218 if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
219 return MakePodDeleteStatusOkay()
220 }
221 if !d.DeleteEmptyDirData {
222 return MakePodDeleteStatusWithError(localStorageFatal)
223 }
224
225
226
227
228 return MakePodDeleteStatusWithWarning(true, localStorageWarning)
229 }
230
231 func (d *Helper) unreplicatedFilter(pod corev1.Pod) PodDeleteStatus {
232
233 if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
234 return MakePodDeleteStatusOkay()
235 }
236
237 controllerRef := metav1.GetControllerOf(&pod)
238 if controllerRef != nil {
239 return MakePodDeleteStatusOkay()
240 }
241 if d.Force {
242 return MakePodDeleteStatusWithWarning(true, unmanagedWarning)
243 }
244 return MakePodDeleteStatusWithError(unmanagedFatal)
245 }
246
247 func shouldSkipPod(pod corev1.Pod, skipDeletedTimeoutSeconds int) bool {
248 return skipDeletedTimeoutSeconds > 0 &&
249 !pod.ObjectMeta.DeletionTimestamp.IsZero() &&
250 int(time.Now().Sub(pod.ObjectMeta.GetDeletionTimestamp().Time).Seconds()) > skipDeletedTimeoutSeconds
251 }
252
253 func (d *Helper) skipDeletedFilter(pod corev1.Pod) PodDeleteStatus {
254 if shouldSkipPod(pod, d.SkipWaitForDeleteTimeoutSeconds) {
255 return MakePodDeleteStatusSkip()
256 }
257 return MakePodDeleteStatusOkay()
258 }
259
View as plain text