-
Notifications
You must be signed in to change notification settings - Fork 110
/
filters.go
258 lines (223 loc) · 7.79 KB
/
filters.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package drain
import (
"context"
"fmt"
"strings"
"time"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
daemonSetFatal = "DaemonSet-managed Pods (use --ignore-daemonsets to ignore)"
daemonSetWarning = "ignoring DaemonSet-managed Pods"
localStorageFatal = "Pods with local storage (use --delete-emptydir-data to override)"
localStorageWarning = "deleting Pods with local storage"
unmanagedFatal = "Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet (use --force to override)"
unmanagedWarning = "deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet"
)
// PodDelete informs filtering logic whether a pod should be deleted or not
type PodDelete struct {
Pod corev1.Pod
Status PodDeleteStatus
}
// PodDeleteList is a wrapper around []PodDelete
type PodDeleteList struct {
items []PodDelete
}
// Pods returns a list of all pods marked for deletion after filtering.
func (l *PodDeleteList) Pods() []corev1.Pod {
pods := []corev1.Pod{}
for _, i := range l.items {
if i.Status.Delete {
pods = append(pods, i.Pod)
}
}
return pods
}
// Warnings returns all warning messages concatenated into a string.
func (l *PodDeleteList) Warnings() string {
ps := make(map[string][]string)
for _, i := range l.items {
if i.Status.Reason == PodDeleteStatusTypeWarning {
ps[i.Status.Message] = append(ps[i.Status.Message], fmt.Sprintf("%s/%s", i.Pod.Namespace, i.Pod.Name))
}
}
msgs := []string{}
for key, pods := range ps {
msgs = append(msgs, fmt.Sprintf("%s: %s", key, strings.Join(pods, ", ")))
}
return strings.Join(msgs, "; ")
}
func (l *PodDeleteList) errors() []error {
failedPods := make(map[string][]string)
for _, i := range l.items {
if i.Status.Reason == PodDeleteStatusTypeError {
msg := i.Status.Message
if msg == "" {
msg = "unexpected error"
}
failedPods[msg] = append(failedPods[msg], fmt.Sprintf("%s/%s", i.Pod.Namespace, i.Pod.Name))
}
}
errs := make([]error, 0, len(failedPods))
for msg, pods := range failedPods {
errs = append(errs, fmt.Errorf("cannot delete %s: %s", msg, strings.Join(pods, ", ")))
}
return errs
}
// PodDeleteStatus informs filters if a pod should be deleted
type PodDeleteStatus struct {
Delete bool
Reason string
Message string
}
// PodFilter takes a pod and returns a PodDeleteStatus
type PodFilter func(corev1.Pod) PodDeleteStatus
const (
// PodDeleteStatusTypeOkay is "Okay"
PodDeleteStatusTypeOkay = "Okay"
// PodDeleteStatusTypeSkip is "Skip"
PodDeleteStatusTypeSkip = "Skip"
// PodDeleteStatusTypeWarning is "Warning"
PodDeleteStatusTypeWarning = "Warning"
// PodDeleteStatusTypeError is "Error"
PodDeleteStatusTypeError = "Error"
)
// MakePodDeleteStatusOkay is a helper method to return the corresponding PodDeleteStatus
func MakePodDeleteStatusOkay() PodDeleteStatus {
return PodDeleteStatus{
Delete: true,
Reason: PodDeleteStatusTypeOkay,
}
}
// MakePodDeleteStatusSkip is a helper method to return the corresponding PodDeleteStatus
func MakePodDeleteStatusSkip() PodDeleteStatus {
return PodDeleteStatus{
Delete: false,
Reason: PodDeleteStatusTypeSkip,
}
}
// MakePodDeleteStatusWithWarning is a helper method to return the corresponding PodDeleteStatus
func MakePodDeleteStatusWithWarning(delete bool, message string) PodDeleteStatus {
return PodDeleteStatus{
Delete: delete,
Reason: PodDeleteStatusTypeWarning,
Message: message,
}
}
// MakePodDeleteStatusWithError is a helper method to return the corresponding PodDeleteStatus
func MakePodDeleteStatusWithError(message string) PodDeleteStatus {
return PodDeleteStatus{
Delete: false,
Reason: PodDeleteStatusTypeError,
Message: message,
}
}
// The filters are applied in a specific order, only the last filter's
// message will be retained if there are any warnings.
func (d *Helper) makeFilters() []PodFilter {
baseFilters := []PodFilter{
d.skipDeletedFilter,
d.daemonSetFilter,
d.mirrorPodFilter,
d.localStorageFilter,
d.unreplicatedFilter,
}
return append(baseFilters, d.AdditionalFilters...)
}
func hasLocalStorage(pod corev1.Pod) bool {
for _, volume := range pod.Spec.Volumes {
if volume.EmptyDir != nil {
return true
}
}
return false
}
func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
// Note that we return false in cases where the pod is DaemonSet managed,
// regardless of flags.
//
// The exception is for pods that are orphaned (the referencing
// management resource - including DaemonSet - is not found).
// Such pods will be deleted if --force is used.
controllerRef := metav1.GetControllerOf(&pod)
if controllerRef == nil || controllerRef.Kind != appsv1.SchemeGroupVersion.WithKind("DaemonSet").Kind {
return MakePodDeleteStatusOkay()
}
// Any finished pod can be removed.
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return MakePodDeleteStatusOkay()
}
if _, err := d.Client.AppsV1().DaemonSets(pod.Namespace).Get(context.TODO(), controllerRef.Name, metav1.GetOptions{}); err != nil {
// remove orphaned pods with a warning if --force is used
if apierrors.IsNotFound(err) && d.Force {
return MakePodDeleteStatusWithWarning(true, err.Error())
}
return MakePodDeleteStatusWithError(err.Error())
}
if !d.IgnoreAllDaemonSets {
return MakePodDeleteStatusWithError(daemonSetFatal)
}
return MakePodDeleteStatusWithWarning(false, daemonSetWarning)
}
func (d *Helper) mirrorPodFilter(pod corev1.Pod) PodDeleteStatus {
if _, found := pod.ObjectMeta.Annotations[corev1.MirrorPodAnnotationKey]; found {
return MakePodDeleteStatusSkip()
}
return MakePodDeleteStatusOkay()
}
func (d *Helper) localStorageFilter(pod corev1.Pod) PodDeleteStatus {
if !hasLocalStorage(pod) {
return MakePodDeleteStatusOkay()
}
// Any finished pod can be removed.
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return MakePodDeleteStatusOkay()
}
if !d.DeleteEmptyDirData {
return MakePodDeleteStatusWithError(localStorageFatal)
}
// TODO: this warning gets dropped by subsequent filters;
// consider accounting for multiple warning conditions or at least
// preserving the last warning message.
return MakePodDeleteStatusWithWarning(true, localStorageWarning)
}
func (d *Helper) unreplicatedFilter(pod corev1.Pod) PodDeleteStatus {
// any finished pod can be removed
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return MakePodDeleteStatusOkay()
}
controllerRef := metav1.GetControllerOf(&pod)
if controllerRef != nil {
return MakePodDeleteStatusOkay()
}
if d.Force {
return MakePodDeleteStatusWithWarning(true, unmanagedWarning)
}
return MakePodDeleteStatusWithError(unmanagedFatal)
}
func shouldSkipPod(pod corev1.Pod, skipDeletedTimeoutSeconds int) bool {
return skipDeletedTimeoutSeconds > 0 &&
!pod.ObjectMeta.DeletionTimestamp.IsZero() &&
int(time.Now().Sub(pod.ObjectMeta.GetDeletionTimestamp().Time).Seconds()) > skipDeletedTimeoutSeconds
}
func (d *Helper) skipDeletedFilter(pod corev1.Pod) PodDeleteStatus {
if shouldSkipPod(pod, d.SkipWaitForDeleteTimeoutSeconds) {
return MakePodDeleteStatusSkip()
}
return MakePodDeleteStatusOkay()
}