Skip to content

Commit

Permalink
Add OLM support for the Upgradeable OperatorCondition
Browse files Browse the repository at this point in the history
OLM will check for OperatorCondition CR for OperatorUpgradeable status.
The pending CSV will not transition to Succeeded status until the operator
is upgradeable based on the OperatorCondition. If the operator doesn't use
OperatorCondition, the normal transition is expected.

Cluster Admin will be able to override the OperatorUpgradeable condition.

Add an e2e test for OperatorUpgradeable condition.

OLM will check for replacedCSV's Upgradeable condition to ensure the new CSV is
able to be upgraded. If the previous condition is False, the new CSV will stay
in Pending to prevent the deployment to be installed.

Signed-off-by: Vu Dinh <vdinh@redhat.com>
  • Loading branch information
dinhxuanvu committed Dec 4, 2020
1 parent cfda6a3 commit cb1fe22
Show file tree
Hide file tree
Showing 11 changed files with 533 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -25,7 +25,7 @@ require (
github.com/onsi/gomega v1.9.0
github.com/openshift/api v0.0.0-20200331152225-585af27e34fd
github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0
github.com/operator-framework/api v0.3.23
github.com/operator-framework/api v0.3.25
github.com/operator-framework/operator-registry v1.13.6
github.com/otiai10/copy v1.2.0
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -756,8 +756,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/operator-framework/api v0.3.7-0.20200602203552-431198de9fc2/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q=
github.com/operator-framework/api v0.3.23 h1:+QiJ0m5SjfV+iMv8uzuGac+PoJFlTaMFIN8eVccUnoY=
github.com/operator-framework/api v0.3.23/go.mod h1:GVNiB6AQucwdZz3ZFXNv9HtcLOzcFnr6O/QldzKG93g=
github.com/operator-framework/api v0.3.25 h1:d6WgHCshCffT37okVZeL+IbGlhrsHy57xdfMnopC8rI=
github.com/operator-framework/api v0.3.25/go.mod h1:GVNiB6AQucwdZz3ZFXNv9HtcLOzcFnr6O/QldzKG93g=
github.com/operator-framework/operator-registry v1.13.6 h1:h/dIjQQS7uneQNRifrSz7h0xg4Xyjg6C9f6XZofbMPg=
github.com/operator-framework/operator-registry v1.13.6/go.mod h1:YhnIzOVjRU2ZwZtzt+fjcjW8ujJaSFynBEu7QVKaSdU=
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
Expand Down
25 changes: 25 additions & 0 deletions pkg/controller/operators/olm/operator.go
Expand Up @@ -220,6 +220,22 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
return nil, err
}

// Register OperatorCondition QueueInformer
opConditionInformer := extInformerFactory.Operators().V1().OperatorConditions()
op.lister.OperatorsV1().RegisterOperatorConditionLister(namespace, opConditionInformer.Lister())
opConditionQueueInformer, err := queueinformer.NewQueueInformer(
ctx,
queueinformer.WithLogger(op.logger),
queueinformer.WithInformer(opConditionInformer.Informer()),
queueinformer.WithSyncer(k8sSyncer),
)
if err != nil {
return nil, err
}
if err := op.RegisterQueueInformer(opConditionQueueInformer); err != nil {
return nil, err
}

