Skip to content

Commit c388bc5

Browse files
benluddyopenshift-cherrypick-robot
authored andcommitted
Make provided API ClusterRoles be owned by the corresponding API.
The existing CSV owner labels lead to a neverending battle for ownership in clusters with more than one CSV providing the same API. The amount of additional work generated by the owner label conflict manifested (not exclusively) as excessive operator installation latency in affected clusters. This conflict is resolved by abandoning CSV owner labels in favor of OwnerReferences to the providing APIService or CustomResourceDefinition. Aggregation of API ClusterRoles to the providing OperatorGroup ClusterRoles is also changed. Instead of labeling API ClusterRoles based on the names of OperatorGroups providing the associated API, each API ClusterRole is labeled based on the name of the API itself. OperatorGroup ClusterRoles accumulate one label selector per provided API.
1 parent e024964 commit c388bc5

File tree

5 files changed

+134
-56
lines changed

5 files changed

+134
-56
lines changed

pkg/api/apis/operators/operatorgroup_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ type OperatorGroupList struct {
7474
}
7575

7676
func (o *OperatorGroup) BuildTargetNamespaces() string {
77-
sort.Strings(o.Status.Namespaces)
78-
return strings.Join(o.Status.Namespaces, ",")
77+
ns := make([]string, len(o.Status.Namespaces))
78+
copy(ns, o.Status.Namespaces)
79+
sort.Strings(ns)
80+
return strings.Join(ns, ",")
7981
}

pkg/api/apis/operators/v1/operatorgroup_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ type OperatorGroupList struct {
7474
}
7575

7676
func (o *OperatorGroup) BuildTargetNamespaces() string {
77-
sort.Strings(o.Status.Namespaces)
78-
return strings.Join(o.Status.Namespaces, ",")
77+
ns := make([]string, len(o.Status.Namespaces))
78+
copy(ns, o.Status.Namespaces)
79+
sort.Strings(ns)
80+
return strings.Join(ns, ",")
7981
}
8082

8183
// IsServiceAccountSpecified returns true if the spec has a service account name specified.

pkg/controller/operators/olm/operator.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ func (a *Operator) operatorGroupFromAnnotations(logger *logrus.Entry, csv *v1alp
10931093
}
10941094

