Skip to content

Commit a3a6ff0

Browse files
author
Ali Syed
committed
Add GatewayAPI upgradeable controller with ConfigMap and CRD watches
- Initialize the GatewayAPI upgradeable controller with required imports and constants. - Set up predicates and watches for ConfigMap and CRD updates. - Implement the reconciler logic to manage the admin gate addition and removal. - Add placeholder methods for future logic implementation. - Ensure proper logging and error handling throughout the code.
1 parent 8be1749 commit a3a6ff0

File tree

5 files changed

+291
-0
lines changed

5 files changed

+291
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package gatewayapi_upgradeable
2+
3+
import (
4+
"context"
5+
"fmt"
6+
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
7+
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
8+
corev1 "k8s.io/api/core/v1"
9+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
"sigs.k8s.io/controller-runtime/pkg/cache"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
"sigs.k8s.io/controller-runtime/pkg/controller"
13+
"sigs.k8s.io/controller-runtime/pkg/event"
14+
"sigs.k8s.io/controller-runtime/pkg/handler"
15+
"sigs.k8s.io/controller-runtime/pkg/manager"
16+
"sigs.k8s.io/controller-runtime/pkg/predicate"
17+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
18+
"sigs.k8s.io/controller-runtime/pkg/source"
19+
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
20+
)
21+
22+
const (
23+
controllerName = "gatewayapi_upgradeable_controller"
24+
GatewayAPIAdminKey = "ack-gateway-api-management"
25+
GatewayAPIAdminMsg = "You are now acknowledging that you are taking control of the Gateway API CRD Management from here on out. Please review the documentation for implications."
26+
)
27+
28+
var (
29+
log = logf.Logger.WithName(controllerName)
30+
)
31+
32+
// New function initializes the controller and sets up the watch for the ConfigMap
33+
func New(mgr manager.Manager, config Config) (controller.Controller, error) {
34+
operatorCache := mgr.GetCache()
35+
reconciler := &reconciler{
36+
cache: config.Cache,
37+
client: mgr.GetClient(),
38+
}
39+
40+
//Create a new controller with given reconciler
41+
c, err := controller.New(controllerName, mgr, controller.Options{
42+
Reconciler: reconciler,
43+
})
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
// Define a predicate filter to watch for specific changes in the ConfigMap
49+
adminGateFilter := predicate.Funcs{
50+
CreateFunc: func(e event.CreateEvent) bool {
51+
// Watch for creation of the "admin-gates" ConfigMap in "openshift-config-managed" namespace
52+
configmap := e.Object.(*corev1.ConfigMap)
53+
return configmap.Name == operatorcontroller.AdminGatesConfigMapName().Name && configmap.Namespace == operatorcontroller.AdminGatesConfigMapName().Namespace
54+
},
55+
DeleteFunc: func(deleteEvent event.DeleteEvent) bool { return false }, // Ignore delete events
56+
UpdateFunc: func(e event.UpdateEvent) bool {
57+
// Watch for updates to the "admin-gates" ConfigMap to check if the admin key changes
58+
configmapOld := e.ObjectOld.(*corev1.ConfigMap)
59+
configmapNew := e.ObjectNew.(*corev1.ConfigMap)
60+
if configmapOld.Name != operatorcontroller.AdminGatesConfigMapName().Name || configmapOld.Namespace != operatorcontroller.AdminGatesConfigMapName().Namespace {
61+
return false
62+
}
63+
oldVal, oldExists := configmapOld.Data[GatewayAPIAdminKey]
64+
newVal, newExists := configmapNew.Data[GatewayAPIAdminKey]
65+
return oldExists != newExists || oldVal != newVal
66+
},
67+
GenericFunc: func(genericEvent event.GenericEvent) bool { return false }, // Ignore generic events
68+
}
69+
70+
toAdminGateConfigMap := func(_ context.Context, _ client.Object) []reconcile.Request {
71+
return []reconcile.Request{{
72+
NamespacedName: operatorcontroller.AdminGatesConfigMapName(),
73+
}}
74+
}
75+
// Define the CRD predicate
76+
crdPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
77+
return o.(*apiextensionsv1.CustomResourceDefinition).Spec.Group == gatewayapiv1.GroupName
78+
})
79+
80+
//TODO: too many args to c.watch and not enough to source.Kind
81+
if err := c.Watch(source.Kind(operatorCache, &corev1.ConfigMap{}),
82+
handler.EnqueueRequestsFromMapFunc(toAdminGateConfigMap),
83+
adminGateFilter); err != nil {
84+
return nil, err
85+
}
86+
87+
// Watch for CRD updates using the reconciler cache and apply the CRD predicate
88+
if err := c.Watch(source.Kind(reconciler.cache, &apiextensionsv1.CustomResourceDefinition{}),
89+
&handler.EnqueueRequestForObject{},
90+
crdPredicate); err != nil {
91+
return nil, err
92+
}
93+
return c, nil
94+
}
95+
96+
// reconciler struct holds the client and cache attributes
97+
type reconciler struct {
98+
client client.Client
99+
cache cache.Cache
100+
}
101+
102+
// Config struct holds the cache information
103+
type Config struct {
104+
//namespace referes to namespace where admin gates controller resides
105+
Namespace string
106+
//cache should be a namespace global cache
107+
Cache cache.Cache
108+
}
109+
110+
// Reconcile function implements the logic to check conditions and manage the admin gate
111+
func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
112+
log.Info("reconciling", "request", request)
113+
adminGateConditionExists, err := r.adminGateConditionExists(ctx)
114+
if err != nil {
115+
return reconcile.Result{}, fmt.Errorf("failed to determine if UnservableInFutureVersionsRoute routes exist: %w", err)
116+
}
117+
if adminGateConditionExists {
118+
if err := r.addAdminGate(ctx); err != nil {
119+
return reconcile.Result{}, fmt.Errorf("failed to add admin gate: %w", err)
120+
}
121+
} else {
122+
if err := r.removeAdminGate(ctx); err != nil {
123+
return reconcile.Result{}, fmt.Errorf("failed to remove admin gate: %w", err)
124+
}
125+
}
126+
127+
return reconcile.Result{}, nil
128+
}
129+
130+
// adminGateConditionExists is a placeholder for now,
131+
// TODO: adminGatesConditionExists needs to be implemented....
132+
func (r *reconciler) adminGateConditionExists(ctx context.Context, condition string) (bool, error) {
133+
134+
// Placeholder: Attempt to list ConfigMaps
135+
// Placeholder: Iterate through ConfigMap list and check for the condition (to be implemented).
136+
137+
// Placeholder return statement, to be replaced with actual logic.
138+
return false, nil
139+
}
140+
141+
// addAdminGate function adds the admin gate to the ConfigMap
142+
func (r *reconciler) addAdminGate(ctx context.Context) error {
143+
adminGateConfigMap := &corev1.ConfigMap{}
144+
if err := r.client.Get(ctx, operatorcontroller.AdminGatesConfigMapName(), adminGateConfigMap); err != nil {
145+
return fmt.Errorf("failed to get configmap %s: %w", operatorcontroller.AdminGatesConfigMapName(), err)
146+
}
147+
148+
if adminGateConfigMap.Data == nil {
149+
adminGateConfigMap.Data = map[string]string{}
150+
}
151+
152+
// Check if the admin key exists and is set to the expected message
153+
if val, ok := adminGateConfigMap.Data[GatewayAPIAdminKey]; ok && val == GatewayAPIAdminMsg {
154+
return nil // Exists as expected
155+
}
156+
adminGateConfigMap.Data[GatewayAPIAdminKey] = GatewayAPIAdminMsg
157+
158+
log.Info("Adding admin gate for Gateway API management")
159+
if err := r.client.Update(ctx, adminGateConfigMap); err != nil {
160+
return fmt.Errorf("failed to update configmap %s: %w", operatorcontroller.AdminGatesConfigMapName(), err)
161+
}
162+
return nil
163+
}
164+
165+
// removeAdminGate function removes the admin gate from the ConfigMap
166+
func (r *reconciler) removeAdminGate(ctx context.Context) error {
167+
adminGateConfigMap := &corev1.ConfigMap{}
168+
if err := r.client.Get(ctx, operatorcontroller.AdminGatesConfigMapName(), adminGateConfigMap); err != nil {
169+
return fmt.Errorf("failed to get configmap %s: %w", operatorcontroller.AdminGatesConfigMapName(), err)
170+
}
171+
172+
// Check if the admin key exists
173+
if _, ok := adminGateConfigMap.Data[GatewayAPIAdminKey]; !ok {
174+
return nil // Nothing to do if the key doesn't exist
175+
}
176+
177+
log.Info("Removing admin gate for Gateway API management")
178+
delete(adminGateConfigMap.Data, GatewayAPIAdminKey)
179+
if err := r.client.Update(ctx, adminGateConfigMap); err != nil {
180+
return fmt.Errorf("failed to update configmap %s: %w", operatorcontroller.AdminGatesConfigMapName(), err)
181+
}
182+
return nil
183+
}

