Skip to content

Commit 4121c47

Browse files
dinhxuanvuJeff Peeler
authored andcommitted
fix(olm): Fix CSVs api-servers battle for ownership of APIServices
Currently, CSVs that install extension api-servers battle for ownership of APIServices. New CSVs only adopt APIServices that have OwnerReferences to CSVs in the new CSV's replaces chain. Signed-off-by: Vu Dinh <[email protected]>
1 parent a6c0fe7 commit 4121c47

File tree

7 files changed

+603
-76
lines changed

7 files changed

+603
-76
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: 80 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,31 @@ 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, _ := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace()).Get(csv.Spec.Replaces)
66+
if replacement != nil {
67+
owners = append(owners, replacement)
68+
}
4569
ruleChecker := install.NewCSVRuleChecker(a.lister.RbacV1().RoleLister(), a.lister.RbacV1().RoleBindingLister(), a.lister.RbacV1().ClusterRoleLister(), a.lister.RbacV1().ClusterRoleBindingLister(), csv)
4670
for _, desc := range csv.GetOwnedAPIServiceDescriptions() {
4771
apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group)
@@ -51,59 +75,76 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
5175
"apiservice": apiServiceName,
5276
})
5377

54-
serviceName := APIServiceNameToServiceName(apiServiceName)
55-
service, err := a.lister.CoreV1().ServiceLister().Services(csv.GetNamespace()).Get(serviceName)
78+
apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(apiServiceName)
5679
if err != nil {
57-
logger.WithField("service", serviceName).Warnf("could not retrieve generated Service")
58-
return err
80+
logger.Warnf("could not retrieve generated APIService")
81+
errs = append(errs, err)
82+
continue
5983
}
6084

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

67101
// Check if the APIService points to the correct service
68102
if apiService.Spec.Service.Name != serviceName || apiService.Spec.Service.Namespace != csv.GetNamespace() {
69103
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")
104+
errs = append(errs, fmt.Errorf("APIService service reference mismatch"))
105+
continue
71106
}
72107

73108
// Check if CA is Active
74109
caBundle := apiService.Spec.CABundle
75110
ca, err := certs.PEMToCert(caBundle)
76111
if err != nil {
77112
logger.Warnf("could not convert APIService CA bundle to x509 cert")
78-
return err
113+
errs = append(errs, err)
114+
continue
79115
}
80116
if !certs.Active(ca) {
81117
logger.Warnf("CA cert not active")
82-
return fmt.Errorf("CA cert not active")
118+
errs = append(errs, fmt.Errorf("CA cert not active"))
119+
continue
83120
}
84121

85122
// Check if serving cert is active
86123
secretName := apiServiceName + "-cert"
87124
secret, err := a.lister.CoreV1().SecretLister().Secrets(csv.GetNamespace()).Get(secretName)
88125
if err != nil {
89126
logger.WithField("secret", secretName).Warnf("could not retrieve generated Secret")
90-
return err
127+
errs = append(errs, err)
128+
continue
91129
}
92130
cert, err := certs.PEMToCert(secret.Data["tls.crt"])
93131
if err != nil {
94132
logger.Warnf("could not convert serving cert to x509 cert")
95-
return err
133+
errs = append(errs, err)
134+
continue
96135
}
97136
if !certs.Active(cert) {
98137
logger.Warnf("serving cert not active")
99-
return fmt.Errorf("serving cert not active")
138+
errs = append(errs, fmt.Errorf("serving cert not active"))
139+
continue
100140
}
101141

102142
// Check if CA hash matches expected
103143
caHash := hashFunc(caBundle)
104144
if hash, ok := secret.GetAnnotations()[OLMCAHashAnnotationKey]; !ok || hash != caHash {
105145
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)
146+
errs = append(errs, fmt.Errorf("secret %s CA cert hash does not match expected", secretName))
147+
continue
107148
}
108149

109150
// Check if serving cert is trusted by the CA
@@ -113,19 +154,22 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
113154
}
114155
for _, host := range hosts {
115156
if err := certs.VerifyCert(ca, cert, host); err != nil {
116-
return fmt.Errorf("could not verify cert: %s", err.Error())
157+
errs = append(errs, fmt.Errorf("could not verify cert: %s", err.Error()))
158+
continue
117159
}
118160
}
119161

