Skip to content

Commit

Permalink
(refactor) Move csv set and replace to a package
Browse files Browse the repository at this point in the history
Promote csv set and replace logic within olm operator to a reusable
package in lib so that it can be reused by other units.

Note:
- No behavior of the operator has changed as part of this refactor.
  • Loading branch information
tkashem committed May 15, 2019
1 parent 3b9d943 commit f55cd1e
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 46 deletions.
77 changes: 31 additions & 46 deletions pkg/controller/operators/olm/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
csvutility "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/csv"
)

var (
Expand Down Expand Up @@ -62,6 +63,8 @@ type Operator struct {
gcQueueIndexer *queueinformer.QueueIndexer
apiLabeler labeler.Labeler
csvIndexers map[string]cache.Indexer
csvSetGenerator csvutility.SetGenerator
csvReplaceFinder csvutility.ReplaceFinder
}

func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient operatorclient.ClientInterface, strategyResolver install.StrategyResolverInterface, wakeupInterval time.Duration, namespaces []string) (*Operator, error) {
Expand All @@ -81,17 +84,24 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o
return nil, err
}

lister := operatorlister.NewLister()
csvSetGenerator := csvutility.NewSetGenerator(logger, lister)

csvReplaceFinder := csvutility.NewReplaceFinder(logger, crClient)

op := &Operator{
Operator: queueOperator,
csvQueueSet: queueinformer.NewEmptyResourceQueueSet(),
ogQueueSet: queueinformer.NewEmptyResourceQueueSet(),
client: crClient,
resolver: strategyResolver,
apiReconciler: resolver.APIIntersectionReconcileFunc(resolver.ReconcileAPIIntersection),
lister: operatorlister.NewLister(),
recorder: eventRecorder,
apiLabeler: labeler.Func(resolver.LabelSetsFor),
csvIndexers: map[string]cache.Indexer{},
Operator: queueOperator,
csvQueueSet: queueinformer.NewEmptyResourceQueueSet(),
ogQueueSet: queueinformer.NewEmptyResourceQueueSet(),
client: crClient,
resolver: strategyResolver,
apiReconciler: resolver.APIIntersectionReconcileFunc(resolver.ReconcileAPIIntersection),
lister: lister,
recorder: eventRecorder,
apiLabeler: labeler.Func(resolver.LabelSetsFor),
csvIndexers: map[string]cache.Indexer{},
csvSetGenerator: csvSetGenerator,
csvReplaceFinder: csvReplaceFinder,
}

// Set up RBAC informers
Expand Down Expand Up @@ -307,6 +317,14 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o
return op, nil
}

func (a *Operator) GetCSVSetGenerator() csvutility.SetGenerator {
return a.csvSetGenerator
}

func (a *Operator) GetReplaceFinder() csvutility.ReplaceFinder {
return a.csvReplaceFinder
}

func (a *Operator) syncObject(obj interface{}) (syncError error) {
// Assert as metav1.Object
metaObj, ok := obj.(metav1.Object)
Expand Down Expand Up @@ -1113,21 +1131,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v

// csvSet gathers all CSVs in the given namespace into a map keyed by CSV name; if metav1.NamespaceAll gets the set across all namespaces
func (a *Operator) csvSet(namespace string, phase v1alpha1.ClusterServiceVersionPhase) map[string]*v1alpha1.ClusterServiceVersion {
csvsInNamespace, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(namespace).List(labels.Everything())

if err != nil {
a.Log.Warnf("could not list CSVs while constructing CSV set")
return nil
}

csvs := make(map[string]*v1alpha1.ClusterServiceVersion, len(csvsInNamespace))
for _, csv := range csvsInNamespace {
if phase != v1alpha1.CSVPhaseAny && csv.Status.Phase != phase {
continue
}
csvs[csv.Name] = csv.DeepCopy()
}
return csvs
return a.csvSetGenerator.WithNamespace(namespace, phase)
}

// checkReplacementsAndUpdateStatus returns an error if we can find a newer CSV and sets the status if so
Expand Down Expand Up @@ -1312,30 +1316,11 @@ func (a *Operator) apiServiceOwnerConflicts(csv *v1alpha1.ClusterServiceVersion)
}

func (a *Operator) isBeingReplaced(in *v1alpha1.ClusterServiceVersion, csvsInNamespace map[string]*v1alpha1.ClusterServiceVersion) (replacedBy *v1alpha1.ClusterServiceVersion) {
for _, csv := range csvsInNamespace {
a.Log.Infof("checking %s", csv.GetName())
if csv.Spec.Replaces == in.GetName() {
a.Log.Infof("%s replaced by %s", in.GetName(), csv.GetName())
replacedBy = csv.DeepCopy()
return
}
}
return
return a.csvReplaceFinder.IsBeingReplaced(in, csvsInNamespace)
}

