Skip to content

Commit b24bd9f

Browse files
(bugfix): update run bundle(-upgrade) logic (#6182) (#6262)
* update run bundle(-upgrade) logic to be able to handle large FBCs by partitioning them into multiple ConfigMaps mounted as volumes * add changelog * fix lint issues * add fbc registry pod unit tests * add unit tests Signed-off-by: rashmigottipati <[email protected]> Signed-off-by: rashmigottipati <[email protected]> Co-authored-by: Bryce Palmer <[email protected]>
1 parent 5779ad7 commit b24bd9f

25 files changed

+520
-70
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# entries is a list of entries to include in
2+
# release notes and/or the migration guide
3+
entries:
4+
- description: >
5+
For `operator-sdk run bundle(-upgrade)`: fix a bug in the logic that would attempt to
6+
create a `ConfigMap` that contained the entire contents of an FBC. Now if the FBC contents
7+
are to large to fit into a single `ConfigMap`, the FBC contents will be partitioned and split
8+
amongst multiple `ConfigMap` resources.
9+
10+
# kind is one of:
11+
# - addition
12+
# - change
13+
# - deprecation
14+
# - removal
15+
# - bugfix
16+
kind: "bugfix"
17+
18+
# Is this a breaking change?
19+
breaking: false

internal/olm/operator/registry/fbcindex/fbc_registry_pod.go

Lines changed: 145 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"path"
23+
"strings"
2324
"text/template"
2425
"time"
2526

@@ -46,8 +47,9 @@ const (
4647
defaultContainerName = "registry-grpc"
4748
defaultContainerPortName = "grpc"
4849

49-
defaultConfigMapName = "operator-sdk-run-bundle-config"
50-
defaultConfigMapKey = "extraFBC"
50+
defaultConfigMapKey = "extraFBC"
51+
52+
maxConfigMapSize = 1 * 1024 * 1024
5153
)
5254

5355
// FBCRegistryPod holds resources necessary for creation of a registry pod in FBC scenarios.
@@ -76,6 +78,8 @@ type FBCRegistryPod struct { //nolint:maligned
7678
// SecurityContext on the Pod
7779
SecurityContext string
7880

81+
configMapName string
82+
7983
cfg *operator.Configuration
8084
}
8185

@@ -89,6 +93,10 @@ func (f *FBCRegistryPod) init(cfg *operator.Configuration, cs *v1alpha1.CatalogS
8993
f.FBCIndexRootDir = fmt.Sprintf("/%s-configs", cs.Name)
9094
}
9195

96+
if f.configMapName == "" {
97+
f.configMapName = fmt.Sprintf("%s-configmap", cs.Name)
98+
}
99+
92100
f.cfg = cfg
93101

94102
// validate the FBCRegistryPod struct and ensure required fields are set
@@ -210,11 +218,39 @@ func (f *FBCRegistryPod) podForBundleRegistry(cs *v1alpha1.CatalogSource) (*core
210218

211219
// create ConfigMap if it does not exist,
212220
// if it exists, then update it with new content.
213-
cm, err := f.createConfigMap(cs)
221+
cms, err := f.createConfigMaps(cs)
214222
if err != nil {
215223
return nil, fmt.Errorf("configMap error: %w", err)
216224
}
217225

226+
volumes := []corev1.Volume{}
227+
volumeMounts := []corev1.VolumeMount{}
228+
229+
for _, cm := range cms {
230+
volumes = append(volumes, corev1.Volume{
231+
Name: k8sutil.TrimDNS1123Label(cm.Name + "-volume"),
232+
VolumeSource: corev1.VolumeSource{
233+
ConfigMap: &corev1.ConfigMapVolumeSource{
234+
Items: []corev1.KeyToPath{
235+
{
236+
Key: defaultConfigMapKey,
237+
Path: path.Join(cm.Name, fmt.Sprintf("%s.yaml", defaultConfigMapKey)),
238+
},
239+
},
240+
LocalObjectReference: corev1.LocalObjectReference{
241+
Name: cm.Name,
242+
},
243+
},
244+
},
245+
})
246+
247+
volumeMounts = append(volumeMounts, corev1.VolumeMount{
248+
Name: k8sutil.TrimDNS1123Label(cm.Name + "-volume"),
249+
MountPath: path.Join(f.FBCIndexRootDir, cm.Name),
250+
SubPath: cm.Name,
251+
})
252+
}
253+
218254
// make the pod definition
219255
f.pod = &corev1.Pod{
220256
ObjectMeta: metav1.ObjectMeta{
@@ -256,24 +292,7 @@ func (f *FBCRegistryPod) podForBundleRegistry(cs *v1alpha1.CatalogSource) (*core
256292
// Type: corev1.SeccompProfileTypeRuntimeDefault,
257293
// },
258294
// },
259-
Volumes: []corev1.Volume{
260-
{
261-
Name: k8sutil.TrimDNS1123Label(cm.Name + "-volume"),
262-
VolumeSource: corev1.VolumeSource{
263-
ConfigMap: &corev1.ConfigMapVolumeSource{
264-
Items: []corev1.KeyToPath{
265-
{
266-
Key: defaultConfigMapKey,
267-
Path: path.Join(defaultConfigMapName, fmt.Sprintf("%s.yaml", defaultConfigMapKey)),
268-
},
269-
},
270-
LocalObjectReference: corev1.LocalObjectReference{
271-
Name: cm.Name,
272-
},
273-
},
274-
},
275-
},
276-
},
295+
Volumes: volumes,
277296
Containers: []corev1.Container{
278297
{
279298
Name: defaultContainerName,
@@ -286,13 +305,7 @@ func (f *FBCRegistryPod) podForBundleRegistry(cs *v1alpha1.CatalogSource) (*core
286305
Ports: []corev1.ContainerPort{
287306
{Name: defaultContainerPortName, ContainerPort: f.GRPCPort},
288307
},
289-
VolumeMounts: []corev1.VolumeMount{
290-
{
291-
Name: k8sutil.TrimDNS1123Label(cm.Name + "-volume"),
292-
MountPath: path.Join(f.FBCIndexRootDir, cm.Name),
293-
SubPath: cm.Name,
294-
},
295-
},
308+
VolumeMounts: volumeMounts,
296309
SecurityContext: &corev1.SecurityContext{
297310
Privileged: pointer.Bool(false),
298311
ReadOnlyRootFilesystem: pointer.Bool(false),
@@ -315,50 +328,134 @@ const fbcCmdTemplate = `opm serve {{ .FBCIndexRootDir}} -p {{ .GRPCPort }}`
315328

316329
// createConfigMap creates a ConfigMap if it does not exist and if it does, then update it with new content.
317330
// Also, sets the owner reference by making CatalogSource the owner of ConfigMap object for cleanup purposes.
318-
func (f *FBCRegistryPod) createConfigMap(cs *v1alpha1.CatalogSource) (*corev1.ConfigMap, error) {
319-
// new ConfigMap
320-
cm := &corev1.ConfigMap{
331+
func (f *FBCRegistryPod) createConfigMaps(cs *v1alpha1.CatalogSource) ([]*corev1.ConfigMap, error) {
332+
// By default just use the partitioning logic.
333+
// If the entire FBC contents can fit in one ConfigMap it will.
334+
cms := f.partitionedConfigMaps()
335+
336+
// Loop through all the ConfigMaps and set the OwnerReference and try to create them
337+
for _, cm := range cms {
338+
// set owner reference by making catalog source the owner of ConfigMap object
339+
if err := controllerutil.SetOwnerReference(cs, cm, f.cfg.Scheme); err != nil {
340+
return nil, fmt.Errorf("set configmap %q owner reference: %v", cm.GetName(), err)
341+
}
342+
343+
err := f.createOrUpdateConfigMap(cm)
344+
if err != nil {
345+
return nil, err
346+
}
347+
}
348+
349+
return cms, nil
350+
}
351+
352+
// partitionedConfigMaps will create and return a list of *corev1.ConfigMap
353+
// that represents all the ConfigMaps that will need to be created to
354+
// properly have all the FBC contents rendered in the registry pod.
355+
func (f *FBCRegistryPod) partitionedConfigMaps() []*corev1.ConfigMap {
356+
// Split on the YAML separator `---`
357+
yamlDefs := strings.Split(f.FBCContent, "---")[1:]
358+
configMaps := []*corev1.ConfigMap{}
359+
360+
// Keep the number of ConfigMaps that are created to a minimum by
361+
// stuffing them as full as possible.
362+
partitionCount := 1
363+
cm := f.makeBaseConfigMap()
364+
// for each chunk of yaml see if it can be added to the ConfigMap partition
365+
for _, yamlDef := range yamlDefs {
366+
// If the ConfigMap has data then lets attempt to add to it
367+
if len(cm.Data) != 0 {
368+
// Create a copy to use to verify that adding the data doesn't
369+
// exceed the max ConfigMap size of 1 MiB.
370+
tempCm := cm.DeepCopy()
371+
tempCm.Data[defaultConfigMapKey] = tempCm.Data[defaultConfigMapKey] + "\n---\n" + yamlDef
372+
373+
// if it would be too large adding the data then partition it.
374+
if tempCm.Size() >= maxConfigMapSize {
375+
// Set the ConfigMap name based on the partition it is
376+
cm.SetName(fmt.Sprintf("%s-partition-%d", f.configMapName, partitionCount))
377+
// Increase the partition count
378+
partitionCount++
379+
// Add the ConfigMap to the list of ConfigMaps
380+
configMaps = append(configMaps, cm.DeepCopy())
381+
382+
// Create a new ConfigMap
383+
cm = f.makeBaseConfigMap()
384+
// Since adding this data would have made the previous
385+
// ConfigMap too large, add it to this new one.
386+
// No chunk of YAML from the bundle should cause
387+
// the ConfigMap size to exceed 1 MiB and if
388+
// somehow it does then there is a problem with the
389+
// YAML itself. We can't reasonably break it up smaller
390+
// since it is a single object.
391+
cm.Data[defaultConfigMapKey] = yamlDef
392+
} else {
393+
// if adding the data to the ConfigMap
394+
// doesn't make the ConfigMap exceed the
395+
// size limit then actually add it.
396+
cm.Data = tempCm.Data
397+
}
398+
} else {
399+
// If there is no data in the ConfigMap
400+
// then this is the first pass. Since it is
401+
// the first pass go ahead and add the data.
402+
cm.Data[defaultConfigMapKey] = yamlDef
403+
}
404+
}
405+
406+
// if there aren't as many ConfigMaps as partitions AND the unadded ConfigMap has data
407+
// then add it to the list of ConfigMaps. This is so we don't miss adding a ConfigMap
408+
// after the above loop completes.
409+
if len(configMaps) != partitionCount && len(cm.Data) != 0 {
410+
cm.SetName(fmt.Sprintf("%s-partition-%d", f.configMapName, partitionCount))
411+
configMaps = append(configMaps, cm.DeepCopy())
412+
}
413+
414+
return configMaps
415+
}
416+
417+
// makeBaseConfigMap will return the base *corev1.ConfigMap
418+
// definition that is used by various functions when creating a ConfigMap.
419+
func (f *FBCRegistryPod) makeBaseConfigMap() *corev1.ConfigMap {
420+
return &corev1.ConfigMap{
321421
TypeMeta: metav1.TypeMeta{
322422
APIVersion: corev1.SchemeGroupVersion.String(),
323423
Kind: "ConfigMap",
324424
},
325425
ObjectMeta: metav1.ObjectMeta{
326-
Name: defaultConfigMapName,
327426
Namespace: f.cfg.Namespace,
328427
},
329-
Data: map[string]string{
330-
defaultConfigMapKey: f.FBCContent,
331-
},
332-
}
333-
334-
// set owner reference by making catalog source the owner of ConfigMap object
335-
if err := controllerutil.SetOwnerReference(cs, cm, f.cfg.Scheme); err != nil {
336-
return nil, fmt.Errorf("set configmap %q owner reference: %v", cm.GetName(), err)
428+
Data: map[string]string{},
337429
}
430+
}
338431

432+
// createOrUpdateConfigMap will create a ConfigMap if it doesn't exist or
433+
// update it if it already exists.
434+
func (f *FBCRegistryPod) createOrUpdateConfigMap(cm *corev1.ConfigMap) error {
339435
cmKey := types.NamespacedName{
340-
Namespace: f.cfg.Namespace,
341-
Name: defaultConfigMapName,
436+
Namespace: cm.GetNamespace(),
437+
Name: cm.GetName(),
342438
}
343439

344440
// create a ConfigMap if it does not exist;
345441
// update it with new data if it already exists.
346442
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
347-
err := f.cfg.Client.Get(context.TODO(), cmKey, cm)
443+
tempCm := &corev1.ConfigMap{}
444+
err := f.cfg.Client.Get(context.TODO(), cmKey, tempCm)
348445
if apierrors.IsNotFound(err) {
349446
if err := f.cfg.Client.Create(context.TODO(), cm); err != nil {
350447
return fmt.Errorf("error creating ConfigMap: %w", err)
351448
}
449+
return nil
352450
}
353451
// update ConfigMap with new FBCContent
354-
cm.Data = map[string]string{defaultConfigMapKey: f.FBCContent}
355-
return f.cfg.Client.Update(context.TODO(), cm)
452+
tempCm.Data = cm.Data
453+
return f.cfg.Client.Update(context.TODO(), tempCm)
356454
}); err != nil {
357-
return nil, fmt.Errorf("error updating ConfigMap: %w", err)
455+
return fmt.Errorf("error updating ConfigMap: %w", err)
358456
}
359457

360-
return cm, nil
361-
458+
return nil
362459
}
363460

364461
// getContainerCmd uses templating to construct the container command

0 commit comments

Comments
 (0)