subInformer := extInformerFactory.Operators().V1alpha1().Subscriptions()
op.lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, subInformer.Lister())
subQueueInformer, err := queueinformer.NewQueueInformer(
Expand Down Expand Up @@ -1426,6 +1442,15 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
logger.Info("scheduling ClusterServiceVersion for requirement verification")
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "requirements not yet checked", now, a.recorder)
case v1alpha1.CSVPhasePending:
// Check previous version's Upgradeable condition
replacedCSV := a.isReplacing(out)
if replacedCSV != nil {
operatorUpgradeable, condErr := a.isOperatorUpgradeable(replacedCSV)
if !operatorUpgradeable {
out.SetPhaseWithEventIfChanged(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonOperatorConditionNotUpgradeable, fmt.Sprintf("operator is not upgradeable: %s", condErr), now, a.recorder)
return
}
}
met, statuses, err := a.requirementAndPermissionStatus(out)
if err != nil {
// TODO: account for Bad Rule as well
Expand Down
45 changes: 45 additions & 0 deletions pkg/controller/operators/olm/operatorconditions.go
@@ -0,0 +1,45 @@
package olm

import (
"fmt"

k8serrors "k8s.io/apimachinery/pkg/api/errors"
meta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
"github.com/operator-framework/api/pkg/operators/v1alpha1"
)

func (a *Operator) isOperatorUpgradeable(csv *v1alpha1.ClusterServiceVersion) (bool, error) {
if csv == nil {
return false, fmt.Errorf("CSV is invalid")
}

cond, err := a.lister.OperatorsV1().OperatorConditionLister().OperatorConditions(csv.GetNamespace()).Get(csv.GetName())
if err != nil {
if k8serrors.IsNotFound(err) {
return true, nil
}
return false, err
}

// Check condition overrides
for _, override := range cond.Spec.Overrides {
if override.Type == operatorsv1.OperatorUpgradeable {
if override.Status == metav1.ConditionTrue {
return true, nil
}
return false, fmt.Errorf("The operator is not upgradeable: %s", override.Message)
}
}

// Check for OperatorUpgradeable condition status
if c := meta.FindStatusCondition(cond.Status.Conditions, operatorsv1.OperatorUpgradeable); c != nil {
if c.Status == metav1.ConditionFalse {
return false, fmt.Errorf("The operator is not upgradeable: %s", c.Message)
}
}

return true, nil
}
8 changes: 6 additions & 2 deletions pkg/lib/operatorlister/lister.go
Expand Up @@ -102,8 +102,10 @@ type OperatorsV1alpha1Lister interface {
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . OperatorsV1Lister
type OperatorsV1Lister interface {
RegisterOperatorGroupLister(namespace string, lister v1.OperatorGroupLister)
RegisterOperatorConditionLister(namespace string, lister v1.OperatorConditionLister)

OperatorGroupLister() v1.OperatorGroupLister
OperatorConditionLister() v1.OperatorConditionLister
}

type appsV1Lister struct {
Expand Down Expand Up @@ -189,12 +191,14 @@ func newOperatorsV1alpha1Lister() *operatorsV1alpha1Lister {
}

type operatorsV1Lister struct {
operatorGroupLister *UnionOperatorGroupLister
operatorGroupLister *UnionOperatorGroupLister
operatorConditionLister *UnionOperatorConditionLister
}

func newOperatorsV1Lister() *operatorsV1Lister {
return &operatorsV1Lister{
operatorGroupLister: &UnionOperatorGroupLister{},
operatorGroupLister: &UnionOperatorGroupLister{},
operatorConditionLister: &UnionOperatorConditionLister{},
}
}

Expand Down
96 changes: 96 additions & 0 deletions pkg/lib/operatorlister/operatorcondition.go
@@ -0,0 +1,96 @@
package operatorlister

import (
"fmt"
"sync"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"

v1 "github.com/operator-framework/api/pkg/operators/v1"
listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1"
)

type UnionOperatorConditionLister struct {
opConditionListers map[string]listers.OperatorConditionLister
opConditionLock sync.RWMutex
}

// List lists all OperatorConditions in the indexer.
func (uol *UnionOperatorConditionLister) List(selector labels.Selector) (ret []*v1.OperatorCondition, err error) {
uol.opConditionLock.RLock()
defer uol.opConditionLock.RUnlock()

set := make(map[types.UID]*v1.OperatorCondition)
for _, cl := range uol.opConditionListers {
csvs, err := cl.List(selector)
if err != nil {
return nil, err
}

for _, csv := range csvs {
set[csv.GetUID()] = csv
}
}

for _, csv := range set {
ret = append(ret, csv)
}

return
}

// OperatorConditions returns an object that can list and get OperatorConditions.
func (uol *UnionOperatorConditionLister) OperatorConditions(namespace string) listers.OperatorConditionNamespaceLister {
uol.opConditionLock.RLock()
defer uol.opConditionLock.RUnlock()

// Check for specific namespace listers
if cl, ok := uol.opConditionListers[namespace]; ok {
return cl.OperatorConditions(namespace)
}

// Check for any namespace-all listers
if cl, ok := uol.opConditionListers[metav1.NamespaceAll]; ok {
return cl.OperatorConditions(namespace)
}

return &NullOperatorConditionNamespaceLister{}
}

func (uol *UnionOperatorConditionLister) RegisterOperatorConditionLister(namespace string, lister listers.OperatorConditionLister) {
uol.opConditionLock.Lock()
defer uol.opConditionLock.Unlock()

if uol.opConditionListers == nil {
uol.opConditionListers = make(map[string]listers.OperatorConditionLister)
}

uol.opConditionListers[namespace] = lister
}

func (l *operatorsV1Lister) RegisterOperatorConditionLister(namespace string, lister listers.OperatorConditionLister) {
l.operatorConditionLister.RegisterOperatorConditionLister(namespace, lister)
}

func (l *operatorsV1Lister) OperatorConditionLister() listers.OperatorConditionLister {
return l.operatorConditionLister
}

// NullOperatorConditionNamespaceLister is an implementation of a null OperatorConditionNamespaceLister. It is
// used to prevent nil pointers when no OperatorConditionNamespaceLister has been registered for a given
// namespace.
type NullOperatorConditionNamespaceLister struct {
listers.OperatorConditionNamespaceLister
}

// List returns nil and an error explaining that this is a NullOperatorConditionNamespaceLister.
func (n *NullOperatorConditionNamespaceLister) List(selector labels.Selector) (ret []*v1.OperatorCondition, err error) {
return nil, fmt.Errorf("cannot list OperatorConditions with a NullOperatorConditionNamespaceLister")
}

// Get returns nil and an error explaining that this is a NullOperatorConditionNamespaceLister.
func (n *NullOperatorConditionNamespaceLister) Get(name string) (*v1.OperatorCondition, error) {
return nil, fmt.Errorf("cannot get OperatorCondition with a NullOperatorConditionNamespaceLister")
}
104 changes: 104 additions & 0 deletions pkg/lib/operatorlister/operatorlisterfakes/fake_operators_v1lister.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cb1fe22

Please sign in to comment.