Skip to content

Commit 26f8b0f

Browse files
Merge pull request #1115 from anirudhAgniRedhat/OCPBUGS-31550-deleting_SMCP_breaks_Gateway_API
OCPBUGS-31550: Gateway API - recreating SMCP which breaks Gateway API
2 parents edf5e71 + e2c66d3 commit 26f8b0f

File tree

5 files changed

+160
-1
lines changed

5 files changed

+160
-1
lines changed

pkg/operator/controller/gatewayclass/controller.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ package gatewayclass
22

33
import (
44
"context"
5+
"fmt"
6+
"sync"
57

68
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
9+
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
10+
11+
maistrav2 "github.com/maistra/istio-operator/pkg/apis/maistra/v2"
12+
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
713

814
"k8s.io/client-go/tools/record"
915

1016
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
1117

18+
"k8s.io/apimachinery/pkg/types"
1219
utilerrors "k8s.io/apimachinery/pkg/util/errors"
1320

1421
"sigs.k8s.io/controller-runtime/pkg/cache"
@@ -35,6 +42,7 @@ const (
3542
)
3643

3744
var log = logf.Logger.WithName(controllerName)
45+
var gatewayClassController controller.Controller
3846

3947
// NewUnmanaged creates and returns a controller that watches gatewayclasses and
4048
// installs and configures Istio. This is an unmanaged controller, which means
@@ -61,6 +69,15 @@ func NewUnmanaged(mgr manager.Manager, config Config) (controller.Controller, er
6169
if err := c.Watch(source.Kind[client.Object](operatorCache, &gatewayapiv1.GatewayClass{}, &handler.EnqueueRequestForObject{}, isOurGatewayClass, notIstioGatewayClass)); err != nil {
6270
return nil, err
6371
}
72+
73+
isServiceMeshSubscription := predicate.NewPredicateFuncs(func(o client.Object) bool {
74+
return o.GetName() == operatorcontroller.ServiceMeshSubscriptionName().Name
75+
})
76+
if err = c.Watch(source.Kind[client.Object](operatorCache, &operatorsv1alpha1.Subscription{},
77+
enqueueRequestForDefaultGatewayClassController(config.OperandNamespace), isServiceMeshSubscription)); err != nil {
78+
return nil, err
79+
}
80+
gatewayClassController = c
6481
return c, nil
6582
}
6683

@@ -81,6 +98,23 @@ type reconciler struct {
8198
client client.Client
8299
cache cache.Cache
83100
recorder record.EventRecorder
101+
102+
startSMCPWatch sync.Once
103+
}
104+
105+
func enqueueRequestForDefaultGatewayClassController(namespace string) handler.EventHandler {
106+
return handler.EnqueueRequestsFromMapFunc(
107+
func(ctx context.Context, a client.Object) []reconcile.Request {
108+
return []reconcile.Request{
109+
{
110+
NamespacedName: types.NamespacedName{
111+
Namespace: namespace,
112+
Name: OpenShiftDefaultGatewayClassName,
113+
},
114+
},
115+
}
116+
},
117+
)
84118
}
85119

86120
// Reconcile expects request to refer to a GatewayClass and creates or
@@ -95,10 +129,23 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
95129

96130
var errs []error
97131
if _, _, err := r.ensureServiceMeshOperatorSubscription(ctx); err != nil {
98-
errs = append(errs, err)
132+
errs = append(errs, fmt.Errorf("failed to ensure ServiceMeshOperatorSubscription: %w", err))
99133
}
100134
if _, _, err := r.ensureServiceMeshControlPlane(ctx, &gatewayclass); err != nil {
101135
errs = append(errs, err)
136+
} else {
137+
// The OSSM operator installs the maistra.io CRDs, Need to create the SMCP watch after the OSSM operator is installed.
138+
// Using sync.Once here, to start the watch for SMCP, which should run only once.
139+
r.startSMCPWatch.Do(func() {
140+
isOurSMCP := predicate.NewPredicateFuncs(func(o client.Object) bool {
141+
return o.GetName() == operatorcontroller.ServiceMeshControlPlaneName(r.config.OperandNamespace).Name
142+
})
143+
if err = gatewayClassController.Watch(source.Kind[client.Object](r.cache, &maistrav2.ServiceMeshControlPlane{}, enqueueRequestForDefaultGatewayClassController(r.config.OperandNamespace), isOurSMCP)); err != nil {
144+
log.Error(err, "failed to watch ServiceMeshControlPlane", "request", request)
145+
errs = append(errs, err)
146+
}
147+
})
102148
}
149+
103150
return reconcile.Result{}, utilerrors.NewAggregate(errs)
104151
}

