Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

controller/registry: implement content extraction for catalog sources #3029

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion deploy/chart/crds/0000_50_olm_00-catalogsources.crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,18 @@ spec:
topologyKey:
description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.
type: string
extractContent:
description: ExtractContent configures the gRPC catalog Pod to extract catalog metadata from the provided index image and use a well-known version of the `opm` server to expose it. The catalog index image that this CatalogSource is configured to use *must* be using the file-based catalogs in order to utilize this feature.
type: object
properties:
cacheDir:
description: CacheDir is the directory storing the pre-calculated API cache.
type: string
catalogDir:
description: CatalogDir is the directory storing the file-based catalog contents.
type: string
memoryTarget:
description: "MemoryTarget configures the $GOMEMLIMIT value for the gRPC catalog Pod. This is a soft memory limit for the server, which the runtime will attempt to meet but makes no guarantees that it will do so. If this value is set, the Pod will have the following modifications made to the container running the server: - the $GOMEMLIMIT environment variable will be set to this value in bytes - the memory request will be set to this value - the memory limit will be set to 200% of this value \n This field should be set if it's desired to reduce the footprint of a catalog server as much as possible, or if a catalog being served is very large and needs more than the default allocation. If your index image has a file- system cache, determine a good approximation for this value by doubling the size of the package cache at /tmp/cache/cache/packages.json in the index image. \n This field is best-effort; if unset, no default will be used and no Pod memory limit or $GOMEMLIMIT value will be set."
description: "MemoryTarget configures the $GOMEMLIMIT value for the gRPC catalog Pod. This is a soft memory limit for the server, which the runtime will attempt to meet but makes no guarantees that it will do so. If this value is set, the Pod will have the following modifications made to the container running the server: - the $GOMEMLIMIT environment variable will be set to this value in bytes - the memory request will be set to this value \n This field should be set if it's desired to reduce the footprint of a catalog server as much as possible, or if a catalog being served is very large and needs more than the default allocation. If your index image has a file- system cache, determine a good approximation for this value by doubling the size of the package cache at /tmp/cache/cache/packages.json in the index image. \n This field is best-effort; if unset, no default will be used and no Pod memory limit or $GOMEMLIMIT value will be set."
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
anyOf:
- type: integer
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/onsi/gomega v1.27.7
github.com/openshift/api v3.9.0+incompatible
github.com/openshift/client-go v0.0.0-20220525160904-9e1acff93e4a
github.com/operator-framework/api v0.17.8-0.20230803152844-704ae942c4a9
github.com/operator-framework/api v0.17.8-0.20230907172037-bb012a3b9b25
github.com/operator-framework/operator-registry v1.29.0
github.com/otiai10/copy v1.2.0
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,8 @@ github.com/openshift/api v0.0.0-20221021112143-4226c2167e40 h1:PxjGCA72RtsdHWToZ
github.com/openshift/api v0.0.0-20221021112143-4226c2167e40/go.mod h1:aQ6LDasvHMvHZXqLHnX2GRmnfTWCF/iIwz8EMTTIE9A=
github.com/openshift/client-go v0.0.0-20221019143426-16aed247da5c h1:CV76yFOTXmq9VciBR3Bve5ZWzSxdft7gaMVB3kS0rwg=
github.com/openshift/client-go v0.0.0-20221019143426-16aed247da5c/go.mod h1:lFMO8mLHXWFzSdYvGNo8ivF9SfF6zInA8ZGw4phRnUE=
github.com/operator-framework/api v0.17.8-0.20230803152844-704ae942c4a9 h1:0rOIH8rbWsjhiZb14DD7V8Ohl0CjPkTYIbm/0G/nv1s=
github.com/operator-framework/api v0.17.8-0.20230803152844-704ae942c4a9/go.mod h1:lnurXgadLnoZ7pufKMHkErr2BVOIZSpHtvEkHBcKvdk=
github.com/operator-framework/api v0.17.8-0.20230907172037-bb012a3b9b25 h1:nY4VZQbe/gtCd+7MZK+ai6N0JMgJzb/S89uOhB61IbY=
github.com/operator-framework/api v0.17.8-0.20230907172037-bb012a3b9b25/go.mod h1:Wbg136l1Po6zqG2QcTN1QZ8dbT4BQvNlQDM9tmQYvz0=
github.com/operator-framework/operator-registry v1.29.0 h1:HMmVTiuOAGoHLzYqR9Lr2QSOqbVzA50++ojNl2mu9f4=
github.com/operator-framework/operator-registry v1.29.0/go.mod h1:4rVQu/cOuCtVt3JzKsAmwyq2lsiu9uPaH9nYNfnqj9o=
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/operators/catalog/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
op.sources = grpc.NewSourceStore(logger, 10*time.Second, 10*time.Minute, op.syncSourceState)
op.sourceInvalidator = resolver.SourceProviderFromRegistryClientProvider(op.sources, logger)
resolverSourceProvider := NewOperatorGroupToggleSourceProvider(op.sourceInvalidator, logger, op.lister.OperatorsV1().OperatorGroupLister())
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now, ssaClient, workloadUserID)
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now, ssaClient, workloadUserID, opmImage)
res := resolver.NewOperatorStepResolver(lister, crClient, operatorNamespace, resolverSourceProvider, logger)
op.resolver = resolver.NewInstrumentedResolver(res, metrics.RegisterDependencyResolutionSuccess, metrics.RegisterDependencyResolutionFailure)

Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/operators/catalog/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1773,7 +1773,7 @@ func NewFakeOperator(ctx context.Context, namespace string, namespaces []string,
}
applier := controllerclient.NewFakeApplier(s, "testowner")

