Skip to content

Commit 22543bd

Browse files
Merge pull request #690 from dinhxuanvu/api-owner
fix(olm): Fix CSVs api-servers battle for ownership of APIServices
2 parents a6c0fe7 + ee5c004 commit 22543bd

File tree

7 files changed

+655
-80
lines changed

7 files changed

+655
-80
lines changed

pkg/controller/errors/errors.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ type MultipleExistingCRDOwnersError struct {
1010
Namespace string
1111
}
1212

13+
type UnadoptableError struct {
14+
resourceNamespace string
15+
resourceName string
16+
}
17+
18+
func (err UnadoptableError) Error() string {
19+
if err.resourceNamespace == "" {
20+
return fmt.Sprintf("%s is unadoptable", err.resourceName)
21+
}
22+
return fmt.Sprintf("%s/%s is unadoptable", err.resourceNamespace, err.resourceName)
23+
}
24+
25+
func NewUnadoptableError(resourceNamespace, resourceName string) UnadoptableError {
26+
return UnadoptableError{resourceNamespace, resourceName}
27+
}
28+
1329
func (m MultipleExistingCRDOwnersError) Error() string {
1430
return fmt.Sprintf("Existing CSVs %v in namespace %s all claim to own CRD %s", m.CSVNames, m.Namespace, m.CRDName)
1531
}

pkg/controller/operators/catalog/operator_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import (
3333
"github.com/operator-framework/operator-lifecycle-manager/pkg/fakes"
3434
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
3535
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
36-
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
3736
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
37+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
3838
)
3939

4040
type mockTransitioner struct {
@@ -141,23 +141,23 @@ func TestExecutePlan(t *testing.T) {
141141
Resource: v1alpha1.StepResource{
142142
CatalogSource: "catalog",
143143
CatalogSourceNamespace: namespace,
144-
Group: "",
145-
Version: "v1",
146-
Kind: "Service",
147-
Name: "service",
148-
Manifest: toManifest(service("service", namespace)),
144+
Group: "",
145+
Version: "v1",
146+
Kind: "Service",
147+
Name: "service",
148+
Manifest: toManifest(service("service", namespace)),
149149
},
150150
Status: v1alpha1.StepStatusUnknown,
151151
},
152152
&v1alpha1.Step{
153153
Resource: v1alpha1.StepResource{
154154
CatalogSource: "catalog",
155155
CatalogSourceNamespace: namespace,
156-
Group: "operators.coreos.com",
157-
Version: "v1alpha1",
158-
Kind: "ClusterServiceVersion",
159-
Name: "csv",
160-
Manifest: toManifest(csv("csv", namespace, nil, nil)),
156+
Group: "operators.coreos.com",
157+
Version: "v1alpha1",
158+
Kind: "ClusterServiceVersion",
159+
Name: "csv",
160+
Manifest: toManifest(csv("csv", namespace, nil, nil)),
161161
},
162162
Status: v1alpha1.StepStatusUnknown,
163163
},
@@ -488,8 +488,8 @@ func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extO
488488
podInformer := informerFactory.Core().V1().Pods()
489489
configMapInformer := informerFactory.Core().V1().ConfigMaps()
490490
subscriptionInformer := externalversions.NewSharedInformerFactoryWithOptions(clientFake, wakeupInterval, externalversions.WithNamespace(namespace)).Operators().V1alpha1().Subscriptions()
491-
installPlanInformer := externalversions.NewSharedInformerFactoryWithOptions(clientFake, wakeupInterval, externalversions.WithNamespace(namespace)).Operators().V1alpha1().InstallPlans()
492-
491+
installPlanInformer := externalversions.NewSharedInformerFactoryWithOptions(clientFake, wakeupInterval, externalversions.WithNamespace(namespace)).Operators().V1alpha1().InstallPlans()
492+
493493
// register informers
494494
registryInformers := []cache.SharedIndexInformer{
495495
roleInformer.Informer(),

pkg/controller/operators/olm/apiservices.go

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
1818
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs"
19+
olmerrors "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/errors"
1920
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
2021
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
2122
)
@@ -40,8 +41,36 @@ func (a *Operator) shouldRotateCerts(csv *v1alpha1.ClusterServiceVersion) bool {
4041
return false
4142
}
4243

44+
// apiServiceResourceErrorsActionable returns true if OLM can do something about any one
45+
// of the apiService errors in errs; otherwise returns false
46+
//
47+
// This method can be used to determine if a CSV in a failed state due to APIService
48+
// issues can resolve them by reinstalling
49+
func (a *Operator) apiServiceResourceErrorsActionable(errs []error) bool {
50+
for _, err := range errs {
51+
switch err.(type) {
52+
case olmerrors.UnadoptableError:
53+
return false
54+
}
55+
}
56+
57+
return true
58+
}
59+
4360
// checkAPIServiceResources checks if all expected generated resources for the given APIService exist
44-
func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion, hashFunc certs.PEMHash) error {
61+
func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion, hashFunc certs.PEMHash) []error {
62+
errs := []error{}
63+
owners := []ownerutil.Owner{csv}
64+
// Get replacing CSV if exists
65+
replacement, 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 errs
70+
}
71+
if replacement != nil {
72+
owners = append(owners, replacement)
73+
}
4574
ruleChecker := install.NewCSVRuleChecker(a.lister.RbacV1().RoleLister(), a.lister.RbacV1().RoleBindingLister(), a.lister.RbacV1().ClusterRoleLister(), a.lister.RbacV1().ClusterRoleBindingLister(), csv)
4675
for _, desc := range csv.GetOwnedAPIServiceDescriptions() {
4776
apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group)
@@ -51,59 +80,76 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
5180
"apiservice": apiServiceName,
5281
})
5382

