Skip to content

Commit 37df153

Browse files
committed
fix(installplans): GC older installplans
1 parent f0495d9 commit 37df153

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"reflect"
9+
"sort"
910
"strings"
1011
"sync"
1112
"time"
@@ -22,6 +23,7 @@ import (
2223
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
2324
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+
"k8s.io/apimachinery/pkg/labels"
2527
"k8s.io/apimachinery/pkg/runtime/schema"
2628
utilclock "k8s.io/apimachinery/pkg/util/clock"
2729
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -67,6 +69,7 @@ const (
6769
roleKind = "Role"
6870
roleBindingKind = "RoleBinding"
6971
generatedByKey = "olm.generated-by"
72+
maxInstallPlanCount = 5
7073
)
7174

7275
// Operator represents a Kubernetes operator that executes InstallPlans by
@@ -1180,6 +1183,64 @@ func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.In
11801183
return unpacked, out, nil
11811184
}
11821185

1186+
func (o *Operator) gcInstallPlans(log logrus.FieldLogger, namespace string) {
1187+
ips, err := o.lister.OperatorsV1alpha1().InstallPlanLister().InstallPlans(namespace).List(labels.Everything())
1188+
if err != nil {
1189+
log.Warn("unable to list installplans for GC")
1190+
}
1191+
1192+
if len(ips) <= maxInstallPlanCount {
1193+
return
1194+
}
1195+
1196+
byGen := map[int][]*v1alpha1.InstallPlan{}
1197+
for _, ip := range ips {
1198+
gen, ok := byGen[ip.Spec.Generation]
1199+
if !ok {
1200+
gen = make([]*v1alpha1.InstallPlan, 0)
1201+
}
1202+
byGen[ip.Spec.Generation] = append(gen, ip)
1203+
}
1204+
1205+
gens := make([]int, 0)
1206+
for i := range byGen {
1207+
gens = append(gens, i)
1208+
}
1209+
1210+
sort.Ints(gens)
1211+
1212+
toDelete := make([]*v1alpha1.InstallPlan, 0)
1213+
1214+
for _, i := range gens {
1215+
g := byGen[i]
1216+
1217+
if len(ips)-len(toDelete) <= maxInstallPlanCount {
1218+
break
1219+
}
1220+
1221+
// if removing all installplans at this generation doesn't dip below the max, safe to delete all of them
1222+
if len(ips)-len(toDelete)-len(g) >= maxInstallPlanCount {
1223+
toDelete = append(toDelete, g...)
1224+
continue
1225+
}
1226+
1227+
// CreationTimestamp sorting shouldn't ever be hit unless there is a bug that causes installplans to be
1228+
// generated without bumping the generation. It is here as a safeguard only.
1229+
1230+
// sort by creation time
1231+
sort.Slice(g, func(i, j int) bool {
1232+
return g[i].CreationTimestamp.Before(&g[j].CreationTimestamp)
1233+
})
1234+
toDelete = append(toDelete, g[:len(ips)-len(toDelete)-maxInstallPlanCount]...)
1235+
}
1236+
1237+
for _, i := range toDelete {
1238+
if err := o.client.OperatorsV1alpha1().InstallPlans(namespace).Delete(context.TODO(), i.GetName(), metav1.DeleteOptions{}); err != nil {
1239+
log.WithField("deleting", i.GetName()).WithError(err).Warn("error GCing old installplan")
1240+
}
1241+
}
1242+
}
1243+
11831244
func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {
11841245
plan, ok := obj.(*v1alpha1.InstallPlan)
11851246
if !ok {
@@ -1196,6 +1257,8 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {
11961257

11971258
logger.Info("syncing")
11981259

1260+
o.gcInstallPlans(logger, plan.GetNamespace())
1261+
11991262
if len(plan.Status.Plan) == 0 && len(plan.Status.BundleLookups) == 0 {
12001263
logger.Info("skip processing installplan without status - subscription sync responsible for initial status")
12011264
return

pkg/controller/operators/catalog/operator_test.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import (
66
"errors"
77
"fmt"
88
"io/ioutil"
9+
"math/rand"
10+
"reflect"
911
"strings"
1012
"testing"
13+
"testing/quick"
1114
"time"
1215

1316
"github.com/sirupsen/logrus"
@@ -28,6 +31,7 @@ import (
2831
"k8s.io/apimachinery/pkg/types"
2932
utilclock "k8s.io/apimachinery/pkg/util/clock"
3033
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
34+
"k8s.io/apiserver/pkg/storage/names"
3135
fakedynamic "k8s.io/client-go/dynamic/fake"
3236
"k8s.io/client-go/informers"
3337
k8sfake "k8s.io/client-go/kubernetes/fake"
@@ -168,7 +172,7 @@ func TestSyncInstallPlanUnhappy(t *testing.T) {
168172
},
169173
},
170174
),
171-
err: fmt.Errorf("attenuated service account query failed - no operator group found that is managing this namespace"),
175+
err: fmt.Errorf("attenuated service account query failed - no operator group found that is managing this namespace"),
172176
},
173177
}
174178

@@ -186,6 +190,70 @@ func TestSyncInstallPlanUnhappy(t *testing.T) {
186190
}
187191
}
188192

193+
type ipSet []runtime.Object
194+
195+
func (ipSet) Generate(rand *rand.Rand, size int) reflect.Value {
196+
ips := []runtime.Object{}
197+
198+
// each i is the generation value
199+
for i := 0; i < rand.Intn(size)+1; i++ {
200+
201+
// generate a few at each generation to account for bugs that don't increment the generation
202+
for j := 0; j < rand.Intn(3); j++ {
203+
ips = append(ips, &v1alpha1.InstallPlan{
204+
ObjectMeta: metav1.ObjectMeta{
205+
Namespace: "ns",
206+
Name: names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%d", i)),
207+
},
208+
Spec: v1alpha1.InstallPlanSpec{
209+
Generation: i,
210+
},
211+
})
212+
}
213+
}
214+
return reflect.ValueOf(ipSet(ips))
215+
}
216+
217+
func TestGC(t *testing.T) {
218+
f := func(ips ipSet) bool {
219+
if len(ips) == 0 {
220+
return true
221+
}
222+
ctx, cancel := context.WithCancel(context.TODO())
223+
defer cancel()
224+
225+
var maxGen int64 = 0
226+
for _, i := range ips {
227+
if g := i.(*v1alpha1.InstallPlan).Generation; g > maxGen {
228+
maxGen = g
229+
}
230+
}
231+
op, err := NewFakeOperator(ctx, "ns", []string{"ns"}, withClientObjs([]runtime.Object(ips)...))
232+
require.NoError(t, err)
233+
234+
err = op.syncInstallPlans(ips[0])
235+
require.NoError(t, err)
236+
237+
out, err := op.client.OperatorsV1alpha1().InstallPlans("ns").List(ctx, metav1.ListOptions{})
238+
require.NoError(t, err)
239+
240+
keptMax := false
241+
for _, o := range out.Items {
242+
if o.Generation == maxGen {
243+
keptMax = true
244+
break
245+
}
246+
}
247+
require.True(t, keptMax)
248+
249+
if len(ips) < maxInstallPlanCount {
250+
return len(out.Items) == len(ips)
251+
}
252+
return len(out.Items) == maxInstallPlanCount
253+
}
254+
require.NoError(t, quick.Check(f, nil))
255+
}
256+
189257
func TestExecutePlan(t *testing.T) {
190258
namespace := "ns"
191259

0 commit comments

Comments
 (0)