op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now, applier, 1001)
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now, applier, 1001, "")
}

op.RunInformers(ctx)
Expand Down Expand Up @@ -1929,7 +1929,7 @@ func toManifest(t *testing.T, obj runtime.Object) string {
}

func pod(s v1alpha1.CatalogSource) *corev1.Pod {
pod := reconciler.Pod(&s, "registry-server", s.Spec.Image, s.GetName(), s.GetLabels(), s.GetAnnotations(), 5, 10, 1001)
pod := reconciler.Pod(&s, "registry-server", "central-opm", s.Spec.Image, s.GetName(), s.GetLabels(), s.GetAnnotations(), 5, 10, 1001)
ownerutil.AddOwner(pod, &s, false, true)
return pod
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/registry/reconciler/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (s *configMapCatalogSourceDecorator) Service() *corev1.Service {
}

func (s *configMapCatalogSourceDecorator) Pod(image string) *corev1.Pod {
pod := Pod(s.CatalogSource, "configmap-registry-server", image, "", s.Labels(), s.Annotations(), 5, 5, s.runAsUser)
pod := Pod(s.CatalogSource, "configmap-registry-server", "", image, "", s.Labels(), s.Annotations(), 5, 5, s.runAsUser)
pod.Spec.ServiceAccountName = s.GetName() + ConfigMapServerPostfix
pod.Spec.Containers[0].Command = []string{"configmap-server", "-c", s.Spec.ConfigMap, "-n", s.GetNamespace()}
ownerutil.AddOwner(pod, s.CatalogSource, false, true)
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/registry/reconciler/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func objectsForCatalogSource(catsrc *v1alpha1.CatalogSource) []runtime.Object {
)
case v1alpha1.SourceTypeGrpc:
if catsrc.Spec.Image != "" {
decorated := grpcCatalogSourceDecorator{catsrc, runAsUser}
decorated := grpcCatalogSourceDecorator{CatalogSource: catsrc, createPodAsUser: runAsUser, opmImage: ""}
objs = clientfake.AddSimpleGeneratedNames(
decorated.Pod(catsrc.GetName()),
decorated.Service(),
Expand Down
19 changes: 15 additions & 4 deletions pkg/controller/registry/reconciler/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
type grpcCatalogSourceDecorator struct {
*v1alpha1.CatalogSource
createPodAsUser int64
opmImage string
}

type UpdateNotReadyErr struct {
Expand Down Expand Up @@ -128,7 +129,7 @@ func (s *grpcCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount {
}

func (s *grpcCatalogSourceDecorator) Pod(saName string) *corev1.Pod {
pod := Pod(s.CatalogSource, "registry-server", s.Spec.Image, saName, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser)
pod := Pod(s.CatalogSource, "registry-server", s.opmImage, s.Spec.Image, saName, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser)
ownerutil.AddOwner(pod, s.CatalogSource, false, true)
return pod
}
Expand All @@ -139,6 +140,7 @@ type GrpcRegistryReconciler struct {
OpClient operatorclient.ClientInterface
SSAClient *controllerclient.ServerSideApplier
createPodAsUser int64
opmImage string
}

var _ RegistryReconciler = &GrpcRegistryReconciler{}
Expand Down Expand Up @@ -196,16 +198,25 @@ func (c *GrpcRegistryReconciler) currentPodsWithCorrectImageAndSpec(source grpcC
found := []*corev1.Pod{}
newPod := source.Pod(saName)
for _, p := range pods {
if p.Spec.Containers[0].Image == source.Spec.Image && podHashMatch(p, newPod) {
if correctImages(source, p) && podHashMatch(p, newPod) {
joelanford marked this conversation as resolved.
Show resolved Hide resolved
found = append(found, p)
}
}
return found
}

func correctImages(source grpcCatalogSourceDecorator, pod *corev1.Pod) bool {
if source.CatalogSource.Spec.GrpcPodConfig != nil && source.CatalogSource.Spec.GrpcPodConfig.ExtractContent != nil {
return pod.Spec.InitContainers[0].Image == source.opmImage &&
pod.Spec.InitContainers[1].Image == source.CatalogSource.Spec.Image &&
pod.Spec.Containers[0].Image == source.opmImage
}
return pod.Spec.Containers[0].Image == source.CatalogSource.Spec.Image
}

// EnsureRegistryServer ensures that all components of registry server are up to date.
func (c *GrpcRegistryReconciler) EnsureRegistryServer(catalogSource *v1alpha1.CatalogSource) error {
source := grpcCatalogSourceDecorator{catalogSource, c.createPodAsUser}
source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage}

// if service status is nil, we force create every object to ensure they're created the first time
overwrite := source.Status.RegistryServiceStatus == nil || !isRegistryServiceStatusValid(&source)
Expand Down Expand Up @@ -454,7 +465,7 @@ func (c *GrpcRegistryReconciler) removePods(pods []*corev1.Pod, namespace string

// CheckRegistryServer returns true if the given CatalogSource is considered healthy; false otherwise.
func (c *GrpcRegistryReconciler) CheckRegistryServer(catalogSource *v1alpha1.CatalogSource) (healthy bool, err error) {
source := grpcCatalogSourceDecorator{catalogSource, c.createPodAsUser}
source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage}
// Check on registry resources
// TODO: add gRPC health check
if len(c.currentPodsWithCorrectImageAndSpec(source, source.ServiceAccount().GetName())) < 1 ||
Expand Down
9 changes: 7 additions & 2 deletions pkg/controller/registry/reconciler/grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package reconciler

import (
"context"
"fmt"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -355,7 +357,7 @@ func TestGrpcRegistryReconciler(t *testing.T) {
}

// Check for resource existence
decorated := grpcCatalogSourceDecorator{tt.in.catsrc, runAsUser}
decorated := grpcCatalogSourceDecorator{CatalogSource: tt.in.catsrc, createPodAsUser: runAsUser}
pod := decorated.Pod(tt.in.catsrc.GetName())
service := decorated.Service()
sa := decorated.ServiceAccount()
Expand All @@ -367,6 +369,9 @@ func TestGrpcRegistryReconciler(t *testing.T) {
case *GrpcRegistryReconciler:
// Should be created by a GrpcRegistryReconciler
require.NoError(t, podErr)
if diff := cmp.Diff(outPods.Items, []corev1.Pod{*pod}); diff != "" {
fmt.Printf("incorrect pods: %s\n", diff)
}
require.Len(t, outPods.Items, 1)
outPod := outPods.Items[0]
require.Equal(t, pod.GetGenerateName(), outPod.GetGenerateName())
Expand Down Expand Up @@ -445,7 +450,7 @@ func TestRegistryPodPriorityClass(t *testing.T) {
require.NoError(t, err)

// Check for resource existence
decorated := grpcCatalogSourceDecorator{tt.in.catsrc, runAsUser}
decorated := grpcCatalogSourceDecorator{CatalogSource: tt.in.catsrc, createPodAsUser: runAsUser}
pod := decorated.Pod(tt.in.catsrc.GetName())
listOptions := metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{CatalogSourceLabelKey: tt.in.catsrc.GetName()}).String()}
outPods, podErr := client.KubernetesInterface().CoreV1().Pods(pod.GetNamespace()).List(context.TODO(), listOptions)
Expand Down
60 changes: 58 additions & 2 deletions pkg/controller/registry/reconciler/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package reconciler
import (
"fmt"
"hash/fnv"
"path/filepath"

"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -64,6 +65,7 @@ type registryReconcilerFactory struct {
ConfigMapServerImage string
SSAClient *controllerclient.ServerSideApplier
createPodAsUser int64
opmImage string
}

// ReconcilerForSource returns a RegistryReconciler based on the configuration of the given CatalogSource.
Expand All @@ -86,6 +88,7 @@ func (r *registryReconcilerFactory) ReconcilerForSource(source *operatorsv1alpha
OpClient: r.OpClient,
SSAClient: r.SSAClient,
createPodAsUser: r.createPodAsUser,
opmImage: r.opmImage,
}
} else if source.Spec.Address != "" {
return &GrpcAddressRegistryReconciler{
Expand All @@ -97,18 +100,19 @@ func (r *registryReconcilerFactory) ReconcilerForSource(source *operatorsv1alpha
}

// NewRegistryReconcilerFactory returns an initialized RegistryReconcilerFactory.
func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient operatorclient.ClientInterface, configMapServerImage string, now nowFunc, ssaClient *controllerclient.ServerSideApplier, createPodAsUser int64) RegistryReconcilerFactory {
func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient operatorclient.ClientInterface, configMapServerImage string, now nowFunc, ssaClient *controllerclient.ServerSideApplier, createPodAsUser int64, opmImage string) RegistryReconcilerFactory {
return &registryReconcilerFactory{
now: now,
Lister: lister,
OpClient: opClient,
ConfigMapServerImage: configMapServerImage,
SSAClient: ssaClient,
createPodAsUser: createPodAsUser,
opmImage: opmImage,
}
}

func Pod(source *operatorsv1alpha1.CatalogSource, name string, img string, saName string, labels map[string]string, annotations map[string]string, readinessDelay int32, livenessDelay int32, runAsUser int64) *corev1.Pod {
func Pod(source *operatorsv1alpha1.CatalogSource, name, opmImg, img, saName string, labels, annotations map[string]string, readinessDelay, livenessDelay int32, runAsUser int64) *corev1.Pod {
// make a copy of the labels and annotations to avoid mutating the input parameters
podLabels := make(map[string]string)
podAnnotations := make(map[string]string)
Expand Down Expand Up @@ -236,6 +240,58 @@ func Pod(source *operatorsv1alpha1.CatalogSource, name string, img string, saNam
Value: grpcPodConfig.MemoryTarget.String() + "B", // k8s resources use Mi, GOMEMLIMIT wants MiB
})
}

// Reconfigure pod to extract content
if grpcPodConfig.ExtractContent != nil {
pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
Name: "utilities",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}, corev1.Volume{
Name: "catalog-content",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
})
const utilitiesPath = "/utilities"
utilitiesVolumeMount := corev1.VolumeMount{
Name: "utilities",
MountPath: utilitiesPath,
}
const catalogPath = "/extracted-catalog"
contentVolumeMount := corev1.VolumeMount{
Name: "catalog-content",
MountPath: catalogPath,
}
pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{
Name: "extract-utilities",
Image: opmImg,
Command: []string{"sh", "-c"},
Args: []string{fmt.Sprintf("cp $( command -v sh ) %s/sh && cp $( command -v cp ) %s/cp",
utilitiesPath, utilitiesPath,
)},
VolumeMounts: []corev1.VolumeMount{utilitiesVolumeMount},
}, corev1.Container{
Name: "extract-content",
Image: img,
Command: []string{utilitiesPath + "/sh", "-c"},
Args: []string{fmt.Sprintf("%s/cp -r %s %s/catalog && %s/cp -r %s %s/cache",
utilitiesPath, grpcPodConfig.ExtractContent.CatalogDir, catalogPath,
utilitiesPath, grpcPodConfig.ExtractContent.CacheDir, catalogPath,
)},
VolumeMounts: []corev1.VolumeMount{utilitiesVolumeMount, contentVolumeMount},
})

pod.Spec.Containers[0].Image = opmImg
pod.Spec.Containers[0].Command = []string{"/bin/opm"}
pod.Spec.Containers[0].Args = []string{
"serve",
filepath.Join(catalogPath, "catalog"),
"--cache-dir=" + filepath.Join(catalogPath, "cache"),
}
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, contentVolumeMount)
}
}

// Set priorityclass if its annotation exists
Expand Down
Loading