10951095
// Target namespaces don't match
1096-
if targets != strings.Join(operatorGroup.Status.Namespaces, ",") {
1096+
if targets != operatorGroup.BuildTargetNamespaces() {
10971097
logger.Info("olm.targetNamespaces annotation doesn't match operatorgroup status")
10981098
return nil
10991099
}
@@ -1105,21 +1105,21 @@ func (a *Operator) operatorGroupForCSV(csv *v1alpha1.ClusterServiceVersion, logg
11051105
now := a.now()
11061106

11071107
// Attempt to associate an OperatorGroup with the CSV.
1108-
operatorGroups, err := a.client.OperatorsV1().OperatorGroups(csv.GetNamespace()).List(metav1.ListOptions{})
1108+
operatorGroups, err := a.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(csv.GetNamespace()).List(labels.Everything())
11091109
if err != nil {
11101110
logger.Errorf("error occurred while attempting to associate csv with operatorgroup")
11111111
return nil, err
11121112
}
11131113
var operatorGroup *v1.OperatorGroup
11141114

1115-
switch len(operatorGroups.Items) {
1115+
switch len(operatorGroups) {
11161116
case 0:
11171117
err = fmt.Errorf("csv in namespace with no operatorgroups")
11181118
logger.Warn(err)
11191119
csv.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonNoOperatorGroup, err.Error(), now, a.recorder)
11201120
return nil, err
11211121
case 1:
1122-
operatorGroup = &operatorGroups.Items[0]
1122+
operatorGroup = operatorGroups[0]
11231123
logger = logger.WithField("opgroup", operatorGroup.GetName())
11241124
if a.operatorGroupAnnotationsDiffer(&csv.ObjectMeta, operatorGroup) {
11251125
a.setOperatorGroupAnnotations(&csv.ObjectMeta, operatorGroup, true)
@@ -1223,13 +1223,16 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
12231223
}
12241224

12251225
// Check for intersecting provided APIs in intersecting OperatorGroups
1226-
options := metav1.ListOptions{
1227-
FieldSelector: fmt.Sprintf("metadata.name!=%s,metadata.namespace!=%s", operatorGroup.GetName(), operatorGroup.GetNamespace()),
1226+
allGroups, err := a.lister.OperatorsV1().OperatorGroupLister().List(labels.Everything())
1227+
otherGroups := make([]v1.OperatorGroup, 0, len(allGroups))
1228+
for _, g := range allGroups {
1229+
if g.GetName() != operatorGroup.GetName() || g.GetNamespace() != operatorGroup.GetNamespace() {
1230+
otherGroups = append(otherGroups, *g)
1231+
}
12281232
}
1229-
otherGroups, err := a.client.OperatorsV1().OperatorGroups(metav1.NamespaceAll).List(options)
12301233

12311234
groupSurface := resolver.NewOperatorGroup(operatorGroup)
1232-
otherGroupSurfaces := resolver.NewOperatorGroupSurfaces(otherGroups.Items...)
1235+
otherGroupSurfaces := resolver.NewOperatorGroupSurfaces(otherGroups...)
12331236
providedAPIs := operatorSurface.ProvidedAPIs().StripPlural()
12341237

12351238
switch result := a.apiReconciler.Reconcile(providedAPIs, groupSurface, otherGroupSurfaces...); {

pkg/controller/operators/olm/operatorgroup.go

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ import (
1919
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
2020
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
2121
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
22+
"github.com/operator-framework/operator-registry/pkg/registry"
2223
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
2324
)
2425

2526
const (
26-
operatorGroupAggregrationKeyPrefix = "olm.opgroup.permissions/aggregate-to-"
27-
kubeRBACAggregationKeyPrefix = "rbac.authorization.k8s.io/aggregate-to-"
28-
AdminSuffix = "admin"
29-
EditSuffix = "edit"
30-
ViewSuffix = "view"
27+
AdminSuffix = "admin"
28+
EditSuffix = "edit"
29+
ViewSuffix = "view"
3130
)
3231

3332
var (
@@ -42,6 +41,14 @@ var (
4241
}
4342
)
4443

44+
func aggregationLabelFromAPIKey(k opregistry.APIKey, suffix string) (string, error) {
45+
hash, err := resolver.APIKeyToGVKHash(k)
46+
if err != nil {
47+
return "", err
48+
}
49+
return fmt.Sprintf("olm.opgroup.permissions/aggregate-to-%s-%s", hash, suffix), nil
50+
}
51+
4552
func (a *Operator) syncOperatorGroups(obj interface{}) error {
4653
op, ok := obj.(*v1.OperatorGroup)
4754
if !ok {
@@ -115,12 +122,6 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
115122
}
116123
logger.Debug("OperatorGroup CSV annotation completed")
117124

118-
if err := a.ensureOpGroupClusterRoles(op); err != nil {
119-
logger.WithError(err).Warn("failed to ensure operatorgroup clusterroles")
120-
return err
121-
}
122-
logger.Debug("operatorgroup clusterroles ensured")
123-
124125
// Requeue all CSVs that provide the same APIs (including those removed). This notifies conflicting CSVs in
125126
// intersecting groups that their conflict has possibly been resolved, either through resizing or through
126127
// deletion of the conflicting CSV.
@@ -135,6 +136,12 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
135136
providedAPIsForGroup[api] = struct{}{}
136137
}
137138

139+
if err := a.ensureOpGroupClusterRoles(op, providedAPIsForGroup); err != nil {
140+
logger.WithError(err).Warn("failed to ensure operatorgroup clusterroles")
141+
return err
142+
}
143+
logger.Debug("operatorgroup clusterroles ensured")
144+
138145
csvs, err := a.findCSVsThatProvideAnyOf(providedAPIsForGroup)
139146
if err != nil {
140147
logger.WithError(err).Warn("could not find csvs that provide group apis")
@@ -295,32 +302,43 @@ func (a *Operator) pruneProvidedAPIs(group *v1.OperatorGroup, groupProvidedAPIs
295302
}
296303

297304
// ensureProvidedAPIClusterRole ensures that a clusterrole exists (admin, edit, or view) for a single provided API Type
298-
func (a *Operator) ensureProvidedAPIClusterRole(operatorGroup *v1.OperatorGroup, csv *v1alpha1.ClusterServiceVersion, namePrefix, suffix string, verbs []string, group, resource string, resourceNames []string) error {
305+
func (a *Operator) ensureProvidedAPIClusterRole(operatorGroup *v1.OperatorGroup, csv *v1alpha1.ClusterServiceVersion, namePrefix, suffix string, verbs []string, group, resource string, resourceNames []string, api ownerutil.Owner, key opregistry.APIKey) error {
306+
aggregationLabel, err := aggregationLabelFromAPIKey(key, suffix)
307+
if err != nil {
308+
return err
309+
}
299310
clusterRole := &rbacv1.ClusterRole{
300311
ObjectMeta: metav1.ObjectMeta{
301312
Name: namePrefix + suffix,
302313
Labels: map[string]string{
303-
kubeRBACAggregationKeyPrefix + suffix: "true",
304-
operatorGroupAggregrationKeyPrefix + suffix: operatorGroup.GetName(),
314+
// Matches aggregation rules on the bootstrap ClusterRoles.
315+
// https://github.com/kubernetes/kubernetes/blob/61847eab61788fb0543b4cf147773c9da646ed2f/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go#L232
316+
fmt.Sprintf("rbac.authorization.k8s.io/aggregate-to-%s", suffix): "true",
317+
aggregationLabel: "true",
305318
},
319+
OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(api)},
306320
},
307321
Rules: []rbacv1.PolicyRule{{Verbs: verbs, APIGroups: []string{group}, Resources: []string{resource}, ResourceNames: resourceNames}},
308322
}
309-
err := ownerutil.AddOwnerLabels(clusterRole, csv)
310-
if err != nil {
311-
return err
312-
}
313-
existingCR, err := a.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole)
314-
if k8serrors.IsAlreadyExists(err) {
315-
if existingCR != nil && reflect.DeepEqual(existingCR.Labels, clusterRole.Labels) && reflect.DeepEqual(existingCR.Rules, clusterRole.Rules) {
323+
324+
existingCR, err := a.lister.RbacV1().ClusterRoleLister().Get(clusterRole.Name)
325+
if existingCR == nil {
326+
existingCR, err = a.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole)
327+
if err == nil {
316328
return nil
317329
}
318-
if _, err = a.opClient.UpdateClusterRole(clusterRole); err != nil {
319-
a.logger.WithError(err).Errorf("Update existing cluster role failed: %v", clusterRole)
330+
if !k8serrors.IsAlreadyExists(err) {
331+
a.logger.WithError(err).Errorf("Create cluster role failed: %v", clusterRole)
320332
return err
321333
}
322-
} else if err != nil {
323-
a.logger.WithError(err).Errorf("Create cluster role failed: %v", clusterRole)
334+
}
335+
336+
if existingCR != nil && reflect.DeepEqual(existingCR.Rules, clusterRole.Rules) && ownerutil.IsOwnedBy(existingCR, api) && labels.Equals(existingCR.Labels, clusterRole.Labels) {
337+
return nil
338+
}
339+
340+
if _, err := a.opClient.UpdateClusterRole(clusterRole); err != nil {
341+
a.logger.WithError(err).Errorf("Update existing cluster role failed: %v", clusterRole)
324342
return err
325343
}
326344
return nil
@@ -329,27 +347,37 @@ func (a *Operator) ensureProvidedAPIClusterRole(operatorGroup *v1.OperatorGroup,
329347
// ensureClusterRolesForCSV ensures that ClusterRoles for writing and reading provided APIs exist for each operator
330348
func (a *Operator) ensureClusterRolesForCSV(csv *v1alpha1.ClusterServiceVersion, operatorGroup *v1.OperatorGroup) error {
331349
for _, owned := range csv.Spec.CustomResourceDefinitions.Owned {
350+
crd, err := a.lister.APIExtensionsV1beta1().CustomResourceDefinitionLister().Get(owned.Name)
351+
if err != nil {
352+
return fmt.Errorf("crd %q not found: %s", owned.Name, err.Error())
353+
}
332354
nameGroupPair := strings.SplitN(owned.Name, ".", 2) // -> etcdclusters etcd.database.coreos.com
333355
if len(nameGroupPair) != 2 {
334356
return fmt.Errorf("invalid parsing of name '%v', got %v", owned.Name, nameGroupPair)
335357
}
336358
plural := nameGroupPair[0]
337359
group := nameGroupPair[1]
338360
namePrefix := fmt.Sprintf("%s-%s-", owned.Name, owned.Version)
339-
361+
key := registry.APIKey{Group: group, Version: owned.Version, Kind: owned.Kind, Plural: plural}
340362
for suffix, verbs := range VerbsForSuffix {
341-
if err := a.ensureProvidedAPIClusterRole(operatorGroup, csv, namePrefix, suffix, verbs, group, plural, nil); err != nil {
363+
if err := a.ensureProvidedAPIClusterRole(operatorGroup, csv, namePrefix, suffix, verbs, group, plural, nil, crd, key); err != nil {
342364
return err
343365
}
344366
}
345-
if err := a.ensureProvidedAPIClusterRole(operatorGroup, csv, namePrefix+"crd", ViewSuffix, []string{"get"}, "apiextensions.k8s.io", "customresourcedefinitions", []string{owned.Name}); err != nil {
367+
if err := a.ensureProvidedAPIClusterRole(operatorGroup, csv, namePrefix+"crd", ViewSuffix, []string{"get"}, "apiextensions.k8s.io", "customresourcedefinitions", []string{owned.Name}, crd, key); err != nil {
346368
return err
347369
}
348370
}
349371
for _, owned := range csv.Spec.APIServiceDefinitions.Owned {
372+
svcName := strings.Join([]string{owned.Version, owned.Group}, ".")
373+
svc, err := a.lister.APIRegistrationV1().APIServiceLister().Get(svcName)
374+
if err != nil {
375+
return fmt.Errorf("apiservice %q not found: %s", svcName, err.Error())
376+
}
350377
namePrefix := fmt.Sprintf("%s-%s-", owned.Name, owned.Version)
378+
key := registry.APIKey{Group: owned.Group, Version: owned.Version, Kind: owned.Kind}
351379
for suffix, verbs := range VerbsForSuffix {
352-
if err := a.ensureProvidedAPIClusterRole(operatorGroup, csv, namePrefix, suffix, verbs, owned.Group, owned.Name, nil); err != nil {
380+
if err := a.ensureProvidedAPIClusterRole(operatorGroup, csv, namePrefix, suffix, verbs, owned.Group, owned.Name, nil, svc, key); err != nil {
353381
return err
354382
}
355383
}
@@ -853,38 +881,60 @@ func (a *Operator) updateNamespaceList(op *v1.OperatorGroup) ([]string, error) {
853881
return namespaceList, nil
854882
}
855883

856-
func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string) error {
884+
func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, apis resolver.APISet) error {
857885
clusterRole := &rbacv1.ClusterRole{
858886
ObjectMeta: metav1.ObjectMeta{
859887
Name: strings.Join([]string{op.GetName(), suffix}, "-"),
860888
},
861-
AggregationRule: &rbacv1.AggregationRule{
862-
ClusterRoleSelectors: []metav1.LabelSelector{
863-
{
864-
MatchLabels: map[string]string{
865-
operatorGroupAggregrationKeyPrefix + suffix: op.GetName(),
866-
},
867-
},
889+
}
890+
var selectors []metav1.LabelSelector
891+
for api := range apis {
892+
aggregationLabel, err := aggregationLabelFromAPIKey(api, suffix)
893+
if err != nil {
894+
return err
895+
}
896+
selectors = append(selectors, metav1.LabelSelector{
897+
MatchLabels: map[string]string{
898+
aggregationLabel: "true",
868899
},
869-
},
900+
})
901+
}
902+
if len(selectors) > 0 {
903+
clusterRole.AggregationRule = &rbacv1.AggregationRule{
904+
ClusterRoleSelectors: selectors,
905+
}
870906
}
871907
err := ownerutil.AddOwnerLabels(clusterRole, op)
872908
if err != nil {
873909
return err
874910
}
875-
_, err = a.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole)
876-
if k8serrors.IsAlreadyExists(err) {
911+
912+
existingRole, err := a.lister.RbacV1().ClusterRoleLister().Get(clusterRole.Name)
913+
if existingRole == nil {
914+
existingRole, err = a.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole)
915+
if err == nil {
916+
return nil
917+
}
918+
if !k8serrors.IsAlreadyExists(err) {
919+
a.logger.WithError(err).Errorf("Create cluster role failed: %v", clusterRole)
920+
return err
921+
}
922+
}
923+
924+
if existingRole != nil && labels.Equals(existingRole.Labels, clusterRole.Labels) && reflect.DeepEqual(existingRole.AggregationRule, clusterRole.AggregationRule) {
877925
return nil
878-
} else if err != nil {
879-
a.logger.WithError(err).Errorf("Create cluster role failed: %v", clusterRole)
926+
}
927+
928+
if _, err := a.opClient.UpdateClusterRole(clusterRole); err != nil {
929+
a.logger.WithError(err).Errorf("Update existing cluster role failed: %v", clusterRole)
880930
return err
881931
}
882932
return nil
883933
}
884934

885-
func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup) error {
935+
func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis resolver.APISet) error {
886936
for _, suffix := range Suffices {
887-
if err := a.ensureOpGroupClusterRole(op, suffix); err != nil {
937+
if err := a.ensureOpGroupClusterRole(op, suffix, apis); err != nil {
888938
return err
889939
}
890940
}

pkg/lib/ownerutil/util.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import (
99
log "github.com/sirupsen/logrus"
1010
corev1 "k8s.io/api/core/v1"
1111
rbac "k8s.io/api/rbac/v1"
12+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
13+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1214
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1315
"k8s.io/apimachinery/pkg/labels"
1416
"k8s.io/apimachinery/pkg/runtime"
1517
"k8s.io/apimachinery/pkg/runtime/schema"
18+
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
1619
)
1720

1821
const (
@@ -365,6 +368,24 @@ func InferGroupVersionKind(obj runtime.Object) error {
365368
Version: v1.GroupVersion,
366369
Kind: "OperatorGroup",
367370
})
371+
case *apiregistrationv1.APIService:
372+
objectKind.SetGroupVersionKind(schema.GroupVersionKind{
373+
Group: apiregistrationv1.GroupName,
374+
Version: apiregistrationv1.SchemeGroupVersion.Version,
375+
Kind: "APIService",
376+
})
377+
case *apiextensionsv1beta1.CustomResourceDefinition:
378+
objectKind.SetGroupVersionKind(schema.GroupVersionKind{
379+
Group: apiextensionsv1beta1.GroupName,
380+
Version: apiextensionsv1beta1.SchemeGroupVersion.Version,
381+
Kind: "CustomResourceDefinition",
382+
})
383+
case *apiextensionsv1.CustomResourceDefinition:
384+
objectKind.SetGroupVersionKind(schema.GroupVersionKind{
385+
Group: apiextensionsv1.GroupName,
386+
Version: apiextensionsv1.SchemeGroupVersion.Version,
387+
Kind: "CustomResourceDefinition",
388+
})
368389
default:
369390
return fmt.Errorf("could not infer GVK for object: %#v, %#v", obj, objectKind)
370391
}

0 commit comments

Comments
 (0)