Skip to content

Commit f370461

Browse files
Merge pull request #750 from jpeeler/apiservice-sync
fix(olm): add deletion monitoring for api services
2 parents f144c0f + fd97525 commit f370461

File tree

2 files changed

+142
-6
lines changed

2 files changed

+142
-6
lines changed

pkg/controller/operators/olm/operator.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"k8s.io/client-go/tools/cache"
2020
"k8s.io/client-go/tools/record"
2121
"k8s.io/client-go/util/workqueue"
22+
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
2223
kagg "k8s.io/kube-aggregator/pkg/client/informers/externalversions"
2324

2425
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
@@ -53,6 +54,7 @@ type Operator struct {
5354
*queueinformer.Operator
5455
csvQueueSet *queueinformer.ResourceQueueSet
5556
ogQueueSet *queueinformer.ResourceQueueSet
57+
apiSvcQueue workqueue.RateLimitingInterface
5658
client versioned.Interface
5759
resolver install.StrategyResolverInterface
5860
apiReconciler resolver.APIIntersectionReconciler
@@ -166,17 +168,20 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o
166168

167169
// Register APIService QueueInformer
168170
apiServiceInformer := kagg.NewSharedInformerFactory(opClient.ApiregistrationV1Interface(), wakeupInterval).Apiregistration().V1().APIServices()
169-
op.RegisterQueueInformer(queueinformer.NewInformer(
170-
workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "apiservices"),
171+
apiServiceQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "apiservices")
172+
apiServiceQueueInformer := queueinformer.NewInformer(
173+
apiServiceQueue,
171174
apiServiceInformer.Informer(),
172-
op.syncObject,
175+
op.syncAPIService,
173176
&cache.ResourceEventHandlerFuncs{
174177
DeleteFunc: op.handleDeletion,
175178
},
176179
"apiservices",
177180
metrics.NewMetricsNil(),
178181
logger,
179-
))
182+
)
183+
op.RegisterQueueInformer(apiServiceQueueInformer)
184+
op.apiSvcQueue = apiServiceQueue
180185
op.lister.APIRegistrationV1().RegisterAPIServiceLister(apiServiceInformer.Lister())
181186

182187
// Register CustomResourceDefinition QueueInformer
@@ -307,6 +312,46 @@ func NewOperator(logger *logrus.Logger, crClient versioned.Interface, opClient o
307312
return op, nil
308313
}
309314

