Skip to content

Commit c5a4d5f

Browse files
committed
fix(csv): properly detect owner conflicts for crds and apiservices
- Use ownerlabels for APIService conflict detection - Only detect CRD owner conflict when the potential conflict is not the syncing CSV or the CSV it replaces
1 parent eec307e commit c5a4d5f

File tree

6 files changed

+98
-105
lines changed

6 files changed

+98
-105
lines changed

pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ type APIResourceReference struct {
113113
Version string `json:"version"`
114114
}
115115

116+
// GetName returns the name of an APIService as derived from its group and version.
117+
func (d APIServiceDescription) GetName() string {
118+
return fmt.Sprintf("%s.%s", d.Version, d.Group)
119+
}
120+
116121
// CustomResourceDefinitions declares all of the CRDs managed or required by
117122
// an operator being ran by ClusterServiceVersion.
118123
//

pkg/controller/operators/olm/apiservices.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,24 +59,28 @@ func (a *Operator) apiServiceResourceErrorActionable(err error) bool {
5959

6060
// checkAPIServiceResources checks if all expected generated resources for the given APIService exist
6161
func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion, hashFunc certs.PEMHash) error {
62+
logger := log.WithFields(log.Fields{
63+
"csv": csv.GetName(),
64+
"namespace": csv.GetNamespace(),
65+
})
66+
6267
errs := []error{}
6368
owners := []ownerutil.Owner{csv}
69+
6470
// Get replacing CSV if exists
6571
replacing, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace()).Get(csv.Spec.Replaces)
66-
if err != nil && k8serrors.IsNotFound(err) == false {
67-
a.Log.Debugf("Replacement error regarding CSV (%v): %v", csv.GetName(), err)
68-
errs = append(errs, err)
69-
return utilerrors.NewAggregate(errs)
72+
if err != nil && !k8serrors.IsNotFound(err) {
73+
logger.WithError(err).Warn("could not get replacement csv")
74+
return err
7075
}
7176
if replacing != nil {
7277
owners = append(owners, replacing)
7378
}
79+
7480
ruleChecker := install.NewCSVRuleChecker(a.lister.RbacV1().RoleLister(), a.lister.RbacV1().RoleBindingLister(), a.lister.RbacV1().ClusterRoleLister(), a.lister.RbacV1().ClusterRoleBindingLister(), csv)
7581
for _, desc := range csv.GetOwnedAPIServiceDescriptions() {
76-
apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group)
77-
logger := log.WithFields(log.Fields{
78-
"csv": csv.GetName(),
79-
"namespace": csv.GetNamespace(),
82+
apiServiceName := desc.GetName()
83+
logger := logger.WithFields(log.Fields{
8084
"apiservice": apiServiceName,
8185
})
8286

@@ -88,7 +92,7 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
8892
}
8993

9094
// Check if the APIService is adoptable
91-
if !ownerutil.OwnersIntersect(owners, apiService) {
95+
if !ownerutil.AdoptableLabels(apiService.GetLabels(), true, owners...) {
9296
err := olmerrors.NewUnadoptableError("", apiServiceName)
9397
logger.WithError(err).Warn("found unadoptable apiservice")
9498
errs = append(errs, err)
@@ -253,8 +257,7 @@ func (a *Operator) isAPIServiceAvailable(apiService *apiregistrationv1.APIServic
253257

254258
func (a *Operator) areAPIServicesAvailable(csv *v1alpha1.ClusterServiceVersion) (bool, error) {
255259
for _, desc := range csv.Spec.APIServiceDefinitions.Owned {
256-
apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group)
257-
apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(apiServiceName)
260+
apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(desc.GetName())
258261
if k8serrors.IsNotFound(err) {
259262
return false, nil
260263
}
@@ -550,7 +553,7 @@ func (a *Operator) installAPIServiceRequirements(desc v1alpha1.APIServiceDescrip
550553
existingAuthDelegatorClusterRoleBinding, err := a.lister.RbacV1().ClusterRoleBindingLister().Get(authDelegatorClusterRoleBinding.GetName())
551554
if err == nil {
552555
// Check if the only owners are this CSV or in this CSV's replacement chain.
553-
if ownerutil.AdoptableLabels(csv, existingAuthDelegatorClusterRoleBinding.GetLabels()) {
556+
if ownerutil.AdoptableLabels(existingAuthDelegatorClusterRoleBinding.GetLabels(), true, csv) {
554557
ownerutil.AddOwnerLabels(authDelegatorClusterRoleBinding, csv)
555558
}
556559

@@ -593,7 +596,7 @@ func (a *Operator) installAPIServiceRequirements(desc v1alpha1.APIServiceDescrip
593596
existingAuthReaderRoleBinding, err := a.lister.RbacV1().RoleBindingLister().RoleBindings("kube-system").Get(authReaderRoleBinding.GetName())
594597
if err == nil {
595598
// Check if the only owners are this CSV or in this CSV's replacement chain.
596-
if ownerutil.AdoptableLabels(csv, existingAuthReaderRoleBinding.GetLabels()) {
599+
if ownerutil.AdoptableLabels(existingAuthReaderRoleBinding.GetLabels(), true, csv) {
597600
ownerutil.AddOwnerLabels(authReaderRoleBinding, csv)
598601
}
599602
// Attempt an update.
@@ -694,13 +697,15 @@ func (a *Operator) installAPIServiceRequirements(desc v1alpha1.APIServiceDescrip
694697
apiService.SetName(apiServiceName)
695698
} else {
696699
owners := []ownerutil.Owner{csv}
700+
697701
// Get replacing CSV
698702
replaces, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace()).Get(csv.Spec.Replaces)
699703
if err == nil {
700704
owners = append(owners, replaces)
701705
}
706+
702707
// check if the APIService is adoptable
703-
if !ownerutil.OwnersIntersect(owners, apiService) {
708+
if !ownerutil.AdoptableLabels(apiService.GetLabels(), true, owners...) {
704709
return nil, fmt.Errorf("pre-existing APIService %s is not adoptable", apiServiceName)
705710
}
706711
}

pkg/controller/operators/olm/operator.go

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import (
3838

3939
var (
4040
ErrRequirementsNotMet = errors.New("requirements were not met")
41-
ErrCRDOwnerConflict = errors.New("CRD owned by another ClusterServiceVersion")
42-
ErrAPIServiceOwnerConflict = errors.New("APIService owned by another ClusterServiceVersion")
41+
ErrCRDOwnerConflict = errors.New("conflicting CRD owner in namespace")
42+
ErrAPIServiceOwnerConflict = errors.New("unable to adopt APIService")
4343
)
4444

4545
var timeNow = func() metav1.Time { return metav1.NewTime(time.Now().UTC()) }
@@ -849,16 +849,18 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
849849
}
850850

851851
// Check for CRD ownership conflicts
852-
// TODO: find CSVs that provide any of those that out does across all namespaces
853-
csvSet := a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)
854-
if syncError = a.crdOwnerConflicts(out, csvSet); syncError != nil {
855-
out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonOwnerConflict, fmt.Sprintf("crd owner conflict: %s", syncError), now, a.recorder)
852+
if syncError = a.crdOwnerConflicts(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)); syncError != nil {
853+
if syncError == ErrCRDOwnerConflict {
854+
out.SetPhaseWithEventIfChanged(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonOwnerConflict, syncError.Error(), now, a.recorder)
855+
}
856856
return
857857
}
858858

859-
// check for APIServices ownership conflicts
860-
if syncError = a.apiServiceOwnerConflicts(out, csvSet); syncError != nil {
861-
out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonOwnerConflict, fmt.Sprintf("apiService owner conflict: %s", syncError), now, a.recorder)
859+
// Check for APIServices ownership conflicts
860+
if syncError = a.apiServiceOwnerConflicts(out); syncError != nil {
861+
if syncError == ErrAPIServiceOwnerConflict {
862+
out.SetPhaseWithEventIfChanged(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonOwnerConflict, syncError.Error(), now, a.recorder)
863+
}
862864
return
863865
}
864866

@@ -897,6 +899,11 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
897899

898900
if installErr := a.updateInstallStatus(out, installer, strategy, v1alpha1.CSVPhaseInstalling, v1alpha1.CSVReasonWaiting); installErr == nil {
899901
logger.WithField("strategy", out.Spec.InstallStrategy.StrategyName).Infof("install strategy successful")
902+
} else {
903+
// Set phase to failed if it's been a long time since the last transition (5 minutes)
904+
if metav1.Now().Sub(out.Status.LastTransitionTime.Time) >= 5*time.Minute {
905+
out.SetPhaseWithEventIfChanged(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInstallCheckFailed, fmt.Sprintf("install timeout"), now, a.recorder)
906+
}
900907
}
901908

902909
case v1alpha1.CSVPhaseSucceeded:
@@ -981,9 +988,11 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
981988
}
982989

983990
// Check if any generated resources are missing and that OLM can action on them
984-
if err := a.checkAPIServiceResources(out, certs.PEMSHA256); err != nil && a.apiServiceResourceErrorActionable(err) {
985-
// Check if API services are adoptable. If not, keep CSV as Failed state
986-
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonAPIServiceResourcesNeedReinstall, err.Error(), now, a.recorder)
991+
if err := a.checkAPIServiceResources(out, certs.PEMSHA256); err != nil {
992+
if a.apiServiceResourceErrorActionable(err) {
993+
// Check if API services are adoptable. If not, keep CSV as Failed state
994+
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonAPIServiceResourcesNeedReinstall, err.Error(), now, a.recorder)
995+
}
987996
return
988997
}
989998

@@ -1009,20 +1018,20 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
10091018
return
10101019
}
10111020

1012-
// if we can find a newer version that's successfully installed, we're safe to mark all intermediates
1021+
// If we can find a newer version that's successfully installed, we're safe to mark all intermediates
10131022
for _, csv := range a.findIntermediatesForDeletion(out) {
10141023
// we only mark them in this step, in case some get deleted but others fail and break the replacement chain
10151024
csv.SetPhaseWithEvent(v1alpha1.CSVPhaseDeleting, v1alpha1.CSVReasonReplaced, "has been replaced by a newer ClusterServiceVersion that has successfully installed.", now, a.recorder)
10161025

1017-
// ignore errors and success here; this step is just an optimization to speed up GC
1026+
// Ignore errors and success here; this step is just an optimization to speed up GC
10181027
_, _ = a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).UpdateStatus(csv)
10191028
err := a.csvQueueSet.Requeue(csv.GetName(), csv.GetNamespace())
10201029
if err != nil {
10211030
a.Log.Warn(err.Error())
10221031
}
10231032
}
10241033

1025-
// if there's no newer version, requeue for processing (likely will be GCable before resync)
1034+
// If there's no newer version, requeue for processing (likely will be GCable before resync)
10261035
err := a.csvQueueSet.Requeue(out.GetName(), out.GetNamespace())
10271036
if err != nil {
10281037
a.Log.Warn(err.Error())
@@ -1136,6 +1145,10 @@ func (a *Operator) updateInstallStatus(csv *v1alpha1.ClusterServiceVersion, inst
11361145

11371146
if strategyErr != nil {
11381147
csv.SetPhaseWithEventIfChanged(requeuePhase, requeueConditionReason, fmt.Sprintf("installing: %s", strategyErr), now, a.recorder)
1148+
if err := a.csvQueueSet.Requeue(csv.GetName(), csv.GetNamespace()); err != nil {
1149+
a.Log.Warn(err.Error())
1150+
}
1151+
11391152
return strategyErr
11401153
}
11411154

@@ -1169,16 +1182,10 @@ func (a *Operator) parseStrategiesAndUpdateStatus(csv *v1alpha1.ClusterServiceVe
11691182
return installer, strategy, previousStrategy
11701183
}
11711184

1172-
func (a *Operator) crdOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvsInNamespace map[string]*v1alpha1.ClusterServiceVersion) error {
1185+
func (a *Operator) crdOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvs map[string]*v1alpha1.ClusterServiceVersion) error {
11731186
for _, crd := range in.Spec.CustomResourceDefinitions.Owned {
1174-
for csvName, csv := range csvsInNamespace {
1175-
if csvName == in.GetName() {
1176-
continue
1177-
}
1178-
if csv.OwnsCRD(crd.Name) {
1179-
if in.Spec.Replaces == csvName {
1180-
return nil
1181-
}
1187+
for name, csv := range csvs {
1188+
if name != in.GetName() && in.Spec.Replaces != name && csv.OwnsCRD(crd.Name) {
11821189
return ErrCRDOwnerConflict
11831190
}
11841191
}
@@ -1187,25 +1194,34 @@ func (a *Operator) crdOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvsInN
11871194
return nil
11881195
}
11891196

1190-
func (a *Operator) apiServiceOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvsInNamespace map[string]*v1alpha1.ClusterServiceVersion) error {
1191-
owned := false
1192-
for _, api := range in.Spec.APIServiceDefinitions.Owned {
1193-
name := fmt.Sprintf("%s.%s", api.Version, api.Group)
1194-
for csvName, csv := range csvsInNamespace {
1195-
if csvName == in.GetName() {
1196-
continue
1197-
}
1198-
if csv.OwnsAPIService(name) {
1199-
owned = true
1200-
}
1201-
if owned && in.Spec.Replaces == csvName {
1202-
return nil
1203-
}
1204-
}
1197+
func (a *Operator) apiServiceOwnerConflicts(csv *v1alpha1.ClusterServiceVersion) error {
1198+
// Get replacing CSV if exists
1199+
replacing, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace()).Get(csv.Spec.Replaces)
1200+
if err != nil && !k8serrors.IsNotFound(err) && !k8serrors.IsGone(err) {
1201+
return err
1202+
}
1203+
1204+
owners := []ownerutil.Owner{csv}
1205+
if replacing != nil {
1206+
owners = append(owners, replacing)
12051207
}
1206-
if owned {
1207-
return ErrAPIServiceOwnerConflict
1208+
1209+
for _, desc := range csv.GetOwnedAPIServiceDescriptions() {
1210+
// Check if the APIService exists
1211+
apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(desc.GetName())
1212+
if err != nil && !k8serrors.IsNotFound(err) && !k8serrors.IsGone(err) {
1213+
return err
1214+
}
1215+
1216+
if apiService == nil {
1217+
continue
1218+
}
1219+
1220+
if !ownerutil.AdoptableLabels(apiService.GetLabels(), true, owners...) {
1221+
return ErrAPIServiceOwnerConflict
1222+
}
12081223
}
1224+
12091225
return nil
12101226
}
12111227

pkg/controller/registry/resolver/operators.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package resolver
22

33
import (
44
"fmt"
5-
"strings"
65
"sort"
6+
"strings"
77

8-
"k8s.io/apimachinery/pkg/runtime/schema"
98
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
10-
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
1111
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
1212
)
1313

pkg/lib/notify/notify.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package notify

pkg/lib/ownerutil/util.go

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"k8s.io/apimachinery/pkg/labels"
1414
"k8s.io/apimachinery/pkg/runtime"
1515
"k8s.io/apimachinery/pkg/runtime/schema"
16-
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
1716
)
1817

1918
const (
@@ -143,46 +142,6 @@ func Adoptable(target Owner, owners []metav1.OwnerReference) bool {
143142
return false
144143
}
145144

146-
// OwnersIntersect checks to see if any member of targets exists in owners
147-
func OwnersIntersect(targets []Owner, apiservice *apiregistrationv1.APIService) bool {
148-
ownerLabels := apiservice.GetLabels()
149-
if len(ownerLabels) == 0 {
150-
// Resources with no owners are not adoptable
151-
log.Debugf("Did not contain labels for apiservice %v", apiservice.GetName())
152-
return false
153-
}
154-
155-
svcOwnerKind, ok := ownerLabels[OwnerKind]
156-
if !ok {
157-
log.Warnf("missing owner kind for apiservice %v", apiservice.GetName())
158-
return false
159-
}
160-
svcOwnerNamespace, ok := ownerLabels[OwnerNamespaceKey]
161-
if !ok {
162-
log.Warnf("missing owner namespace for apiservice %v", apiservice.GetName())
163-
return false
164-
}
165-
svcOwnerName, ok := ownerLabels[OwnerKey]
166-
if !ok {
167-
log.Warnf("missing owner name for apiservice %v", apiservice.GetName())
168-
return false
169-
}
170-
171-
for _, target := range targets {
172-
if err := InferGroupVersionKind(target); err != nil {
173-
log.Warnf("GVK infer failed for %v: %v", target, err)
174-
return false
175-
}
176-
if svcOwnerKind == target.GetObjectKind().GroupVersionKind().Kind &&
177-
svcOwnerNamespace == target.GetNamespace() &&
178-
svcOwnerName == target.GetName() {
179-
return true
180-
}
181-
}
182-
183-
return false
184-
}
185-
186145
// AddNonBlockingOwner adds a nonblocking owner to the ownerref list.
187146
func AddNonBlockingOwner(object metav1.Object, owner Owner) {
188147
ownerRefs := object.GetOwnerReferences()
@@ -257,17 +216,24 @@ func IsOwnedByKindLabel(object metav1.Object, ownerKind string) bool {
257216
return object.GetLabels()[OwnerKind] == ownerKind
258217
}
259218

260-
// AdoptableLabels determines if an OLM managed resource is adoptable based on its owner labels
261-
// Generally used for cross-namespace ownership and for Cluster -> Namespace scope
262-
func AdoptableLabels(target Owner, labels map[string]string) bool {
219+
// AdoptableLabels determines if an OLM managed resource is adoptable by any of the given targets based on its owner labels.
220+
// The checkName perimeter enables an additional check for name equality with the `olm.owner` label.
221+
// Generally used for cross-namespace ownership and for Cluster -> Namespace scope.
222+
func AdoptableLabels(labels map[string]string, checkName bool, targets ...Owner) bool {
263223
if len(labels) == 0 {
264224
// Resources with no owners are not adoptable
265225
return false
266226
}
267227

268-
if labels[OwnerKind] == target.GetObjectKind().GroupVersionKind().Kind &&
269-
labels[OwnerNamespaceKey] == target.GetNamespace() {
270-
return true
228+
for _, target := range targets {
229+
if err := InferGroupVersionKind(target); err != nil {
230+
log.Warn(err.Error())
231+
}
232+
if labels[OwnerKind] == target.GetObjectKind().GroupVersionKind().Kind &&
233+
labels[OwnerNamespaceKey] == target.GetNamespace() &&
234+
(!checkName || labels[OwnerKey] == target.GetName()) {
235+
return true
236+
}
271237
}
272238

273239
return false

0 commit comments

Comments
 (0)