Skip to content

Commit abaf218

Browse files
committed
Add OLM support for the Upgradeable OperatorCondition
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 <[email protected]>
1 parent cfda6a3 commit abaf218

File tree

11 files changed

+532
-6
lines changed

11 files changed

+532
-6
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require (
2525
github.com/onsi/gomega v1.9.0
2626
github.com/openshift/api v0.0.0-20200331152225-585af27e34fd
2727
github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0
28-
github.com/operator-framework/api v0.3.23
28+
github.com/operator-framework/api v0.3.25
2929
github.com/operator-framework/operator-registry v1.13.6
3030
github.com/otiai10/copy v1.2.0
3131
github.com/pkg/errors v0.9.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -756,8 +756,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
756756
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
757757
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
758758
github.com/operator-framework/api v0.3.7-0.20200602203552-431198de9fc2/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q=
759-
github.com/operator-framework/api v0.3.23 h1:+QiJ0m5SjfV+iMv8uzuGac+PoJFlTaMFIN8eVccUnoY=
760-
github.com/operator-framework/api v0.3.23/go.mod h1:GVNiB6AQucwdZz3ZFXNv9HtcLOzcFnr6O/QldzKG93g=
759+
github.com/operator-framework/api v0.3.25 h1:d6WgHCshCffT37okVZeL+IbGlhrsHy57xdfMnopC8rI=
760+
github.com/operator-framework/api v0.3.25/go.mod h1:GVNiB6AQucwdZz3ZFXNv9HtcLOzcFnr6O/QldzKG93g=
761761
github.com/operator-framework/operator-registry v1.13.6 h1:h/dIjQQS7uneQNRifrSz7h0xg4Xyjg6C9f6XZofbMPg=
762762
github.com/operator-framework/operator-registry v1.13.6/go.mod h1:YhnIzOVjRU2ZwZtzt+fjcjW8ujJaSFynBEu7QVKaSdU=
763763
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=

pkg/controller/operators/olm/operator.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,22 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
220220
return nil, err
221221
}
222222