pkg/operator/controller/gatewayapi/controller.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ type Config struct {
8383
// resources. The gatewayapi controller starts these controllers once
8484
// the Gateway API CRDs have been created.
8585
DependentControllers []controller.Controller
86+
87+
//AdminConsentRequired indicates
88+
AdminConsentRequired bool
8689
}
8790

8891
// reconciler reconciles gatewayclasses.

pkg/operator/controller/names.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ const (
4949
RemoteWorkerLabel = "node.openshift.io/remote-worker"
5050
)
5151

52+
func AdminGatesConfigMapName() types.NamespacedName {
53+
return types.NamespacedName{
54+
Name: "admin-gates",
55+
Namespace: "openshift-config",
56+
}
57+
}
58+
5259
// IngressClusterOperatorName returns the namespaced name of the ClusterOperator
5360
// resource for the operator.
5461
func IngressClusterOperatorName() types.NamespacedName {

pkg/operator/operator.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package operator
33
import (
44
"context"
55
"fmt"
6+
gatewayapi_upgradeable "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gatewayapi-upgradeable"
67
"time"
78

89
configclient "github.com/openshift/client-go/config/clientset/versioned"
@@ -319,6 +320,13 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro
319320
return nil, fmt.Errorf("failed to create gatewayapi controller: %w", err)
320321
}
321322

323+
//set up the gateway api upgradeable controller
324+
if _, err := gatewayapi_upgradeable.New(mgr, gatewayapi_upgradeable.Config{
325+
Cache: mgr.GetCache(),
326+
}); err != nil {
327+
return nil, fmt.Errorf("failed to create gatewayapi upgradeable controller: %w", err)
328+
}
329+
322330
return &Operator{
323331
manager: mgr,
324332
// TODO: These are only needed for the default ingress controller stuff, which
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"fmt"
9+
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
10+
gatewayapi_upgradeable "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gatewayapi-upgradeable"
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/types"
14+
"testing"
15+
"time"
16+
)
17+
18+
const (
19+
waitForAdminGateInterval = 3 * time.Second
20+
waitForAdminGateTimeout = 3 * time.Minute
21+
)
22+
23+
// TestAdminGateForGatewayAPIManagement tests the gatewayapi_upgradeable control loop by creating and updating the "admin-gates" ConfigMap,
24+
// expecting the Ingress Operator to set and remove the admin gate for Gateway API management.
25+
func TestAdminGateForGatewayAPIManagement(t *testing.T) {
26+
// Ensure admin gate doesn't exist before starting the test.
27+
if err := waitForAdminGate(t, gatewayapi_upgradeable.GatewayAPIAdminKey, false, waitForAdminGateInterval, waitForAdminGateTimeout); err != nil {
28+
t.Fatalf("failed to observe initial admin gate value: %v", err)
29+
}
30+
31+
// Create the "admin-gates" ConfigMap with no data initially.
32+
configMapName := types.NamespacedName{Name: operatorcontroller.AdminGatesConfigMapName().Name, Namespace: operatorcontroller.AdminGatesConfigMapName().Namespace}
33+
configMap := &corev1.ConfigMap{
34+
ObjectMeta: metav1.ObjectMeta{
35+
Name: configMapName.Name,
36+
Namespace: configMapName.Namespace,
37+
},
38+
Data: map[string]string{},
39+
}
40+
if err := kclient.Create(context.Background(), configMap); err != nil {
41+
t.Fatalf("failed to create configmap %s: %v", configMapName, err)
42+
}
43+
t.Cleanup(func() {
44+
if err := kclient.Delete(context.Background(), configMap); err != nil {
45+
t.Fatalf("failed to delete configmap %s: %v", configMapName, err)
46+
}
47+
})
48+
49+
// Poll for the admin gate, and wait until it is added.
50+
if err := waitForAdminGate(t, gatewayapi_upgradeable.GatewayAPIAdminKey, true, waitForAdminGateInterval, waitForAdminGateTimeout); err != nil {
51+
t.Fatalf("admin gate observation error: %v", err)
52+
}
53+
54+
// Resolve the admin gate by removing the key from the ConfigMap.
55+
t.Log("resolving admin gate issue by removing the admin key")
56+
if err := kclient.Get(context.Background(), configMapName, configMap); err != nil {
57+
t.Fatalf("failed to get configmap %s: %v", configMapName, err)
58+
}
59+
delete(configMap.Data, gatewayapi_upgradeable.GatewayAPIAdminKey)
60+
if err := kclient.Update(context.Background(), configMap); err != nil {
61+
t.Fatalf("failed to update configmap %s: %v", configMapName, err)
62+
}
63+
if err := waitForAdminGate(t, gatewayapi_upgradeable.GatewayAPIAdminKey, false, waitForAdminGateInterval, waitForAdminGateTimeout); err != nil {
64+
t.Fatalf("admin gate observation error: %v", err)
65+
}
66+
}
67+
68+
// waitForAdminGate is a helper function to wait for the admin gate to be in the expected state.
69+
func waitForAdminGate(t *testing.T, key string, expected bool, interval, timeout time.Duration) error {
70+
t.Helper()
71+
ticker := time.NewTicker(interval)
72+
defer ticker.Stop()
73+
timeoutCh := time.After(timeout)
74+
75+
for {
76+
select {
77+
case <-ticker.C:
78+
configMap := &corev1.ConfigMap{}
79+
if err := kclient.Get(context.Background(), operatorcontroller.AdminGatesConfigMapName(), configMap); err != nil {
80+
return err
81+
}
82+
_, exists := configMap.Data[key]
83+
if exists == expected {
84+
return nil
85+
}
86+
case <-timeoutCh:
87+
return fmt.Errorf("timeout waiting for admin gate to be %v", expected)
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)