Skip to content

Commit 52f368d

Browse files
authored
Introduce olmConfig controller (#2466)
This commit introduces a controller for the olmConfig CRD. The olmConfig CRD will be used to configure olm's behavior on cluster. As of today, this CRD introduces the ability for customer to disable copied csvs for operators installed in allNamespace mode. When copied csv are disabled, an event will be created in the operators namespace signaling that it has no copied csvs and that users on the cluster may have difficulty identifying which operators are available in a given namespace. Signed-off-by: Alexander Greene <[email protected]>
1 parent be0c556 commit 52f368d

File tree

3 files changed

+545
-3
lines changed

3 files changed

+545
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
apiVersion: operators.coreos.com/v1
2+
kind: OLMConfig
3+
metadata:
4+
name: cluster

pkg/controller/operators/olm/operator.go

+287-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
rbacv1 "k8s.io/api/rbac/v1"
1515
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
1616
k8serrors "k8s.io/apimachinery/pkg/api/errors"
17+
"k8s.io/apimachinery/pkg/api/meta"
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819
"k8s.io/apimachinery/pkg/labels"
1920
"k8s.io/apimachinery/pkg/runtime"
21+
"k8s.io/apimachinery/pkg/selection"
2022
utilclock "k8s.io/apimachinery/pkg/util/clock"
2123
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2224
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -68,6 +70,7 @@ type Operator struct {
6870
copiedCSVLister operatorsv1alpha1listers.ClusterServiceVersionLister
6971
ogQueueSet *queueinformer.ResourceQueueSet
7072
csvQueueSet *queueinformer.ResourceQueueSet
73+
olmConfigQueue workqueue.RateLimitingInterface
7174
csvCopyQueueSet *queueinformer.ResourceQueueSet
7275
copiedCSVGCQueueSet *queueinformer.ResourceQueueSet
7376
objGCQueueSet *queueinformer.ResourceQueueSet
@@ -124,6 +127,7 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
124127
client: config.externalClient,
125128
ogQueueSet: queueinformer.NewEmptyResourceQueueSet(),
126129
csvQueueSet: queueinformer.NewEmptyResourceQueueSet(),
130+
olmConfigQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "olmConfig"),
127131
csvCopyQueueSet: queueinformer.NewEmptyResourceQueueSet(),
128132
copiedCSVGCQueueSet: queueinformer.NewEmptyResourceQueueSet(),
129133
objGCQueueSet: queueinformer.NewEmptyResourceQueueSet(),
@@ -433,6 +437,26 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
433437
return nil, err
434438
}
435439

440+
// Register QueueInformer for olmConfig
441+
olmConfigInformer := externalversions.NewSharedInformerFactoryWithOptions(
442+
op.client,
443+
config.resyncPeriod(),
444+
).Operators().V1().OLMConfigs().Informer()
445+
olmConfigQueueInformer, err := queueinformer.NewQueueInformer(
446+
ctx,
447+
queueinformer.WithInformer(olmConfigInformer),
448+
queueinformer.WithLogger(op.logger),
449+
queueinformer.WithQueue(op.olmConfigQueue),
450+
queueinformer.WithIndexer(olmConfigInformer.GetIndexer()),
451+
queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncOLMConfig).ToSyncer()),
452+
)
453+
if err != nil {
454+
return nil, err
455+
}
456+
if err := op.RegisterQueueInformer(olmConfigQueueInformer); err != nil {
457+
return nil, err
458+
}
459+
436460
k8sInformerFactory := informers.NewSharedInformerFactory(op.opClient.KubernetesInterface(), config.resyncPeriod())
437461
clusterRoleInformer := k8sInformerFactory.Rbac().V1().ClusterRoles()
438462
op.lister.RbacV1().RegisterClusterRoleLister(clusterRoleInformer.Lister())
@@ -1211,13 +1235,143 @@ func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error)
12111235
return
12121236
}
12131237

