Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gateway class controller #1995

Merged
merged 3 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common-controller/internal/operator/constant/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
ApplicationController string = "ApplicationController"
Tharsanan1 marked this conversation as resolved.
Show resolved Hide resolved
SubscriptionController string = "SubscriptionController"
ApplicationMappingController string = "ApplicationMappingController"
GatewayClassController string = "GatewayClassController"
)

// API events related constants
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package dp

import (
"context"
"fmt"
"sync"
"time"

"github.com/wso2/apk/adapter/pkg/logging"
loggers "github.com/wso2/apk/common-controller/internal/loggers"
constants "github.com/wso2/apk/common-controller/internal/operator/constant"
"github.com/wso2/apk/common-controller/internal/operator/status"
k8error "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
k8client "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/source"
gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)

const (
gatewayRateLimitPolicyIndex = "gatewayRateLimitPolicyIndex"
gatewayAPIPolicyIndex = "gatewayAPIPolicyIndex"
)

var (
setReadiness sync.Once
wso2APKDefaultControllerName = "wso2.com/apk-gateway-default"
)

// GatewayClassReconciler reconciles a Gateway object
type GatewayClassReconciler struct {
client k8client.Client
statusUpdater *status.UpdateHandler
mgr manager.Manager
}

// NewGatewayClassController creates a new GatewayClass controller instance. GatewayClass Controllers watches for gwapiv1b1.GatewayClass.
func NewGatewayClassController(mgr manager.Manager, statusUpdater *status.UpdateHandler) error {
r := &GatewayClassReconciler{
client: mgr.GetClient(),
statusUpdater: statusUpdater,
mgr: mgr,
}

c, err := controller.New(constants.GatewayClassController, mgr, controller.Options{Reconciler: r})
if err != nil {
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2663, logging.BLOCKER,
"Error creating GatewayClass controller: %v", err.Error()))
return err
}

if err := c.Watch(source.Kind(mgr.GetCache(), &gwapiv1b1.GatewayClass{}), &handler.EnqueueRequestForObject{}); err != nil {
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2639, logging.BLOCKER,
"Error watching GatewayClass resources: %v", err.Error()))
return err
}

loggers.LoggerAPKOperator.Info("GatwayClasses Controller successfully started. Watching GatewayClass Objects...")
return nil
}

//+kubebuilder:rbac:groups=dp.wso2.com,resources=gatewayclasses,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=dp.wso2.com,resources=gatewayclasses/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=dp.wso2.com,resources=gatewayclasses/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile
func (gatewayClassReconciler *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// Check whether the Gateway CR exist, if not consider as a DELETE event.
loggers.LoggerAPKOperator.Info("Reconciling gateway class...")
var gatewayClassDef gwapiv1b1.GatewayClass
if err := gatewayClassReconciler.client.Get(ctx, req.NamespacedName, &gatewayClassDef); err != nil {
if k8error.IsNotFound(err) {
// Gateway deleted. We dont have to handle this
return ctrl.Result{}, nil
}
return ctrl.Result{
RequeueAfter: time.Duration(1 * time.Second),
}, nil
}
// Check whether the gateway class controller name refers to wso2 apk and update the status as accepted, if it is.
controllerName := string(gatewayClassDef.Spec.ControllerName)
if (controllerName == wso2APKDefaultControllerName) {
gatewayClassReconciler.handleGatewayClassStatus(req.NamespacedName, constants.Create, []string{})
} else {
loggers.LoggerAPKOperator.Warnf("Gateway class's controllerName: %s does not match any supported implemenation in WSO2 APK. Hence ignoring the GatewayClass: %s/%s. Supported controller name : %s",
gatewayClassDef.Name, gatewayClassDef.Namespace, controllerName, wso2APKDefaultControllerName)
}
Tharsanan1 marked this conversation as resolved.
Show resolved Hide resolved
return ctrl.Result{}, nil
}

// handleGatewayClassStatus updates the Gateway CR update
func (gatewayClassReconciler *GatewayClassReconciler) handleGatewayClassStatus(gatewayKey types.NamespacedName, state string, events []string) {
accept := false
message := ""

switch state {
case constants.Create:
accept = true
message = "GatewayClass is deployed successfully"
case constants.Update:
accept = true
message = fmt.Sprintf("GatewayClass update is deployed successfully. %v Updated", events)
}
timeNow := metav1.Now()
gatewayClassReconciler.statusUpdater.Send(status.Update{
NamespacedName: gatewayKey,
Resource: new(gwapiv1b1.GatewayClass),
UpdateStatus: func(obj k8client.Object) k8client.Object {
h, ok := obj.(*gwapiv1b1.GatewayClass)
if !ok {
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error3109, logging.BLOCKER, "Error while updating GatewayClass status %v", obj))
}
hCopy := h.DeepCopy()
var gwConditions []metav1.Condition = hCopy.Status.Conditions
gwConditions[0].Status = "Unknown"
if accept {
gwConditions[0].Status = "True"
} else {
gwConditions[0].Status = "False"
}
gwConditions[0].Message = message
gwConditions[0].LastTransitionTime = timeNow
gwConditions[0].Reason = "Reconciled"
gwConditions[0].Type = "Accepted"
generation := hCopy.ObjectMeta.Generation
for i := range gwConditions {
// Assign generation to ObservedGeneration
gwConditions[i].ObservedGeneration = generation
}
hCopy.Status.Conditions = gwConditions
return hCopy
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package dp
import (
"context"
"fmt"
"time"

logger "github.com/sirupsen/logrus"
k8error "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -148,7 +149,12 @@ func (ratelimitReconsiler *RateLimitPolicyReconciler) Reconcile(ctx context.Cont
xds.DeleteCustomRateLimitPolicies(resolveCustomRateLimitPolicy)
xds.UpdateRateLimiterPolicies(conf.CommonController.Server.Label)
}
return ctrl.Result{}, nil
if (k8error.IsNotFound(err)) {
return ctrl.Result{}, nil
}
return ctrl.Result{
RequeueAfter: time.Duration(1 * time.Second),
}, nil
}

if ratelimitPolicy.Spec.Override != nil && ratelimitPolicy.Spec.Override.Custom != nil {
Expand Down
11 changes: 11 additions & 0 deletions common-controller/internal/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
//+kubebuilder:scaffold:imports
"github.com/wso2/apk/common-controller/internal/operator/status"
)

var (
Expand Down Expand Up @@ -154,6 +155,16 @@ func InitOperator() {
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error3117, logging.MAJOR,
"Error creating Application Mapping controller, error: %v", err))
}

