Skip to content

Commit 21ac32d

Browse files
Merge pull request #1217 from Miciah/OCPBUGS-54745-status-conditionally-add-CRDs-to-relatedObjects
OCPBUGS-54745: status: Conditionally add CRDs to relatedObjects
2 parents f0e62c3 + 6f42089 commit 21ac32d

File tree

3 files changed

+137
-49
lines changed

3 files changed

+137
-49
lines changed

pkg/operator/controller/status/controller.go

Lines changed: 119 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import (
2424

2525
corev1 "k8s.io/api/core/v1"
2626

27+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2728
"k8s.io/apimachinery/pkg/api/errors"
2829
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/types"
31+
"k8s.io/apimachinery/pkg/util/sets"
3032
utilclock "k8s.io/utils/clock"
3133

3234
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -50,13 +52,27 @@ const (
5052

5153
ingressesEqualConditionMessage = "desired and current number of IngressControllers are equal"
5254

55+
// gatewaysResourceName is the name of the Gateway API gateways CRD.
56+
gatewaysResourceName = "gateways.gateway.networking.k8s.io"
57+
// gatewayclassesResourceName is the name of the Gateway API
58+
// gatewayclasses CRD.
59+
gatewayclassesResourceName = "gatewayclasses.gateway.networking.k8s.io"
60+
// istiosResourceName is the name of the Sail Operator istios CRD.
61+
istiosResourceName = "istios.sailoperator.io"
62+
5363
controllerName = "status_controller"
5464
)
5565

56-
var log = logf.Logger.WithName(controllerName)
66+
var (
67+
log = logf.Logger.WithName(controllerName)
68+
69+
// clock is to enable unit testing
70+
clock utilclock.Clock = utilclock.RealClock{}
5771

58-
// clock is to enable unit testing
59-
var clock utilclock.Clock = utilclock.RealClock{}
72+
// relatedObjectsCRDs is a set of names of CRDs that we add to
73+
// relatedObjects if they exist.
74+
relatedObjectsCRDs = sets.New[string](gatewaysResourceName, gatewayclassesResourceName, istiosResourceName)
75+
)
6076

6177
// New creates the status controller. This is the controller that handles all
6278
// the logic for creating the ClusterOperator operator and updating its status.
@@ -99,15 +115,15 @@ func New(mgr manager.Manager, config Config) (controller.Controller, error) {
99115
return nil, err
100116
}
101117

102-
// If the "GatewayAPI" controller featuregate is enabled, watch
103-
// subscriptions so that this controller can update status when the OSSM
104-
// subscription is created or updated. Note that the subscriptions
105-
// resource only exists if the "OperatorLifecycleManager" capability is
106-
// enabled, so we cannot watch it if the capability is not enabled.
107-
// Additionally, the default catalog only exists if the "marketplace"
108-
// capability is enabled, so we cannot install OSSM without that
109-
// capability.
110-
if config.GatewayAPIEnabled && config.MarketplaceEnabled && config.OperatorLifecycleManagerEnabled {
118+
// If the "GatewayAPI" and "GatewayAPIController" featuregates are
119+
// enabled, watch subscriptions so that this controller can update
120+
// status when the OSSM subscription is created or updated. Note that
121+
// the subscriptions resource only exists if the
122+
// "OperatorLifecycleManager" capability is enabled, so we cannot watch
123+
// it if the capability is not enabled. Additionally, the default
124+
// catalog only exists if the "marketplace" capability is enabled, so we
125+
// cannot install OSSM without that capability.
126+
if config.GatewayAPIEnabled && config.GatewayAPIControllerEnabled && config.MarketplaceEnabled && config.OperatorLifecycleManagerEnabled {
111127
if err := c.Watch(source.Kind[client.Object](operatorCache, &operatorsv1alpha1.Subscription{}, handler.EnqueueRequestsFromMapFunc(toDefaultIngressController), predicate.Funcs{
112128
CreateFunc: func(e event.CreateEvent) bool {
113129
return e.Object.GetNamespace() == operatorcontroller.OpenshiftOperatorNamespace
@@ -124,6 +140,22 @@ func New(mgr manager.Manager, config Config) (controller.Controller, error) {
124140
})); err != nil {
125141
return nil, err
126142
}
143+
if err := c.Watch(source.Kind[client.Object](operatorCache, &apiextensionsv1.CustomResourceDefinition{}, handler.EnqueueRequestsFromMapFunc(toDefaultIngressController), predicate.Funcs{
144+
CreateFunc: func(e event.CreateEvent) bool {
145+
return relatedObjectsCRDs.Has(e.Object.GetName())
146+
},
147+
UpdateFunc: func(e event.UpdateEvent) bool {
148+
return false
149+
},
150+
DeleteFunc: func(e event.DeleteEvent) bool {
151+
return relatedObjectsCRDs.Has(e.Object.GetName())
152+
},
153+
GenericFunc: func(e event.GenericEvent) bool {
154+
return false
155+
},
156+
})); err != nil {
157+
return nil, err
158+
}
127159
}
128160

129161
return c, nil
@@ -133,6 +165,9 @@ func New(mgr manager.Manager, config Config) (controller.Controller, error) {
133165
type Config struct {
134166
// GatewayAPIEnabled indicates that the "GatewayAPI" featuregate is enabled.
135167
GatewayAPIEnabled bool
168+
// GatewayAPIControllerEnabled indicates that the "GatewayAPIController"
169+
// featuregate is enabled.
170+
GatewayAPIControllerEnabled bool
136171
// MarketplaceEnabled indicates whether the "marketplace" capability is
137172
// enabled.
138173
MarketplaceEnabled bool
@@ -183,7 +218,7 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
183218
}
184219
oldStatus := co.Status.DeepCopy()
185220

186-
state, err := r.getOperatorState(ingressNamespace, canaryNamespace, co)
221+
state, err := r.getOperatorState(ctx, ingressNamespace, canaryNamespace, co)
187222
if err != nil {
188223
return reconcile.Result{}, fmt.Errorf("failed to get operator state: %v", err)
189224
}
@@ -220,11 +255,7 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
220255
Name: state.CanaryNamespace.Name,
221256
})
222257
}
223-
if r.config.GatewayAPIEnabled {
224-
related = append(related, configv1.ObjectReference{
225-
Group: gatewayapiv1.GroupName,
226-
Resource: "gatewayclasses",
227-
})
258+
if r.config.GatewayAPIEnabled && r.config.GatewayAPIControllerEnabled {
228259
if state.haveOSSMSubscription {
229260
subscriptionName := operatorcontroller.ServiceMeshOperatorSubscriptionName()
230261
related = append(related, configv1.ObjectReference{
@@ -233,18 +264,25 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
233264
Namespace: subscriptionName.Namespace,
234265
Name: subscriptionName.Name,
235266
})
236-
if state.IngressNamespace != nil {
237-
related = append(related, configv1.ObjectReference{
238-
Group: sailv1.GroupVersion.Group,
239-
Resource: "istios",
240-
Namespace: state.IngressNamespace.Name,
241-
})
242-
related = append(related, configv1.ObjectReference{
243-
Group: gatewayapiv1.GroupName,
244-
Resource: "gateways",
245-
Namespace: state.IngressNamespace.Name,
246-
})
247-
}
267+
}
268+
if state.haveIstiosResource {
269+
related = append(related, configv1.ObjectReference{
270+
Group: sailv1.GroupVersion.Group,
271+
Resource: "istios",
272+
})
273+
}
274+
if state.haveGatewayclassesResource {
275+
related = append(related, configv1.ObjectReference{
276+
Group: gatewayapiv1.GroupName,
277+
Resource: "gatewayclasses",
278+
})
279+
}
280+
if state.haveGatewaysResource {
281+
related = append(related, configv1.ObjectReference{
282+
Group: gatewayapiv1.GroupName,
283+
Resource: "gateways",
284+
Namespace: "", // Include all namespaces.
285+
})
248286
}
249287
}
250288

@@ -317,17 +355,26 @@ type operatorState struct {
317355
IngressControllers []operatorv1.IngressController
318356
DNSRecords []iov1.DNSRecord
319357

320-
haveOSSMSubscription bool
321358
unmanagedGatewayAPICRDNames string
359+
// haveOSSMSubscription means that the subscription for OSSM 3 exists.
360+
haveOSSMSubscription bool
361+
// haveIstiosResource means that the "istios.sailproject.io" CRD exists.
362+
haveIstiosResource bool
363+
// haveGatewaysResource means that the
364+
// "gateways.gateway.networking.k8s.io" CRD exists.
365+
haveGatewaysResource bool
366+
// haveGatewayclassesResource means that the
367+
// "gatewayclasses.gateway.networking.k8s.io" CRD exists.
368+
haveGatewayclassesResource bool
322369
}
323370

324371
// getOperatorState gets and returns the resources necessary to compute the
325372
// operator's current state.
326-
func (r *reconciler) getOperatorState(ingressNamespace, canaryNamespace string, co *configv1.ClusterOperator) (operatorState, error) {
373+
func (r *reconciler) getOperatorState(ctx context.Context, ingressNamespace, canaryNamespace string, co *configv1.ClusterOperator) (operatorState, error) {
327374
state := operatorState{}
328375

329376
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ingressNamespace}}
330-
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ingressNamespace}, ns); err != nil {
377+
if err := r.client.Get(ctx, types.NamespacedName{Name: ingressNamespace}, ns); err != nil {
331378
if !errors.IsNotFound(err) {
332379
return state, fmt.Errorf("failed to get namespace %q: %v", ingressNamespace, err)
333380
}
@@ -336,7 +383,7 @@ func (r *reconciler) getOperatorState(ingressNamespace, canaryNamespace string,
336383
}
337384

338385
ns = &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: canaryNamespace}}
339-
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: canaryNamespace}, ns); err != nil {
386+
if err := r.client.Get(ctx, types.NamespacedName{Name: canaryNamespace}, ns); err != nil {
340387
if !errors.IsNotFound(err) {
341388
return state, fmt.Errorf("failed to get namespace %q: %v", canaryNamespace, err)
342389
}
@@ -345,32 +392,61 @@ func (r *reconciler) getOperatorState(ingressNamespace, canaryNamespace string,
345392
}
346393

347394
ingressList := &operatorv1.IngressControllerList{}
348-
if err := r.cache.List(context.TODO(), ingressList, client.InNamespace(r.config.Namespace)); err != nil {
395+
if err := r.cache.List(ctx, ingressList, client.InNamespace(r.config.Namespace)); err != nil {
349396
return state, fmt.Errorf("failed to list ingresscontrollers in %q: %v", r.config.Namespace, err)
350397
} else {
351398
state.IngressControllers = ingressList.Items
352399
}
353400

354401
if r.config.GatewayAPIEnabled {
355-
if r.config.MarketplaceEnabled && r.config.OperatorLifecycleManagerEnabled {
402+
if len(co.Status.Extension.Raw) > 0 {
403+
extension := &IngressOperatorStatusExtension{}
404+
if err := json.Unmarshal(co.Status.Extension.Raw, extension); err != nil {
405+
return state, fmt.Errorf("failed to unmarshal status extension of cluster operator %q: %w", co.Name, err)
406+
}
407+
state.unmanagedGatewayAPICRDNames = extension.UnmanagedGatewayAPICRDNames
408+
}
409+
410+
if r.config.GatewayAPIControllerEnabled && r.config.MarketplaceEnabled && r.config.OperatorLifecycleManagerEnabled {
356411
var subscription operatorsv1alpha1.Subscription
357412
subscriptionName := operatorcontroller.ServiceMeshOperatorSubscriptionName()
358-
if err := r.cache.Get(context.TODO(), subscriptionName, &subscription); err != nil {
413+
if err := r.cache.Get(ctx, subscriptionName, &subscription); err != nil {
359414
if !errors.IsNotFound(err) {
360415
return state, fmt.Errorf("failed to get subscription %q: %v", subscriptionName, err)
361416
}
362417
} else {
363418
state.haveOSSMSubscription = true
364419

365420
}
366-
}
367421

368-
if len(co.Status.Extension.Raw) > 0 {
369-
extension := &IngressOperatorStatusExtension{}
370-
if err := json.Unmarshal(co.Status.Extension.Raw, extension); err != nil {
371-
return state, fmt.Errorf("failed to unmarshal status extension of cluster operator %q: %w", co.Name, err)
422+
var (
423+
crd apiextensionsv1.CustomResourceDefinition
424+
gatewaysResourceNamespacedName = types.NamespacedName{Name: gatewaysResourceName}
425+
gatewayclassesResourceNamespacedName = types.NamespacedName{Name: gatewayclassesResourceName}
426+
istiosResourceNamespacedName = types.NamespacedName{Name: istiosResourceName}
427+
)
428+
429+
if err := r.cache.Get(ctx, gatewaysResourceNamespacedName, &crd); err != nil {
430+
if !errors.IsNotFound(err) {
431+
return state, fmt.Errorf("failed to get CRD %q: %v", gatewaysResourceName, err)
432+
}
433+
} else {
434+
state.haveGatewaysResource = true
435+
}
436+
if err := r.cache.Get(ctx, gatewayclassesResourceNamespacedName, &crd); err != nil {
437+
if !errors.IsNotFound(err) {
438+
return state, fmt.Errorf("failed to get CRD %q: %v", gatewayclassesResourceName, err)
439+
}
440+
} else {
441+
state.haveGatewayclassesResource = true
442+
}
443+
if err := r.cache.Get(ctx, istiosResourceNamespacedName, &crd); err != nil {
444+
if !errors.IsNotFound(err) {
445+
return state, fmt.Errorf("failed to get CRD %q: %v", istiosResourceName, err)
446+
}
447+
} else {
448+
state.haveIstiosResource = true
372449
}
373-
state.unmanagedGatewayAPICRDNames = extension.UnmanagedGatewayAPICRDNames
374450
}
375451
}
376452

pkg/operator/operator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro
225225
CanaryImage: config.CanaryImage,
226226
OperatorReleaseVersion: config.OperatorReleaseVersion,
227227
GatewayAPIEnabled: gatewayAPIEnabled,
228+
GatewayAPIControllerEnabled: gatewayAPIControllerEnabled,
228229
MarketplaceEnabled: marketplaceEnabled,
229230
OperatorLifecycleManagerEnabled: olmEnabled,
230231
}); err != nil {

test/e2e/operator_test.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,23 @@ func TestClusterOperatorStatusRelatedObjects(t *testing.T) {
233233
if gatewayAPIEnabled, err := isFeatureGateEnabled(features.FeatureGateGatewayAPI); err != nil {
234234
t.Fatalf("Failed to look up %q featuregate: %v", features.FeatureGateGatewayAPI, err)
235235
} else if gatewayAPIEnabled {
236-
expected = append(expected, configv1.ObjectReference{
237-
Group: "gateway.networking.k8s.io",
238-
Resource: "gatewayclasses",
239-
})
240-
// This test runs before TestGatewayAPI, so we do *not* expect
241-
// to see subscriptions, istios, or gateways in relatedObjects.
236+
if gatewayAPIControllerEnabled, err := isFeatureGateEnabled(features.FeatureGateGatewayAPIController); err != nil {
237+
t.Fatalf("Failed to look up %q featuregate: %v", features.FeatureGateGatewayAPIController, err)
238+
} else if gatewayAPIControllerEnabled {
239+
// This test runs before TestGatewayAPI creates the
240+
// subscription to install OSSM, so we do *not* expect
241+
// to see subscriptions or istios in relatedObjects.
242+
// However, we *do* expect to see gatewayclasses and
243+
// gateways whenever the GatewayAPI and
244+
// GatewayAPIController featuregates are enabled.
245+
expected = append(expected, configv1.ObjectReference{
246+
Group: "gateway.networking.k8s.io",
247+
Resource: "gatewayclasses",
248+
}, configv1.ObjectReference{
249+
Group: "gateway.networking.k8s.io",
250+
Resource: "gateways",
251+
})
252+
}
242253
}
243254

244255
coName := controller.IngressClusterOperatorName()

0 commit comments

Comments
 (0)