1238+
func (a *Operator) allNamespaceOperatorGroups() ([]*v1.OperatorGroup, error) {
1239+
operatorGroups, err := a.lister.OperatorsV1().OperatorGroupLister().List(labels.Everything())
1240+
if err != nil {
1241+
return nil, err
1242+
}
1243+
1244+
result := []*v1.OperatorGroup{}
1245+
for _, operatorGroup := range operatorGroups {
1246+
if NewNamespaceSet(operatorGroup.Status.Namespaces).IsAllNamespaces() {
1247+
result = append(result, operatorGroup)
1248+
}
1249+
}
1250+
return result, nil
1251+
}
1252+
1253+
func (a *Operator) syncOLMConfig(obj interface{}) (syncError error) {
1254+
a.logger.Info("Processing olmConfig")
1255+
olmConfig, ok := obj.(*v1.OLMConfig)
1256+
if !ok {
1257+
return fmt.Errorf("casting OLMConfig failed")
1258+
}
1259+
1260+
// Generate an array of allNamespace OperatorGroups
1261+
allNSOperatorGroups, err := a.allNamespaceOperatorGroups()
1262+
if err != nil {
1263+
return err
1264+
}
1265+
1266+
nonCopiedCSVRequirement, err := labels.NewRequirement(v1alpha1.CopiedLabelKey, selection.DoesNotExist, []string{})
1267+
if err != nil {
1268+
return err
1269+
}
1270+
1271+
csvIsRequeued := false
1272+
for _, og := range allNSOperatorGroups {
1273+
// Get all copied CSVs owned by this operatorGroup
1274+
copiedCSVRequirement, err := labels.NewRequirement(v1alpha1.CopiedLabelKey, selection.Equals, []string{og.GetNamespace()})
1275+
if err != nil {
1276+
return err
1277+
}
1278+
1279+
copiedCSVs, err := a.copiedCSVLister.List(labels.NewSelector().Add(*copiedCSVRequirement))
1280+
if err != nil {
1281+
return err
1282+
}
1283+
1284+
// Filter to unique copies
1285+
uniqueCopiedCSVs := map[string]struct{}{}
1286+
for _, copiedCSV := range copiedCSVs {
1287+
uniqueCopiedCSVs[copiedCSV.GetName()] = struct{}{}
1288+
}
1289+
1290+
csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(og.GetNamespace()).List(labels.NewSelector().Add(*nonCopiedCSVRequirement))
1291+
if err != nil {
1292+
return err
1293+
}
1294+
1295+
for _, csv := range csvs {
1296+
// If the correct number of copied CSVs were found, continue
1297+
if _, ok := uniqueCopiedCSVs[csv.GetName()]; ok == olmConfig.CopiedCSVsAreEnabled() {
1298+
continue
1299+
}
1300+
1301+
if err := a.csvQueueSet.Requeue(csv.GetNamespace(), csv.GetName()); err != nil {
1302+
a.logger.WithError(err).Warn("unable to requeue")
1303+
}
1304+
csvIsRequeued = true
1305+
}
1306+
}
1307+
1308+
// Update the olmConfig status if it has changed.
1309+
condition := getCopiedCSVsCondition(!olmConfig.CopiedCSVsAreEnabled(), csvIsRequeued)
1310+
if !isStatusConditionPresentAndAreTypeReasonMessageStatusEqual(olmConfig.Status.Conditions, condition) {
1311+
meta.SetStatusCondition(&olmConfig.Status.Conditions, condition)
1312+
if _, err := a.client.OperatorsV1().OLMConfigs().UpdateStatus(context.TODO(), olmConfig, metav1.UpdateOptions{}); err != nil {
1313+
return err
1314+
}
1315+
}
1316+
1317+
return nil
1318+
}
1319+
1320+
func isStatusConditionPresentAndAreTypeReasonMessageStatusEqual(conditions []metav1.Condition, condition metav1.Condition) bool {
1321+
foundCondition := meta.FindStatusCondition(conditions, condition.Type)
1322+
if foundCondition == nil {
1323+
return false
1324+
}
1325+
return foundCondition.Type == condition.Type &&
1326+
foundCondition.Reason == condition.Reason &&
1327+
foundCondition.Message == condition.Message &&
1328+
foundCondition.Status == condition.Status
1329+
}
1330+
1331+
func getCopiedCSVsCondition(isDisabled, csvIsRequeued bool) metav1.Condition {
1332+
condition := metav1.Condition{
1333+
Type: v1.DisabledCopiedCSVsConditionType,
1334+
LastTransitionTime: metav1.Now(),
1335+
Status: metav1.ConditionFalse,
1336+
}
1337+
if !isDisabled {
1338+
condition.Reason = "CopiedCSVsEnabled"
1339+
condition.Message = "Copied CSVs are enabled and present accross the cluster"
1340+
if csvIsRequeued {
1341+
condition.Message = "Copied CSVs are enabled and at least one copied CSVs is missing"
1342+
}
1343+
return condition
1344+
}
1345+
1346+
if csvIsRequeued {
1347+
condition.Reason = "CopiedCSVsFound"
1348+
condition.Message = "Copied CSVs are disabled and at least one copied CSV was found for an operator installed in AllNamespace mode"
1349+
return condition
1350+
}
1351+
1352+
condition.Status = metav1.ConditionTrue
1353+
condition.Reason = "NoCopiedCSVsFound"
1354+
condition.Message = "Copied CSVs are disabled and none were found for operators installed in AllNamespace mode"
1355+
1356+
return condition
1357+
}
1358+
12141359
func (a *Operator) syncCopyCSV(obj interface{}) (syncError error) {
12151360
clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion)
12161361
if !ok {
12171362
a.logger.Debugf("wrong type: %#v", obj)
12181363
return fmt.Errorf("casting ClusterServiceVersion failed")
12191364
}
12201365