func (a *Operator) isReplacing(in *v1alpha1.ClusterServiceVersion) *v1alpha1.ClusterServiceVersion {
a.Log.Debugf("checking if csv is replacing an older version")
if in.Spec.Replaces == "" {
return nil
}

// using the client instead of a lister; missing an object because of a cache sync can cause upgrades to fail
previous, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(in.GetNamespace()).Get(in.Spec.Replaces, metav1.GetOptions{})
if err != nil {
a.Log.WithField("replacing", in.Spec.Replaces).WithError(err).Debugf("unable to get previous csv")
return nil
}
return previous
return a.csvReplaceFinder.IsReplacing(in)
}

func (a *Operator) handleDeletion(obj interface{}) {
Expand Down
55 changes: 55 additions & 0 deletions pkg/lib/csv/csvset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package csv

import (
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/labels"
)

// NewSetGenerator returns a new instance of SetGenerator.
func NewSetGenerator(logger *logrus.Logger, lister operatorlister.OperatorLister) SetGenerator {
return &csvSet{
logger: logger,
lister: lister,
}
}

// SetGenerator is an interface that returns a map of ClusterServiceVersion
// objects that match a certain set of criteria.
//
// SetGenerator gathers all CSV(s) in the given namespace into a map keyed by
// CSV name; if metav1.NamespaceAll gets the set across all namespaces
type SetGenerator interface {
WithNamespace(namespace string, phase v1alpha1.ClusterServiceVersionPhase) map[string]*v1alpha1.ClusterServiceVersion
}

type csvSet struct {
lister operatorlister.OperatorLister
logger *logrus.Logger
}

// WithNamespace returns all ClusterServiceVersion resource(s) that matches the
// specified phase from a given namespace.
func (s *csvSet) WithNamespace(namespace string, phase v1alpha1.ClusterServiceVersionPhase) map[string]*v1alpha1.ClusterServiceVersion {
return s.with(namespace, phase, labels.Everything())
}

func (s *csvSet) with(namespace string, phase v1alpha1.ClusterServiceVersionPhase, selector labels.Selector) map[string]*v1alpha1.ClusterServiceVersion {
csvsInNamespace, err := s.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(namespace).List(selector)

if err != nil {
s.logger.Warnf("could not list CSVs while constructing CSV set")
return nil
}

csvs := make(map[string]*v1alpha1.ClusterServiceVersion, len(csvsInNamespace))
for _, csv := range csvsInNamespace {
if phase != v1alpha1.CSVPhaseAny && csv.Status.Phase != phase {
continue
}
csvs[csv.Name] = csv.DeepCopy()
}

return csvs
}
65 changes: 65 additions & 0 deletions pkg/lib/csv/replace_finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package csv

import (
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// NewReplaceFinder returns an instance of ReplaceFinder
func NewReplaceFinder(logger *logrus.Logger, client versioned.Interface) ReplaceFinder {
return &replace{
logger: logger,
client: client,
}
}

// ReplaceFinder is an interface that finds the next or previous
// ClusterServiceVersion object in the upgrade path for a given CSV.
type ReplaceFinder interface {
IsBeingReplaced(in *v1alpha1.ClusterServiceVersion, csvsInNamespace map[string]*v1alpha1.ClusterServiceVersion) (replacedBy *v1alpha1.ClusterServiceVersion)
IsReplacing(in *v1alpha1.ClusterServiceVersion) *v1alpha1.ClusterServiceVersion
}

type replace struct {
logger *logrus.Logger
client versioned.Interface
}

// IsBeingReplaced returns the corresponding ClusterServiceVersion object that
// is replacing the given CSV specified.
//
// If the corresponding ClusterServiceVersion is not found nil is returned.
func (r *replace) IsBeingReplaced(in *v1alpha1.ClusterServiceVersion, csvsInNamespace map[string]*v1alpha1.ClusterServiceVersion) (replacedBy *v1alpha1.ClusterServiceVersion) {
for _, csv := range csvsInNamespace {
r.logger.Infof("checking %s", csv.GetName())
if csv.Spec.Replaces == in.GetName() {
r.logger.Infof("%s replaced by %s", in.GetName(), csv.GetName())
replacedBy = csv.DeepCopy()
return
}
}

return
}

// IsReplacing returns the corresponding ClusterServiceVersion object that the
// given CSV specified replaces.
//
// If the corresponding ClusterServiceVersion is not found nil is returned.
func (r *replace) IsReplacing(in *v1alpha1.ClusterServiceVersion) *v1alpha1.ClusterServiceVersion {
r.logger.Debugf("checking if csv is replacing an older version")
if in.Spec.Replaces == "" {
return nil
}

// using the client instead of a lister; missing an object because of a cache sync can cause upgrades to fail
previous, err := r.client.OperatorsV1alpha1().ClusterServiceVersions(in.GetNamespace()).Get(in.Spec.Replaces, metav1.GetOptions{})
if err != nil {
r.logger.WithField("replacing", in.Spec.Replaces).WithError(err).Debugf("unable to get previous csv")
return nil
}

return previous
}

0 comments on commit f55cd1e

Please sign in to comment.