updateHandler := status.NewUpdateHandler(mgr.GetClient())
if err := mgr.Add(updateHandler); err != nil {
loggers.LoggerAPKOperator.Errorf("Failed to add status update handler %v", err)
}
if err := dpcontrollers.NewGatewayClassController(mgr, updateHandler); err != nil {
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error3114, logging.MAJOR,
"Error creating GatewayClass controller, error: %v", err))
}

//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
Expand Down
110 changes: 110 additions & 0 deletions common-controller/internal/operator/status/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package status

import (
"context"

"github.com/wso2/apk/common-controller/internal/loggers"
dpv1alpha1 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// Update contain status update event information
type Update struct {
NamespacedName types.NamespacedName
Resource client.Object
UpdateStatus func(client.Object) client.Object
}

// UpdateHandler handles status updates
type UpdateHandler struct {
client client.Client
updateChannel chan Update
}

// NewUpdateHandler get a new status update handler
func NewUpdateHandler(client client.Client) *UpdateHandler {
return &UpdateHandler{
client: client,
updateChannel: make(chan Update, 100),
}
}

// applyUpdate perform the status update on CR
func (updateHandler *UpdateHandler) applyUpdate(update Update) {
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err := updateHandler.client.Get(context.Background(), update.NamespacedName, update.Resource); err != nil {
return err
}

resourceCopy := update.UpdateStatus(update.Resource)
if isStatusEqual(update.Resource, resourceCopy) {
loggers.LoggerAPKOperator.Debugf("Status unchanged, hence not updating. %s", update.NamespacedName.String())
return nil
}
loggers.LoggerAPKOperator.Debugf("Status is updating for %s ...", update.NamespacedName.String())
return updateHandler.client.Status().Update(context.Background(), resourceCopy)
})

if err != nil {
loggers.LoggerAPKOperator.Errorf("Unable to update status for %s, Error : %v ", update.NamespacedName.String(), err)
}
}

// Start starts the status update handler go routine.
func (updateHandler *UpdateHandler) Start(ctx context.Context) error {
loggers.LoggerAPKOperator.Info("Started status update handler")
defer loggers.LoggerAPKOperator.Info("Stopped status update handler")

for {
select {
case update := <-updateHandler.updateChannel:
loggers.LoggerAPKOperator.Debugf("Received a status update in %s", update.NamespacedName.String())
updateHandler.applyUpdate(update)
case <-ctx.Done():
return nil
}
}
}

// Send public method to add status update events to the update channel.
func (updateHandler *UpdateHandler) Send(update Update) {
loggers.LoggerAPKOperator.Debugf("SEND Received a status update in %s", update.NamespacedName.String())
updateHandler.updateChannel <- update
}

// isStatusEqual checks if two objects have equivalent status.
// Supported:
// - API
func isStatusEqual(objA, objB interface{}) bool {
switch a := objA.(type) {
case *dpv1alpha1.API:
if b, ok := objB.(*dpv1alpha1.API); ok {
return compareAPIs(a, b)
}
}
return false
}

// compareAPIs compares status in API CRs.
func compareAPIs(api1 *dpv1alpha1.API, api2 *dpv1alpha1.API) bool {
return api1.Status.DeploymentStatus.Message == api2.Status.DeploymentStatus.Message
}
2 changes: 1 addition & 1 deletion developer/design/crds/examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: Default
name: wso2-apk-default
rules:
- backendRefs:
- group: ""
Expand Down
4 changes: 2 additions & 2 deletions developer/design/crds/sample-gateway.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: default
name: wso2-apk-default
annotations:
cert-manager.io/issuer: selfsigned-issuer
spec:
gatewayClassName: default
gatewayClassName: wso2-apk-default
listeners:
- name: examplelistener
hostname: "*.example.com"
Expand Down
6 changes: 3 additions & 3 deletions developer/tryout/samples/sample-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: default
name: wso2-apk-default
sectionName: httpslistener
rules:
- backendRefs:
Expand Down Expand Up @@ -92,7 +92,7 @@ spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: default
name: wso2-apk-default
sectionName: httpslistener
rules:
- backendRefs:
Expand Down Expand Up @@ -136,7 +136,7 @@ spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: default
name: wso2-apk-default
sectionName: httpslistener
rules:
- backendRefs:
Expand Down
4 changes: 2 additions & 2 deletions developer/tryout/samples/sample-custom-ratelimit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# organization: default
# targetRef:
# kind: Gateway
# name: default
# name: wso2-apk-default
# group: gateway.networking.k8s.io
# ---
# apiVersion: dp.wso2.com/v1alpha1
Expand All @@ -45,5 +45,5 @@
# organization: default
# targetRef:
# kind: Gateway
# name: default
# name: wso2-apk-default
# group: gateway.networking.k8s.io
Loading
Loading