Skip to content

Commit a670268

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

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"k8s.io/apimachinery/pkg/labels"
89
"reflect"
10+
"sort"
911
"strings"
1012
"sync"
1113
"time"
@@ -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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import (
66
"errors"
77
"fmt"
88
"io/ioutil"
9+
"k8s.io/apiserver/pkg/storage/names"
10+
"math/rand"
11+
"reflect"
912
"strings"
1013
"testing"
14+
"testing/quick"
1115
"time"
1216

1317
"github.com/sirupsen/logrus"
@@ -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)