1366+
olmConfig, err := a.client.OperatorsV1().OLMConfigs().Get(context.TODO(), "cluster", metav1.GetOptions{})
1367+
if err != nil && !k8serrors.IsNotFound(err) {
1368+
return err
1369+
}
1370+
1371+
if err == nil {
1372+
go a.olmConfigQueue.AddAfter(olmConfig, time.Second*5)
1373+
}
1374+
12211375
logger := a.logger.WithFields(logrus.Fields{
12221376
"id": queueinformer.NewLoopID(),
12231377
"csv": clusterServiceVersion.GetName(),
@@ -1239,15 +1393,145 @@ func (a *Operator) syncCopyCSV(obj interface{}) (syncError error) {
12391393
"targetNamespaces": strings.Join(operatorGroup.Status.Namespaces, ","),
12401394
}).Debug("copying csv to targets")
12411395

1396+
copiedCSVsAreEnabled, err := a.copiedCSVsAreEnabled()
1397+
if err != nil {
1398+
return err
1399+
}
1400+
12421401
// Check if we need to do any copying / annotation for the operatorgroup
1243-
if err := a.ensureCSVsInNamespaces(clusterServiceVersion, operatorGroup, NewNamespaceSet(operatorGroup.Status.Namespaces)); err != nil {
1244-
logger.WithError(err).Info("couldn't copy CSV to target namespaces")
1245-
syncError = err
1402+
namespaceSet := NewNamespaceSet(operatorGroup.Status.Namespaces)
1403+
if copiedCSVsAreEnabled || !namespaceSet.IsAllNamespaces() {
1404+
if err := a.ensureCSVsInNamespaces(clusterServiceVersion, operatorGroup, namespaceSet); err != nil {
1405+
logger.WithError(err).Info("couldn't copy CSV to target namespaces")
1406+
syncError = err
1407+
}
1408+
1409+
// If the CSV was installed in AllNamespace mode, remove any "CSV Copying Disabled" events
1410+
// in which the related object's name, namespace, and uid match the given CSV's.
1411+
if namespaceSet.IsAllNamespaces() {
1412+
if err := a.deleteCSVCopyingDisabledEvent(clusterServiceVersion); err != nil {
1413+
return err
1414+
}
1415+
}
1416+
return
1417+
}
1418+
1419+
requirement, err := labels.NewRequirement(v1alpha1.CopiedLabelKey, selection.Equals, []string{clusterServiceVersion.Namespace})
1420+
if err != nil {
1421+
return err
1422+
}
1423+
1424+
copiedCSVs, err := a.copiedCSVLister.List(labels.NewSelector().Add(*requirement))
1425+
if err != nil {
1426+
return err
1427+
}
1428+
1429+
for _, copiedCSV := range copiedCSVs {
1430+
err := a.client.OperatorsV1alpha1().ClusterServiceVersions(copiedCSV.Namespace).Delete(context.TODO(), copiedCSV.Name, metav1.DeleteOptions{})
1431+
if err != nil && !k8serrors.IsNotFound(err) {
1432+
return err
1433+
}
1434+
}
1435+
1436+
if err := a.createCSVCopyingDisabledEvent(clusterServiceVersion); err != nil {
1437+
return err
12461438
}
12471439

12481440
return
12491441
}
12501442

1443+
// copiedCSVsAreEnabled determines if csv copying is enabled for OLM.
1444+
//
1445+
// This method will first attempt to get the "cluster" olmConfig resource,
1446+
// if any error other than "IsNotFound" is encountered, false and the error
1447+
// will be returned.
1448+
//
1449+
// If the "cluster" olmConfig resource is found, the value of
1450+
// olmConfig.spec.features.disableCopiedCSVs will be returned along with a
1451+
// nil error.
1452+
//
1453+
// If the "cluster" olmConfig resource is not found, true will be returned
1454+
// without an error.
1455+
func (a *Operator) copiedCSVsAreEnabled() (bool, error) {
1456+
olmConfig, err := a.client.OperatorsV1().OLMConfigs().Get(context.TODO(), "cluster", metav1.GetOptions{})
1457+
if err != nil {
1458+
// Default to true if olmConfig singleton cannot be found
1459+
if k8serrors.IsNotFound(err) {
1460+
return true, nil
1461+
}
1462+
// If there was an error that wasn't an IsNotFound, return the error
1463+
return false, err
1464+
}
1465+
1466+
// If there was no error, return value based on olmConfig singleton
1467+
return olmConfig.CopiedCSVsAreEnabled(), nil
1468+
}
1469+
1470+
func (a *Operator) getCopiedCSVDisabledEventsForCSV(csv *v1alpha1.ClusterServiceVersion) ([]corev1.Event, error) {
1471+
result := []corev1.Event{}
1472+
if csv == nil {
1473+
return result, nil
1474+
}
1475+
1476+
events, err := a.opClient.KubernetesInterface().CoreV1().Events(csv.GetNamespace()).List(context.TODO(), metav1.ListOptions{})
1477+
if err != nil {
1478+
return nil, err
1479+
}
1480+
1481+
for _, event := range events.Items {
1482+
if event.InvolvedObject.Namespace == csv.GetNamespace() &&
1483+
event.InvolvedObject.Name == csv.GetName() &&
1484+
event.InvolvedObject.UID == csv.GetUID() &&
1485+
event.Reason == v1.DisabledCopiedCSVsConditionType {
1486+
result = append(result, event)
1487+
}
1488+
}
1489+
1490+
return result, nil
1491+
}
1492+
1493+
func (a *Operator) deleteCSVCopyingDisabledEvent(csv *v1alpha1.ClusterServiceVersion) error {
1494+
events, err := a.getCopiedCSVDisabledEventsForCSV(csv)
1495+
if err != nil {
1496+
return err
1497+
}
1498+
1499+
// Remove existing events.
1500+
return a.deleteEvents(events)
1501+
}
1502+
1503+
func (a *Operator) deleteEvents(events []corev1.Event) error {
1504+
for _, event := range events {
1505+
err := a.opClient.KubernetesInterface().EventsV1().Events(event.GetNamespace()).Delete(context.TODO(), event.GetName(), metav1.DeleteOptions{})
1506+
if err != nil && !k8serrors.IsNotFound(err) {
1507+
return err
1508+
}
1509+
}
1510+
return nil
1511+
}
1512+
1513+
func (a *Operator) createCSVCopyingDisabledEvent(csv *v1alpha1.ClusterServiceVersion) error {
1514+
events, err := a.getCopiedCSVDisabledEventsForCSV(csv)
1515+
if err != nil {
1516+
return err
1517+
}
1518+
1519+
if len(events) == 1 {
1520+
return nil
1521+
}
1522+
1523+
// Remove existing events.
1524+
if len(events) > 1 {
1525+
if err := a.deleteEvents(events); err != nil {
1526+
return err
1527+
}
1528+
}
1529+
1530+
a.recorder.Eventf(csv, corev1.EventTypeWarning, v1.DisabledCopiedCSVsConditionType, "CSV copying disabled for %s/%s", csv.GetNamespace(), csv.GetName())
1531+
1532+
return nil
1533+
}
1534+
12511535
func (a *Operator) syncGcCsv(obj interface{}) (syncError error) {
12521536
clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion)
12531537
if !ok {

0 commit comments

Comments
 (0)