315+
func (a *Operator) syncAPIService(obj interface{}) (syncError error) {
316+
apiSvc, ok := obj.(*apiregistrationv1.APIService)
317+
if !ok {
318+
a.Log.Debugf("wrong type: %#v", obj)
319+
return fmt.Errorf("casting APIService failed")
320+
}
321+
322+
logger := a.Log.WithFields(logrus.Fields{
323+
"id": queueinformer.NewLoopID(),
324+
"apiSvc": apiSvc.GetName(),
325+
})
326+
logger.Info("syncing APIService")
327+
328+
if name, ns, ok := ownerutil.GetOwnerByKindLabel(apiSvc, v1alpha1.ClusterServiceVersionKind); ok {
329+
_, err := a.lister.CoreV1().NamespaceLister().Get(ns)
330+
if k8serrors.IsNotFound(err) {
331+
logger.Debug("Deleting api service since owning namespace is not found")
332+
syncError = a.OpClient.DeleteAPIService(apiSvc.GetName(), &metav1.DeleteOptions{})
333+
return
334+
}
335+
336+
_, err = a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(ns).Get(name)
337+
if k8serrors.IsNotFound(err) {
338+
logger.Debug("Deleting api service since owning CSV is not found")
339+
syncError = a.OpClient.DeleteAPIService(apiSvc.GetName(), &metav1.DeleteOptions{})
340+
return
341+
} else if err != nil {
342+
syncError = err
343+
return
344+
} else {
345+
if ownerutil.IsOwnedByKindLabel(apiSvc, v1alpha1.ClusterServiceVersionKind) {
346+
logger.Debug("requeueing owner CSVs")
347+
a.requeueOwnerCSVs(apiSvc)
348+
}
349+
}
350+
}
351+
352+
return nil
353+
}
354+
310355
func (a *Operator) syncObject(obj interface{}) (syncError error) {
311356
// Assert as metav1.Object
312357
metaObj, ok := obj.(metav1.Object)

test/e2e/csv_e2e_test.go

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77
"testing"
8+
"time"
89

910
"github.com/stretchr/testify/require"
1011
appsv1 "k8s.io/api/apps/v1"
@@ -17,6 +18,8 @@ import (
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819
"k8s.io/apimachinery/pkg/runtime"
1920
"k8s.io/apimachinery/pkg/util/wait"
21+
"k8s.io/apimachinery/pkg/watch"
22+
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
2023

2124
v1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1"
2225
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
@@ -1245,9 +1248,31 @@ func TestCreateCSVWithOwnedAPIService(t *testing.T) {
12451248
csv.SetName(depName)
12461249

12471250
// Create the APIService CSV
1248-
cleanupCSV, err := createCSV(t, c, crc, csv, testNamespace, false, true)
1251+
cleanupCSV, err := createCSV(t, c, crc, csv, testNamespace, false, false)
12491252
require.NoError(t, err)
1250-
defer cleanupCSV()
1253+
defer func() {
1254+
watcher, err := c.ApiregistrationV1Interface().ApiregistrationV1().APIServices().Watch(metav1.ListOptions{FieldSelector: "metadata.name=" + apiServiceName})
1255+
require.NoError(t, err)
1256+
1257+
deleted := make(chan struct{})
1258+
go func() {
1259+
events := watcher.ResultChan()
1260+
for {
1261+
select {
1262+
case evt := <-events:
1263+
if evt.Type == watch.Deleted {
1264+
deleted <- struct{}{}
1265+
return
1266+
}
1267+
case <-time.After(pollDuration):
1268+
require.FailNow(t, "apiservice not cleaned up after CSV deleted")
1269+
}
1270+
}
1271+
}()
1272+
1273+
cleanupCSV()
1274+
<-deleted
1275+
}()
12511276

12521277
fetchedCSV, err := fetchCSV(t, crc, csv.Name, testNamespace, csvSucceededChecker)
12531278
require.NoError(t, err)
@@ -1748,6 +1773,72 @@ func TestCreateSameCSVWithOwnedAPIServiceMultiNamespace(t *testing.T) {
17481773
require.NoError(t, err)
17491774
}
17501775

1776+
func TestOrphanedAPIServiceCleanUp(t *testing.T) {
1777+
defer cleaner.NotifyTestComplete(t, true)
1778+
1779+
c := newKubeClient(t)
1780+
1781+
mockGroup := fmt.Sprintf("hats.%s.redhat.com", genName(""))
1782+
version := "v1alpha1"
1783+
apiServiceName := strings.Join([]string{version, mockGroup}, ".")
1784+
1785+
apiService := &apiregistrationv1.APIService{
1786+
ObjectMeta: metav1.ObjectMeta{
1787+
Name: apiServiceName,
1788+
},
1789+
Spec: apiregistrationv1.APIServiceSpec{
1790+
Group: mockGroup,
1791+
Version: version,
1792+
GroupPriorityMinimum: 100,
1793+
VersionPriority: 100,
1794+
},
1795+
}
1796+
1797+
watcher, err := c.ApiregistrationV1Interface().ApiregistrationV1().APIServices().Watch(metav1.ListOptions{FieldSelector: "metadata.name=" + apiServiceName})
1798+
require.NoError(t, err)
1799+
1800+
deleted := make(chan struct{})
1801+
quit := make(chan struct{})
1802+
defer close(quit)
1803+
go func() {
1804+
events := watcher.ResultChan()
1805+
for {
1806+
select {
1807+
case <-quit:
1808+
return
1809+
case evt := <-events:
1810+
if evt.Type == watch.Deleted {
1811+
deleted <- struct{}{}
1812+
}
1813+
case <-time.After(pollDuration):
1814+
require.FailNow(t, "orphaned apiservice not cleaned up as expected")
1815+
}
1816+
}
1817+
}()
1818+
1819+
_, err = c.CreateAPIService(apiService)
1820+
require.NoError(t, err, "error creating expected APIService")
1821+
orphanedAPISvc, err := c.GetAPIService(apiServiceName)
1822+
require.NoError(t, err, "error getting expected APIService")
1823+
1824+
newLabels := map[string]string{"olm.owner": "hat-serverfd4r5", "olm.owner.kind": "ClusterServiceVersion", "olm.owner.namespace": "nonexistent-namespace"}
1825+
orphanedAPISvc.SetLabels(newLabels)
1826+
_, err = c.UpdateAPIService(orphanedAPISvc)
1827+
require.NoError(t, err, "error updating APIService")
1828+
<-deleted
1829+
1830+
_, err = c.CreateAPIService(apiService)
1831+
require.NoError(t, err, "error creating expected APIService")
1832+
orphanedAPISvc, err = c.GetAPIService(apiServiceName)
1833+
require.NoError(t, err, "error getting expected APIService")
1834+
1835+
newLabels = map[string]string{"olm.owner": "hat-serverfd4r5", "olm.owner.kind": "ClusterServiceVersion", "olm.owner.namespace": testNamespace}
1836+
orphanedAPISvc.SetLabels(newLabels)
1837+
_, err = c.UpdateAPIService(orphanedAPISvc)
1838+
require.NoError(t, err, "error updating APIService")
1839+
<-deleted
1840+
}
1841+
17511842
func TestUpdateCSVSameDeploymentName(t *testing.T) {
17521843
defer cleaner.NotifyTestComplete(t, true)
17531844

0 commit comments

Comments
 (0)