223+
// Register OperatorCondition QueueInformer
224+
opConditionInformer := extInformerFactory.Operators().V1().OperatorConditions()
225+
op.lister.OperatorsV1().RegisterOperatorConditionLister(namespace, opConditionInformer.Lister())
226+
opConditionQueueInformer, err := queueinformer.NewQueueInformer(
227+
ctx,
228+
queueinformer.WithLogger(op.logger),
229+
queueinformer.WithInformer(opConditionInformer.Informer()),
230+
queueinformer.WithSyncer(k8sSyncer),
231+
)
232+
if err != nil {
233+
return nil, err
234+
}
235+
if err := op.RegisterQueueInformer(opConditionQueueInformer); err != nil {
236+
return nil, err
237+
}
238+
223239
subInformer := extInformerFactory.Operators().V1alpha1().Subscriptions()
224240
op.lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, subInformer.Lister())
225241
subQueueInformer, err := queueinformer.NewQueueInformer(
@@ -1426,6 +1442,15 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
14261442
logger.Info("scheduling ClusterServiceVersion for requirement verification")
14271443
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "requirements not yet checked", now, a.recorder)
14281444
case v1alpha1.CSVPhasePending:
1445+
// Check previous version's Upgradeable condition
1446+
replacedCSV := a.isReplacing(out)
1447+
if replacedCSV != nil {
1448+
operatorUpgradeable, condErr := a.isOperatorUpgradeable(replacedCSV)
1449+
if !operatorUpgradeable {
1450+
out.SetPhaseWithEventIfChanged(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonOperatorConditionNotUpgradeable, fmt.Sprintf("operator is not upgradeable: %s", condErr), now, a.recorder)
1451+
return
1452+
}
1453+
}
14291454
met, statuses, err := a.requirementAndPermissionStatus(out)
14301455
if err != nil {
14311456
// TODO: account for Bad Rule as well
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package olm
2+
3+
import (
4+
"fmt"
5+
6+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
7+
meta "k8s.io/apimachinery/pkg/api/meta"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
11+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
12+
)
13+
14+
func (a *Operator) isOperatorUpgradeable(csv *v1alpha1.ClusterServiceVersion) (bool, error) {
15+
if csv == nil {
16+
return false, fmt.Errorf("CSV is invalid")
17+
}
18+
19+
cond, err := a.lister.OperatorsV1().OperatorConditionLister().OperatorConditions(csv.GetNamespace()).Get(csv.GetName())
20+
if err != nil {
21+
if k8serrors.IsNotFound(err) {
22+
return true, nil
23+
}
24+
return false, err
25+
}
26+
27+
// Check condition overrides
28+
for _, override := range cond.Spec.Overrides {
29+
if override.Type == operatorsv1.OperatorUpgradeable {
30+
if override.Status == metav1.ConditionTrue {
31+
return true, nil
32+
}
33+
return false, fmt.Errorf("The operator is not upgradeable: %s", override.Message)
34+
}
35+
}
36+
37+
// Check for OperatorUpgradeable condition status
38+
if c := meta.FindStatusCondition(cond.Status.Conditions, operatorsv1.OperatorUpgradeable); c != nil {
39+
if c.Status == metav1.ConditionFalse {
40+
return false, fmt.Errorf("The operator is not upgradeable: %s", c.Message)
41+
}
42+
}
43+
44+
return true, nil
45+
}

pkg/lib/operatorlister/lister.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ type OperatorsV1alpha1Lister interface {
102102
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . OperatorsV1Lister
103103
type OperatorsV1Lister interface {
104104
RegisterOperatorGroupLister(namespace string, lister v1.OperatorGroupLister)
105+
RegisterOperatorConditionLister(namespace string, lister v1.OperatorConditionLister)
105106

106107
OperatorGroupLister() v1.OperatorGroupLister
108+
OperatorConditionLister() v1.OperatorConditionLister
107109
}
108110

109111
type appsV1Lister struct {
@@ -189,12 +191,14 @@ func newOperatorsV1alpha1Lister() *operatorsV1alpha1Lister {
189191
}
190192

191193
type operatorsV1Lister struct {
192-
operatorGroupLister *UnionOperatorGroupLister
194+
operatorGroupLister *UnionOperatorGroupLister
195+
operatorConditionLister *UnionOperatorConditionLister
193196
}
194197

195198
func newOperatorsV1Lister() *operatorsV1Lister {
196199
return &operatorsV1Lister{
197-
operatorGroupLister: &UnionOperatorGroupLister{},
200+
operatorGroupLister: &UnionOperatorGroupLister{},
201+
operatorConditionLister: &UnionOperatorConditionLister{},
198202
}
199203
}
200204

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package operatorlister
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/labels"
9+
"k8s.io/apimachinery/pkg/types"
10+
11+
v1 "github.com/operator-framework/api/pkg/operators/v1"
12+
listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1"
13+
)
14+
15+
type UnionOperatorConditionLister struct {
16+
opConditionListers map[string]listers.OperatorConditionLister
17+
opConditionLock sync.RWMutex
18+
}
19+
20+
// List lists all OperatorConditions in the indexer.
21+
func (uol *UnionOperatorConditionLister) List(selector labels.Selector) (ret []*v1.OperatorCondition, err error) {
22+
uol.opConditionLock.RLock()
23+
defer uol.opConditionLock.RUnlock()
24+
25+
set := make(map[types.UID]*v1.OperatorCondition)
26+
for _, cl := range uol.opConditionListers {
27+
csvs, err := cl.List(selector)
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
for _, csv := range csvs {
33+
set[csv.GetUID()] = csv
34+
}
35+
}
36+
37+
for _, csv := range set {
38+
ret = append(ret, csv)
39+
}
40+
41+
return
42+
}
43+
44+
// OperatorConditions returns an object that can list and get OperatorConditions.
45+
func (uol *UnionOperatorConditionLister) OperatorConditions(namespace string) listers.OperatorConditionNamespaceLister {
46+
uol.opConditionLock.RLock()
47+
defer uol.opConditionLock.RUnlock()
48+
49+
// Check for specific namespace listers
50+
if cl, ok := uol.opConditionListers[namespace]; ok {
51+
return cl.OperatorConditions(namespace)
52+
}
53+
54+
// Check for any namespace-all listers
55+
if cl, ok := uol.opConditionListers[metav1.NamespaceAll]; ok {
56+
return cl.OperatorConditions(namespace)
57+
}
58+
59+
return &NullOperatorConditionNamespaceLister{}
60+
}
61+
62+
func (uol *UnionOperatorConditionLister) RegisterOperatorConditionLister(namespace string, lister listers.OperatorConditionLister) {
63+
uol.opConditionLock.Lock()
64+
defer uol.opConditionLock.Unlock()
65+
66+
if uol.opConditionListers == nil {
67+
uol.opConditionListers = make(map[string]listers.OperatorConditionLister)
68+
}
69+
70+
uol.opConditionListers[namespace] = lister
71+
}
72+
73+
func (l *operatorsV1Lister) RegisterOperatorConditionLister(namespace string, lister listers.OperatorConditionLister) {
74+
l.operatorConditionLister.RegisterOperatorConditionLister(namespace, lister)
75+
}
76+
77+
func (l *operatorsV1Lister) OperatorConditionLister() listers.OperatorConditionLister {
78+
return l.operatorConditionLister
79+
}
80+
81+
// NullOperatorConditionNamespaceLister is an implementation of a null OperatorConditionNamespaceLister. It is
82+
// used to prevent nil pointers when no OperatorConditionNamespaceLister has been registered for a given
83+
// namespace.
84+
type NullOperatorConditionNamespaceLister struct {
85+
listers.OperatorConditionNamespaceLister
86+
}
87+
88+
// List returns nil and an error explaining that this is a NullOperatorConditionNamespaceLister.
89+
func (n *NullOperatorConditionNamespaceLister) List(selector labels.Selector) (ret []*v1.OperatorCondition, err error) {
90+
return nil, fmt.Errorf("cannot list OperatorConditions with a NullOperatorConditionNamespaceLister")
91+
}
92+
93+
// Get returns nil and an error explaining that this is a NullOperatorConditionNamespaceLister.
94+
func (n *NullOperatorConditionNamespaceLister) Get(name string) (*v1.OperatorCondition, error) {
95+
return nil, fmt.Errorf("cannot get OperatorCondition with a NullOperatorConditionNamespaceLister")
96+
}

pkg/lib/operatorlister/operatorlisterfakes/fake_operators_v1lister.go

Lines changed: 104 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)