120162
// Ensure the existing Deployment has a matching CA hash annotation
121163
deployment, err := a.lister.AppsV1().DeploymentLister().Deployments(csv.GetNamespace()).Get(desc.DeploymentName)
122164
if k8serrors.IsNotFound(err) || err != nil {
123165
logger.WithField("deployment", desc.DeploymentName).Warnf("expected Deployment could not be retrieved")
124-
return err
166+
errs = append(errs, err)
167+
continue
125168
}
126169
if hash, ok := deployment.Spec.Template.GetAnnotations()[OLMCAHashAnnotationKey]; !ok || hash != caHash {
127170
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)
171+
errs = append(errs, fmt.Errorf("Deployment %s CA cert hash does not match expected", desc.DeploymentName))
172+
continue
129173
}
130174

131175
// Ensure the Deployment's ServiceAccount exists
@@ -136,7 +180,8 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
136180
serviceAccount, err := a.lister.CoreV1().ServiceAccountLister().ServiceAccounts(deployment.GetNamespace()).Get(serviceAccountName)
137181
if err != nil {
138182
logger.WithField("serviceaccount", serviceAccountName).Warnf("could not retrieve ServiceAccount")
139-
return err
183+
errs = append(errs, err)
184+
continue
140185
}
141186

142187
// Ensure RBAC permissions for the APIService are correct
@@ -158,15 +203,17 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
158203
authReaderRole, err := a.lister.RbacV1().RoleLister().Roles("kube-system").Get("extension-apiserver-authentication-reader")
159204
if err != nil {
160205
logger.Warnf("could not retrieve Role extension-apiserver-authentication-reader")
161-
return err
206+
errs = append(errs, err)
207+
continue
162208
}
163209
rulesMap["kube-system"] = append(rulesMap["kube-system"], authReaderRole.Rules...)
164210

165211
// system:auth-delegator
166212
authDelegatorClusterRole, err := a.lister.RbacV1().ClusterRoleLister().Get("system:auth-delegator")
167213
if err != nil {
168214
logger.Warnf("could not retrieve ClusterRole system:auth-delegator")
169-
return err
215+
errs = append(errs, err)
216+
continue
170217
}
171218
rulesMap[metav1.NamespaceAll] = append(rulesMap[metav1.NamespaceAll], authDelegatorClusterRole.Rules...)
172219

@@ -175,17 +222,19 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
175222
satisfied, err := ruleChecker.RuleSatisfied(serviceAccount, namespace, rule)
176223
if err != nil {
177224
logger.WithField("rule", fmt.Sprintf("%+v", rule)).Warnf("error checking Rule")
178-
return err
225+
errs = append(errs, err)
226+
continue
179227
}
180228
if !satisfied {
181229
logger.WithField("rule", fmt.Sprintf("%+v", rule)).Warnf("Rule not satisfied")
182-
return fmt.Errorf("Rule %+v not satisfied", rule)
230+
errs = append(errs, fmt.Errorf("Rule %+v not satisfied", rule))
231+
continue
183232
}
184233
}
185234
}
186235
}
187236

188-
return nil
237+
return errs
189238
}
190239

191240
func (a *Operator) isAPIServiceAvailable(apiService *apiregistrationv1.APIService) bool {
@@ -639,8 +688,14 @@ func (a *Operator) installAPIServiceRequirements(desc v1alpha1.APIServiceDescrip
639688
}
640689
apiService.SetName(apiServiceName)
641690
} else {
691+
owners := []ownerutil.Owner{csv}
692+
// Get replacing CSV
693+
replaces, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace()).Get(csv.Spec.Replaces)
694+
if err == nil {
695+
owners = append(owners, replaces)
696+
}
642697
// check if the APIService is adoptable
643-
if !ownerutil.AdoptableLabels(csv, apiService.GetLabels()) {
698+
if !ownerutil.OwnersIntersect(owners, apiService.GetLabels()) {
644699
return nil, fmt.Errorf("pre-existing APIService %s is not adoptable", apiServiceName)
645700
}
646701
}

0 commit comments

Comments
 (0)