Skip to content

Commit 7ad841b

Browse files
committed
fix(installplan): don't store the manifest contents on the installplan
the installplan status.plan field stores a list of all manifests that will be installed when the installplan is approved. if the installplan is resolving a large set of dependencies, it is possible for it to exceed various limits in kube (grpc request size limit, etcd value size limit, etc). this commit changes the way objects are tracked if they come from bundle images. in that case, the manifests are already unpacked onto the cluster into a configmap. the installplan now stores a reference to the source configmap for each step, rather than storing the manifest itself. this allows the installplan to scale to a large number of resolved objects.
1 parent a90b83a commit 7ad841b

File tree

9 files changed

+247
-46
lines changed

9 files changed

+247
-46
lines changed

pkg/controller/bundle/bundle_unpacker.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"crypto/sha256"
66
"fmt"
7-
87
"github.com/operator-framework/operator-registry/pkg/api"
98
"github.com/operator-framework/operator-registry/pkg/configmap"
109
batchv1 "k8s.io/api/batch/v1"
@@ -34,6 +33,10 @@ func (b *BundleUnpackResult) Bundle() *api.Bundle {
3433
return b.bundle
3534
}
3635

36+
func (b *BundleUnpackResult) Name() string {
37+
return b.name
38+
}
39+
3740
var catalogSourceGVK = operatorsv1alpha1.SchemeGroupVersion.WithKind(operatorsv1alpha1.CatalogSourceKind)
3841

3942
func newBundleUnpackResult(lookup *operatorsv1alpha1.BundleLookup) *BundleUnpackResult {
@@ -251,16 +254,26 @@ const (
251254
CatalogSourceMissingMessage = "referenced catalogsource not found"
252255
JobIncompleteReason = "JobIncomplete"
253256
JobIncompleteMessage = "unpack job not completed"
257+
JobNotStartedReason = "JobNotStarted"
258+
JobNotStartedMessage = "unpack job not yet started"
259+
NotUnpackedReason = "BundleNotUnpacked"
260+
NotUnpackedMessage = "bundle contents have not yet been persisted to installplan status"
254261
)
255262

256263
func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup) (result *BundleUnpackResult, err error) {
257264
result = newBundleUnpackResult(lookup)
265+
266+
// if pending condition is missing, bundle has already been unpacked
258267
cond := result.GetCondition(operatorsv1alpha1.BundleLookupPending)
268+
if cond.Status == corev1.ConditionUnknown {
269+
return result, nil
270+
}
271+
259272
now := c.now()
260273

261274
var cs *operatorsv1alpha1.CatalogSource
262275
if cs, err = c.csLister.CatalogSources(result.CatalogSourceRef.Namespace).Get(result.CatalogSourceRef.Name); err != nil {
263-
if apierrors.IsNotFound(err) && cond.Status != corev1.ConditionTrue && cond.Reason != CatalogSourceMissingReason {
276+
if apierrors.IsNotFound(err) && cond.Reason != CatalogSourceMissingReason {
264277
cond.Status = corev1.ConditionTrue
265278
cond.Reason = CatalogSourceMissingReason
266279
cond.Message = CatalogSourceMissingMessage
@@ -304,7 +317,7 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup)
304317
return
305318
}
306319

307-
if !jobConditionTrue(job, batchv1.JobComplete) && cond.Status != corev1.ConditionTrue && cond.Reason != JobIncompleteReason {
320+
if !jobConditionTrue(job, batchv1.JobComplete) && (cond.Status != corev1.ConditionTrue || cond.Reason != JobIncompleteReason) {
308321
cond.Status = corev1.ConditionTrue
309322
cond.Reason = JobIncompleteReason
310323
cond.Message = JobIncompleteMessage
@@ -319,6 +332,10 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup)
319332
return
320333
}
321334

335+
if result.Bundle() == nil || len(result.Bundle().GetObject()) == 0 {
336+
return
337+
}
338+
322339
// A successful load should remove the pending condition
323340
result.RemoveCondition(operatorsv1alpha1.BundleLookupPending)
324341

pkg/controller/bundle/bundle_unpacker_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ func TestConfigMapUnpacker(t *testing.T) {
7171
Namespace: "ns-a",
7272
Name: "src-a",
7373
},
74+
Conditions: []operatorsv1alpha1.BundleLookupCondition{
75+
{
76+
Type: operatorsv1alpha1.BundleLookupPending,
77+
Status: corev1.ConditionTrue,
78+
Reason: JobNotStartedReason,
79+
Message: JobNotStartedMessage,
80+
},
81+
},
7482
},
7583
},
7684
expected: expected{
@@ -116,6 +124,14 @@ func TestConfigMapUnpacker(t *testing.T) {
116124
Namespace: "ns-a",
117125
Name: "src-a",
118126
},
127+
Conditions: []operatorsv1alpha1.BundleLookupCondition{
128+
{
129+
Type: operatorsv1alpha1.BundleLookupPending,
130+
Status: corev1.ConditionTrue,
131+
Reason: JobNotStartedReason,
132+
Message: JobNotStartedMessage,
133+
},
134+
},
119135
},
120136
},
121137
expected: expected{

pkg/controller/operators/catalog/operator.go

Lines changed: 125 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/scoped"
5454
sharedtime "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/time"
5555
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
56+
"github.com/operator-framework/operator-registry/pkg/configmap"
5657
)
5758