54-
serviceName := APIServiceNameToServiceName(apiServiceName)
55-
service, err := a.lister.CoreV1().ServiceLister().Services(csv.GetNamespace()).Get(serviceName)
83+
apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(apiServiceName)
5684
if err != nil {
57-
logger.WithField("service", serviceName).Warnf("could not retrieve generated Service")
58-
return err
85+
logger.Warnf("could not retrieve generated APIService")
86+
errs = append(errs, err)
87+
continue
5988
}
6089

61-
apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(apiServiceName)
90+
// Check if the APIService is adoptable
91+
if !ownerutil.OwnersIntersect(owners, apiService) {
92+
err := olmerrors.NewUnadoptableError("", apiServiceName)
93+
logger.WithError(err).Warn("found unadoptable apiservice")
94+
errs = append(errs, err)
95+
return errs
96+
}
97+
98+
serviceName := APIServiceNameToServiceName(apiServiceName)
99+
service, err := a.lister.CoreV1().ServiceLister().Services(csv.GetNamespace()).Get(serviceName)
62100
if err != nil {
63-
logger.Warnf("could not retrieve generated APIService")
64-
return err
101+
logger.WithField("service", serviceName).Warnf("could not retrieve generated Service")
102+
errs = append(errs, err)
103+
continue
65104
}
66105

67106
// Check if the APIService points to the correct service
68107
if apiService.Spec.Service.Name != serviceName || apiService.Spec.Service.Namespace != csv.GetNamespace() {
69108
logger.WithFields(log.Fields{"service": apiService.Spec.Service.Name, "serviceNamespace": apiService.Spec.Service.Namespace}).Warnf("APIService service reference mismatch")
70-
return fmt.Errorf("APIService service reference mismatch")
109+
errs = append(errs, fmt.Errorf("APIService service reference mismatch"))
110+
continue
71111
}
72112

73113
// Check if CA is Active
74114
caBundle := apiService.Spec.CABundle
75115
ca, err := certs.PEMToCert(caBundle)
76116
if err != nil {
77117
logger.Warnf("could not convert APIService CA bundle to x509 cert")
78-
return err
118+
errs = append(errs, err)
119+
continue
79120
}
80121
if !certs.Active(ca) {
81122
logger.Warnf("CA cert not active")
82-
return fmt.Errorf("CA cert not active")
123+
errs = append(errs, fmt.Errorf("CA cert not active"))
124+
continue
83125
}
84126

85127
// Check if serving cert is active
86128
secretName := apiServiceName + "-cert"
87129
secret, err := a.lister.CoreV1().SecretLister().Secrets(csv.GetNamespace()).Get(secretName)
88130
if err != nil {
89131
logger.WithField("secret", secretName).Warnf("could not retrieve generated Secret")
90-
return err
132+
errs = append(errs, err)
133+
continue
91134
}
92135
cert, err := certs.PEMToCert(secret.Data["tls.crt"])
93136
if err != nil {
94137
logger.Warnf("could not convert serving cert to x509 cert")
95-
return err
138+
errs = append(errs, err)
139+
continue
96140
}
97141
if !certs.Active(cert) {
98142
logger.Warnf("serving cert not active")
99-
return fmt.Errorf("serving cert not active")
143+
errs = append(errs, fmt.Errorf("serving cert not active"))
144+
continue
100145
}
101146

102147
// Check if CA hash matches expected
103148
caHash := hashFunc(caBundle)
104149
if hash, ok := secret.GetAnnotations()[OLMCAHashAnnotationKey]; !ok || hash != caHash {
105150
logger.WithField("secret", secretName).Warnf("secret CA cert hash does not match expected")
106-
return fmt.Errorf("secret %s CA cert hash does not match expected", secretName)
151+
errs = append(errs, fmt.Errorf("secret %s CA cert hash does not match expected", secretName))
152+
continue
107153
}
108154