pkg/operator/controller/names.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ const (
4747
// Remote worker label, used for node affinity of router deployment.
4848
// Router should not run on remote worker nodes
4949
RemoteWorkerLabel = "node.openshift.io/remote-worker"
50+
51+
// OpenshiftOperatorNamespace is the default namespace for
52+
// the openshift operator resources.
53+
OpenshiftOperatorNamespace = "openshift-operators"
5054
)
5155

5256
// IngressClusterOperatorName returns the namespaced name of the ClusterOperator

pkg/operator/operator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro
147147
operatorcontroller.DefaultOperandNamespace: {},
148148
operatorcontroller.DefaultCanaryNamespace: {},
149149
operatorcontroller.GlobalMachineSpecifiedConfigNamespace: {},
150+
operatorcontroller.OpenshiftOperatorNamespace: {},
150151
},
151152
},
152153
// Use a non-caching client everywhere. The default split client does not

test/e2e/gateway_api_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func testGatewayAPIResources(t *testing.T) {
9999
// - the OSSM Istio operator is installed successfully and has status Running and Ready. e.g. istio-operator-9f5c88857-2xfrr -n openshift-operators
100100
// - Istiod is installed successfully and has status Running and Ready. e.g istiod-openshift-gateway-867bb8d5c7-4z6mp -n openshift-ingress
101101
// - the SMCP is created successfully (OSSM 2.x).
102+
// - deletes SMCP and subscription and tests if it gets recreated
102103
func testGatewayAPIIstioInstallation(t *testing.T) {
103104
t.Helper()
104105

@@ -118,6 +119,22 @@ func testGatewayAPIIstioInstallation(t *testing.T) {
118119
if err := assertSMCP(t); err != nil {
119120
t.Fatalf("failed to find expected SMCP: %v", err)
120121
}
122+
// delete existing SMCP to test it gets recreated
123+
if err := deleteExistingSMCP(t); err != nil {
124+
t.Fatalf("failed to delete existing SMCP: %v", err)
125+
}
126+
// check if SMCP gets recreated
127+
if err := assertSMCP(t); err != nil {
128+
t.Fatalf("failed to find expected SMCP: %v", err)
129+
}
130+
// delete existing Subscription to test it gets recreated
131+
if err := deleteExistingSubscription(t, openshiftOperatorsNamespace, expectedSubscriptionName); err != nil {
132+
t.Fatalf("failed to delete existing Subscription %s: %v", expectedSubscriptionName, err)
133+
}
134+
// checks if subscription gets recreated.
135+
if err := assertSubscription(t, openshiftOperatorsNamespace, expectedSubscriptionName); err != nil {
136+
t.Fatalf("failed to find expected Subscription %s: %v", expectedSubscriptionName, err)
137+
}
121138
}
122139

123140
// testGatewayAPIObjects tests that Gateway API objects can be created successfully.

test/e2e/util_gatewayapi_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,51 @@ func assertSubscription(t *testing.T, namespace, subName string) error {
289289
return err
290290
}
291291

292+
// deleteExistingSubscription deletes if the subscription of the given name exists and returns an error if not.
293+
func deleteExistingSubscription(t *testing.T, namespace, subName string) error {
294+
t.Helper()
295+
existingSubscription := &operatorsv1alpha1.Subscription{}
296+
newSubscription := &operatorsv1alpha1.Subscription{}
297+
nsName := types.NamespacedName{Namespace: namespace, Name: subName}
298+
299+
err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 30*time.Second, false, func(context context.Context) (bool, error) {
300+
if err := kclient.Get(context, nsName, existingSubscription); err != nil {
301+
t.Logf("failed to get Subscription %s: %v", nsName.Name, err)
302+
return false, nil
303+
}
304+
return true, nil
305+
})
306+
if err != nil {
307+
return fmt.Errorf("timed out getting subscription %s: %w", nsName.Name, err)
308+
}
309+
// deleting Subscription.
310+
err = kclient.Delete(context.Background(), existingSubscription)
311+
if err != nil {
312+
return err
313+
}
314+
err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 1*time.Minute, false, func(ctx context.Context) (bool, error) {
315+
if err := kclient.Get(ctx, nsName, newSubscription); err != nil {
316+
if kerrors.IsNotFound(err) {
317+
return true, nil
318+
}
319+
t.Logf("failed to get deleted subscription %s: %v", nsName.Name, err)
320+
return false, nil
321+
}
322+
// if new Subscription got recreated while the poll ensures the Subscription is deleted.
323+
if newSubscription != nil && newSubscription.UID != existingSubscription.UID {
324+
return true, nil
325+
}
326+
t.Logf("Subscription %s still exists", nsName.Name)
327+
return false, nil
328+
})
329+
if err != nil {
330+
return fmt.Errorf("timed out waiting for Subscription %s to be deleted: %v", nsName.Name, err)
331+
}
332+
t.Logf("deleted Subscription %s", nsName.Name)
333+
return nil
334+
335+
}
336+
292337
// assertOSSMOperator checks if the OSSM Istio operator gets successfully installed
293338
// and returns an error if not.
294339
func assertOSSMOperator(t *testing.T) error {
@@ -625,6 +670,51 @@ func assertSMCP(t *testing.T) error {
625670
return err
626671
}
627672

673+
// deleteExistingSMCP deletes if the SMCP exists and returns an error if not.
674+
func deleteExistingSMCP(t *testing.T) error {
675+
t.Helper()
676+
existingSMCP := &maistrav2.ServiceMeshControlPlane{}
677+
newSMCP := &maistrav2.ServiceMeshControlPlane{}
678+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: openshiftSMCPName}
679+
680+
err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 30*time.Second, false, func(context context.Context) (bool, error) {
681+
if err := kclient.Get(context, nsName, existingSMCP); err != nil {
682+
t.Logf("failed to get smcp %s: %v", nsName.Name, err)
683+
return false, nil
684+
}
685+
return true, nil
686+
})
687+
if err != nil {
688+
return fmt.Errorf("timed out getting smcp %s: %w", nsName.Name, err)
689+
}
690+
// deleting SMCP.
691+
err = kclient.Delete(context.Background(), existingSMCP)
692+
if err != nil {
693+
t.Errorf("failed to delete smcp %s: %v", nsName.Name, err)
694+
return err
695+
}
696+
err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 1*time.Minute, false, func(ctx context.Context) (bool, error) {
697+
if err := kclient.Get(ctx, nsName, newSMCP); err != nil {
698+
if kerrors.IsNotFound(err) {
699+
return true, nil
700+
}
701+
t.Logf("failed to get deleted SMCP %s: %v", nsName.Name, err)
702+
return false, nil
703+
}
704+
// if new SMCP got recreated while the poll ensures the SMCP is deleted.
705+
if newSMCP != nil && newSMCP.UID != existingSMCP.UID {
706+
return true, nil
707+
}
708+
t.Logf("smcp %s still exists", nsName.Name)
709+
return false, nil
710+
})
711+
if err != nil {
712+
return fmt.Errorf("timed out waiting for SMCP %s to be deleted: %v", nsName.Name, err)
713+
}
714+
t.Logf("deleted smcp %s", nsName.Name)
715+
return nil
716+
}
717+
628718
// assertDNSRecord checks to make sure a DNSRecord exists in a ready state,
629719
// and returns an error if not.
630720
func assertDNSRecord(t *testing.T, recordName types.NamespacedName) error {

0 commit comments

Comments
 (0)