5859
const (
@@ -1105,9 +1106,20 @@ func (o *Operator) createInstallPlan(namespace string, gen int, subs []*v1alpha1
11051106
return reference.GetReference(res)
11061107
}
11071108

1109+
type UnpackedBundleReference struct {
1110+
Kind string `json:"kind"`
1111+
Name string `json:"name"`
1112+
Namespace string `json:"namespace"`
1113+
CatalogSourceName string `json:"catalogSourceName"`
1114+
CatalogSourceNamespace string `json:"catalogSourceNamespace"`
1115+
Replaces string `json:"replaces"`
1116+
}
1117+
1118+
// unpackBundles makes one walk through the bundlelookups and attempts to progress them
11081119
func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.InstallPlan, error) {
11091120
out := plan.DeepCopy()
11101121
unpacked := true
1122+
unpackedCount := len(out.Status.BundleLookups)
11111123

11121124
var errs []error
11131125
for i := 0; i < len(out.Status.BundleLookups); i++ {
@@ -1117,32 +1129,55 @@ func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.In
11171129
errs = append(errs, err)
11181130
continue
11191131
}
1132+
out.Status.BundleLookups[i] = *res.BundleLookup
11201133

1121-
if res == nil {
1134+
// if pending condition is present, still waiting for the job to unpack to configmap
1135+
if res.GetCondition(v1alpha1.BundleLookupPending).Status == corev1.ConditionTrue {
11221136
unpacked = false
11231137
continue
11241138
}
11251139

1126-
out.Status.BundleLookups[i] = *res.BundleLookup
1127-
if res.Bundle() == nil || len(res.Bundle().GetObject()) == 0 {
1128-
unpacked = false
1140+
// if packed condition is missing, bundle has already been unpacked into steps, continue
1141+
if res.GetCondition(resolver.BundleLookupConditionPacked).Status == corev1.ConditionUnknown {
1142+
unpackedCount--
11291143
continue
11301144
}
11311145

1146+
// Ensure that bundle can be applied by the current version of OLM by converting to steps
11321147
steps, err := resolver.NewStepsFromBundle(res.Bundle(), out.GetNamespace(), res.Replaces, res.CatalogSourceRef.Name, res.CatalogSourceRef.Namespace)
11331148
if err != nil {
1134-
errs = append(errs, fmt.Errorf("failed to turn bundle into steps: %s", err.Error()))
1149+
errs = append(errs, fmt.Errorf("failed to turn bundle into steps: %v", err))
11351150
unpacked = false
11361151
continue
11371152
}
11381153

1139-
// Add steps and remove resolved bundle lookup
1154+
// step manifests are replaced with references to the configmap containing them
1155+
for i, s := range steps {
1156+
ref := UnpackedBundleReference{
1157+
Kind: "ConfigMap",
1158+
Namespace: res.CatalogSourceRef.Namespace,
1159+
Name: res.Name(),
1160+
CatalogSourceName: res.CatalogSourceRef.Name,
1161+
CatalogSourceNamespace: res.CatalogSourceRef.Namespace,
1162+
Replaces: res.Replaces,
1163+
}
1164+
r, err := json.Marshal(&ref)
1165+
if err != nil {
1166+
errs = append(errs, fmt.Errorf("failed to generate reference for configmap: %v", err))
1167+
unpacked = false
1168+
continue
1169+
}
1170+
s.Resource.Manifest = string(r)
1171+
steps[i] = s
1172+
}
1173+
unpackedCount--
1174+
res.RemoveCondition(resolver.BundleLookupConditionPacked)
1175+
out.Status.BundleLookups[i] = *res.BundleLookup
11401176
out.Status.Plan = append(out.Status.Plan, steps...)
1141-
out.Status.BundleLookups = append(out.Status.BundleLookups[:i], out.Status.BundleLookups[i+1:]...)
1142-
i--
11431177
}
11441178

11451179
if err := utilerrors.NewAggregate(errs); err != nil {
1180+
o.logger.Debugf("failed to unpack bundles: %v", err)
11461181
return false, nil, err
11471182
}
11481183

@@ -1418,9 +1453,79 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
14181453
ensurer := newStepEnsurer(kubeclient, crclient, dynamicClient)
14191454
b := newBuilder(kubeclient, dynamicClient, o.csvProvidedAPIsIndexer, o.logger)
14201455

1456+
refForStep := func(step *v1alpha1.Step) *UnpackedBundleReference {
1457+
log := o.logger.WithFields(logrus.Fields{"resolving": step.Resolving, "step": step.Resource.Name})
1458+
var ref UnpackedBundleReference
1459+
if err := json.Unmarshal([]byte(step.Resource.Manifest), &ref); err != nil {
1460+
log.Debug("step is not a reference to an unpacked bundle (this is not an error if the step is a manifest)")
1461+
return nil
1462+
}
1463+
log = log.WithField("ref", ref)
1464+
if ref.Kind != "ConfigMap" || ref.Name == "" || ref.Namespace == "" || ref.CatalogSourceName == "" || ref.CatalogSourceNamespace == "" {
1465+
log.Debug("step is not a reference to an unpacked bundle (this is not an error if the step is a manifest)")
1466+
return nil
1467+
}
1468+
return &ref
1469+
}
1470+
1471+
// for each step that points to a configmap instead of holding the full step manifest, find the steps and cache them
1472+
unpackedSteps := map[string][]v1alpha1.StepResource{}
1473+
loader := configmap.NewBundleLoader()
1474+
for _, step := range plan.Status.Plan {
1475+
ref := refForStep(step)
1476+
if ref == nil {
1477+
continue
1478+
}
1479+
cm, err := o.lister.CoreV1().ConfigMapLister().ConfigMaps(ref.Namespace).Get(ref.Name)
1480+
if err != nil {
1481+
return errorwrap.Wrapf(err, "error finding unpacked bundle configmap for ref %s", step.Resource.Manifest)
1482+
}
1483+
b, err := loader.Load(cm)
1484+
if err != nil {
1485+
return errorwrap.Wrapf(err, "error loading unpacked bundle configmap for ref %s", step.Resource.Manifest)
1486+
}
1487+
steps, err := resolver.NewStepResourceFromBundle(b, plan.GetNamespace(), ref.Replaces, ref.CatalogSourceName, ref.CatalogSourceNamespace)
1488+
if err != nil {
1489+
return errorwrap.Wrapf(err, "error calculating steps for ref %s", step.Resource.Manifest)
1490+
}
1491+
unpackedSteps[step.Resolving] = steps
1492+
}
1493+
1494+
manifestForStep := func(step *v1alpha1.Step) (string, error) {
1495+
manifest := step.Resource.Manifest
1496+
ref := refForStep(step)
1497+
if ref != nil {
1498+
log := o.logger.WithFields(logrus.Fields{"resolving": step.Resolving, "step": step.Resource.Name})
1499+
log.WithField("ref", ref).Debug("step is a reference to configmap")
1500+
if usteps, ok := unpackedSteps[step.Resolving]; ok {
1501+
log.Debugf("checking cache for unpacked step")
1502+
// need to find the real manifest from the unpacked steps
1503+
for _, u := range usteps {
1504+
if u.Name == step.Resource.Name &&
1505+
u.Kind == step.Resource.Kind &&
1506+
u.Version == step.Resource.Version &&
1507+
u.Group == step.Resource.Group {
1508+
manifest = u.Manifest
1509+
log.WithField("manifest", manifest).Debug("step replaced with unpacked value")
1510+
}
1511+
}
1512+
}
1513+
if manifest == step.Resource.Manifest {
1514+
return "", errorwrap.Wrapf(err, "couldn't find unpacked step for %v", step)
1515+
}
1516+
}
1517+
return manifest, nil
1518+
}
1519+
14211520
for i, step := range plan.Status.Plan {
1521+
// TODO: should lazy load only when the manifest is actually needed
1522+
manifest, err := manifestForStep(step)
1523+
if err != nil {
1524+
return err
1525+
}
1526+
14221527
doStep := true
1423-
s, err := b.create(step)
1528+
s, err := b.create(step, manifest)
14241529
if err != nil {
14251530
if _, ok := err.(notSupportedStepperErr); ok {
14261531
// stepper not implemented for this type yet
@@ -1448,7 +1553,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
14481553
case v1alpha1.ClusterServiceVersionKind:
14491554
// Marshal the manifest into a CSV instance.
14501555
var csv v1alpha1.ClusterServiceVersion
1451-
err := json.Unmarshal([]byte(step.Resource.Manifest), &csv)
1556+
err := json.Unmarshal([]byte(manifest), &csv)
14521557
if err != nil {
14531558
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
14541559
}
@@ -1481,7 +1586,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
14811586
case v1alpha1.SubscriptionKind:
14821587
// Marshal the manifest into a subscription instance.
14831588
var sub v1alpha1.Subscription
1484-
err := json.Unmarshal([]byte(step.Resource.Manifest), &sub)
1589+
err := json.Unmarshal([]byte(manifest), &sub)
14851590
if err != nil {
14861591
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
14871592
}
@@ -1505,7 +1610,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
15051610

15061611
case resolver.BundleSecretKind:
15071612
var s corev1.Secret
1508-
err := json.Unmarshal([]byte(step.Resource.Manifest), &s)
1613+
err := json.Unmarshal([]byte(manifest), &s)
15091614
if err != nil {
15101615
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
15111616
}
@@ -1544,7 +1649,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
15441649
case clusterRoleKind:
15451650
// Marshal the manifest into a ClusterRole instance.
15461651
var cr rbacv1.ClusterRole
1547-
err := json.Unmarshal([]byte(step.Resource.Manifest), &cr)
1652+
err := json.Unmarshal([]byte(manifest), &cr)
15481653
if err != nil {
15491654
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
15501655
}
@@ -1559,7 +1664,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
15591664
case clusterRoleBindingKind:
15601665
// Marshal the manifest into a RoleBinding instance.
15611666
var rb rbacv1.ClusterRoleBinding
1562-
err := json.Unmarshal([]byte(step.Resource.Manifest), &rb)
1667+
err := json.Unmarshal([]byte(manifest), &rb)
15631668
if err != nil {
15641669
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
15651670
}
@@ -1574,7 +1679,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
15741679
case roleKind:
15751680
// Marshal the manifest into a Role instance.
15761681
var r rbacv1.Role
1577-
err := json.Unmarshal([]byte(step.Resource.Manifest), &r)
1682+
err := json.Unmarshal([]byte(manifest), &r)
15781683
if err != nil {
15791684
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
15801685
}
@@ -1597,7 +1702,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
15971702
case roleBindingKind:
15981703
// Marshal the manifest into a RoleBinding instance.
15991704
var rb rbacv1.RoleBinding
1600-
err := json.Unmarshal([]byte(step.Resource.Manifest), &rb)
1705+
err := json.Unmarshal([]byte(manifest), &rb)
16011706
if err != nil {
16021707
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
16031708
}
@@ -1620,7 +1725,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
16201725
case serviceAccountKind:
16211726
// Marshal the manifest into a ServiceAccount instance.
16221727
var sa corev1.ServiceAccount
1623-
err := json.Unmarshal([]byte(step.Resource.Manifest), &sa)
1728+
err := json.Unmarshal([]byte(manifest), &sa)
16241729
if err != nil {
16251730
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
16261731
}
@@ -1643,7 +1748,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
16431748
case serviceKind:
16441749
// Marshal the manifest into a Service instance
16451750
var s corev1.Service
1646-
err := json.Unmarshal([]byte(step.Resource.Manifest), &s)
1751+
err := json.Unmarshal([]byte(manifest), &s)
16471752
if err != nil {
16481753
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
16491754
}
@@ -1673,7 +1778,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
16731778

16741779
case configMapKind:
16751780
var cfg corev1.ConfigMap
1676-
err := json.Unmarshal([]byte(step.Resource.Manifest), &cfg)
1781+
err := json.Unmarshal([]byte(manifest), &cfg)
16771782
if err != nil {
16781783
return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name)
16791784
}
@@ -1709,7 +1814,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
17091814
}
17101815

17111816
// Marshal the manifest into an unstructured object
1712-
dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(step.Resource.Manifest), 10)
1817+
dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(manifest), 10)
17131818
unstructuredObject := &unstructured.Unstructured{}
17141819
if err := dec.Decode(unstructuredObject); err != nil {
17151820
return errorwrap.Wrapf(err, "error decoding %s object to an unstructured object", step.Resource.Name)

pkg/controller/operators/catalog/operator_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,13 @@ func TestExecutePlan(t *testing.T) {
269269
Version: "v1",
270270
Kind: "ConfigMap",
271271
Name: "cfg",
272-
Manifest: toManifest(t, configmap("cfg", namespace)),
272+
Manifest: toManifest(t, configMap("cfg", namespace)),
273273
},
274274
Status: v1alpha1.StepStatusUnknown,
275275
},
276276
},
277277
),
278-
want: []runtime.Object{configmap("cfg", namespace)},
278+
want: []runtime.Object{configMap("cfg", namespace)},
279279
err: nil,
280280
},
281281
{
@@ -1307,7 +1307,7 @@ func serviceAccount(name, namespace, generateName string, secretRef *corev1.Obje
13071307
}
13081308
}
13091309

1310-
func configmap(name, namespace string) *corev1.ConfigMap {
1310+
func configMap(name, namespace string) *corev1.ConfigMap {
13111311
return &corev1.ConfigMap{
13121312
TypeMeta: metav1.TypeMeta{Kind: configMapKind},
13131313
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},

0 commit comments

Comments
 (0)