109155
// Check if serving cert is trusted by the CA
@@ -113,19 +159,22 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
113159
}
114160
for _, host := range hosts {
115161
if err := certs.VerifyCert(ca, cert, host); err != nil {
116-
return fmt.Errorf("could not verify cert: %s", err.Error())
162+
errs = append(errs, fmt.Errorf("could not verify cert: %s", err.Error()))
163+
continue
117164
}
118165
}
119166

120167
// Ensure the existing Deployment has a matching CA hash annotation
121168
deployment, err := a.lister.AppsV1().DeploymentLister().Deployments(csv.GetNamespace()).Get(desc.DeploymentName)
122169
if k8serrors.IsNotFound(err) || err != nil {
123170
logger.WithField("deployment", desc.DeploymentName).Warnf("expected Deployment could not be retrieved")
124-
return err
171+
errs = append(errs, err)
172+
continue
125173
}
126174
if hash, ok := deployment.Spec.Template.GetAnnotations()[OLMCAHashAnnotationKey]; !ok || hash != caHash {
127175
logger.WithField("deployment", desc.DeploymentName).Warnf("Deployment CA cert hash does not match expected")
128-
return fmt.Errorf("Deployment %s CA cert hash does not match expected", desc.DeploymentName)
176+
errs = append(errs, fmt.Errorf("Deployment %s CA cert hash does not match expected", desc.DeploymentName))
177+
continue
129178
}
130179

131180
// Ensure the Deployment's ServiceAccount exists
@@ -136,7 +185,8 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
136185
serviceAccount, err := a.lister.CoreV1().ServiceAccountLister().ServiceAccounts(deployment.GetNamespace()).Get(serviceAccountName)
137186
if err != nil {
138187
logger.WithField("serviceaccount", serviceAccountName).Warnf("could not retrieve ServiceAccount")
139-
return err
188+
errs = append(errs, err)
189+
continue
140190
}
141191

142192
// Ensure RBAC permissions for the APIService are correct
@@ -158,15 +208,17 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
158208
authReaderRole, err := a.lister.RbacV1().RoleLister().Roles("kube-system").Get("extension-apiserver-authentication-reader")
159209
if err != nil {
160210
logger.Warnf("could not retrieve Role extension-apiserver-authentication-reader")
161-
return err
211+
errs = append(errs, err)
212+
continue
162213
}
163214
rulesMap["kube-system"] = append(rulesMap["kube-system"], authReaderRole.Rules...)
164215

165216
// system:auth-delegator
166217
authDelegatorClusterRole, err := a.lister.RbacV1().ClusterRoleLister().Get("system:auth-delegator")
167218
if err != nil {
168219
logger.Warnf("could not retrieve ClusterRole system:auth-delegator")
169-
return err
220+
errs = append(errs, err)
221+
continue
170222
}
171223
rulesMap[metav1.NamespaceAll] = append(rulesMap[metav1.NamespaceAll], authDelegatorClusterRole.Rules...)
172224

@@ -175,17 +227,19 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
175227
satisfied, err := ruleChecker.RuleSatisfied(serviceAccount, namespace, rule)
176228
if err != nil {
177229
logger.WithField("rule", fmt.Sprintf("%+v", rule)).Warnf("error checking Rule")
178-
return err
230+
errs = append(errs, err)
231+
continue
179232
}
180233
if !satisfied {
181234
logger.WithField("rule", fmt.Sprintf("%+v", rule)).Warnf("Rule not satisfied")
182-
return fmt.Errorf("Rule %+v not satisfied", rule)
235+
errs = append(errs, fmt.Errorf("Rule %+v not satisfied", rule))
236+
continue
183237
}
184238
}
185239
}
186240
}
187241

188-
return nil
242+
return errs
189243
}
190244

191245
func (a *Operator) isAPIServiceAvailable(apiService *apiregistrationv1.APIService) bool {
@@ -639,8 +693,14 @@ func (a *Operator) installAPIServiceRequirements(desc v1alpha1.APIServiceDescrip
639693
}
640694
apiService.SetName(apiServiceName)
641695
} else {
696+
owners := []ownerutil.Owner{csv}
697+
// Get replacing CSV
698+
replaces, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace()).Get(csv.Spec.Replaces)
699+
if err == nil {
700+
owners = append(owners, replaces)
701+
}
642702
// check if the APIService is adoptable
643-
if !ownerutil.AdoptableLabels(csv, apiService.GetLabels()) {
703+
if !ownerutil.OwnersIntersect(owners, apiService) {
644704
return nil, fmt.Errorf("pre-existing APIService %s is not adoptable", apiServiceName)
645705
}
646706
}

0 commit comments

Comments
 (0)