Skip to content
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
166 changes: 70 additions & 96 deletions controller/gateway/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
operatorv2beta1 "github.com/kong/kong-operator/api/gateway-operator/v2beta1"
konnectv1alpha1 "github.com/kong/kong-operator/api/konnect/v1alpha1"
konnectv1alpha2 "github.com/kong/kong-operator/api/konnect/v1alpha2"
ctrlconsts "github.com/kong/kong-operator/controller/consts"
"github.com/kong/kong-operator/controller/pkg/extensions"
"github.com/kong/kong-operator/controller/pkg/finalizer"
"github.com/kong/kong-operator/controller/pkg/log"
Expand Down Expand Up @@ -236,10 +235,75 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
return ctrl.Result{}, err
}

var konnectExtension *konnectv1alpha2.KonnectExtension
isHybridGateway := isGatewayHybrid(gatewayConfig)
if isHybridGateway {
log.Trace(logger, "Hybrid Gateway provisioning")
konnectControlPlane := r.provisionKonnectGatewayControlPlane(ctx, logger, &gateway, gatewayConfig)
// Set the KonnectGatewayControlPlaneProgrammedType Condition to False. This happens only if:
// * the new status is false and there was no KonnectGatewayControlPlaneProgrammedType condition in the gateway
// * the new status is false and the previous status was true
if condition, found := k8sutils.GetCondition(kcfggateway.KonnectGatewayControlPlaneProgrammedType, gwConditionAware); found && condition.Status != metav1.ConditionTrue {
if condition.Reason == string(kcfgdataplane.UnableToProvisionReason) {
log.Debug(logger, "unable to provision controlplane, requeueing")
return ctrl.Result{Requeue: true}, nil
}

conditionOld, foundOld := k8sutils.GetCondition(kcfggateway.KonnectGatewayControlPlaneProgrammedType, oldGwConditionsAware)
if !foundOld || conditionOld.Status == metav1.ConditionTrue {
gwConditionAware.setProgrammed(metav1.ConditionFalse)
if err := r.patchStatus(ctx, &gateway, oldGateway); err != nil {
return ctrl.Result{}, err
}
log.Debug(logger, "KonnectGatewayControlplane not ready yet")
}
return ctrl.Result{}, nil // requeue will be triggered by the update of the controlplane status
}
// if the controlplane wasn't ready before this reconciliation loop and now is ready, log this event
if !k8sutils.HasConditionTrue(kcfggateway.KonnectGatewayControlPlaneProgrammedType, oldGwConditionsAware) {
log.Debug(logger, "KonnectGatewayControlplane is ready")
}
// This should never happen as the ControlPlane at this point is always != nil.
// Nevertheless, this kind of check makes the Gateway controller bulletproof.
if konnectControlPlane == nil {
return ctrl.Result{}, errors.New("unexpected error, KonnectGatewayControlPlane is nil. Returning to avoid panic")
}

konnectExtension = r.provisionKonnectExtension(ctx, logger, &gateway, konnectControlPlane)
// Set the KonnectExtensionReadyType Condition to False. This happens only if:
// * the new status is false and there was no KonnectExtensionReadyType condition in the gateway
// * the new status is false and the previous status was true
if condition, found := k8sutils.GetCondition(kcfggateway.KonnectExtensionReadyType, gwConditionAware); found && condition.Status != metav1.ConditionTrue {
if condition.Reason == string(kcfgdataplane.UnableToProvisionReason) {
log.Debug(logger, "unable to provision KonnectExtension, requeueing")
return ctrl.Result{Requeue: true}, nil
}

conditionOld, foundOld := k8sutils.GetCondition(kcfggateway.KonnectExtensionReadyType, oldGwConditionsAware)
if !foundOld || conditionOld.Status == metav1.ConditionTrue {
gwConditionAware.setProgrammed(metav1.ConditionFalse)
if err := r.patchStatus(ctx, &gateway, oldGateway); err != nil {
return ctrl.Result{}, err
}
log.Debug(logger, "KonnectExtension not ready yet")
}
return ctrl.Result{}, nil // requeue will be triggered by the update of the controlplane status
}
// if the KonnectExtension wasn't ready before this reconciliation loop and now is ready, log this event
if !k8sutils.HasConditionTrue(kcfggateway.KonnectExtensionReadyType, oldGwConditionsAware) {
log.Debug(logger, "KonnectExtension is ready")
}
// This should never happen as the KonnectExtension at this point is always != nil.
// Nevertheless, this kind of check makes the Gateway controller bulletproof.
if konnectExtension == nil {
return ctrl.Result{}, errors.New("unexpected error, KonnectExtension is nil. Returning to avoid panic")
}
}

// Provision dataplane creates a dataplane and adds the DataPlaneReady=True
// condition to the Gateway status if the dataplane is ready. If not ready
// the status DataPlaneReady=False will be set instead.
dataplane, provisionErr := r.provisionDataPlane(ctx, logger, &gateway, gatewayConfig)
dataplane, provisionErr := r.provisionDataPlane(ctx, logger, &gateway, gatewayConfig, konnectExtension)

// Set the DataPlaneReady Condition to False. This happens only if:
// * the new status is false and there was no DataPlaneReady condition in the old gateway, or
Expand Down Expand Up @@ -369,98 +433,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
if err != nil {
return ctrl.Result{}, err
}

if isGatewayHybrid(gatewayConfig) {
log.Trace(logger, "Type of Gateway - Hybrid")
konnectControlPlane := r.provisionKonnectGatewayControlPlane(ctx, logger, &gateway, gatewayConfig)
// Set the KonnectGatewayControlPlaneProgrammedType Condition to False. This happens only if:
// * the new status is false and there was no KonnectGatewayControlPlaneProgrammedType condition in the gateway
// * the new status is false and the previous status was true
if condition, found := k8sutils.GetCondition(kcfggateway.KonnectGatewayControlPlaneProgrammedType, gwConditionAware); found && condition.Status != metav1.ConditionTrue {
if condition.Reason == string(kcfgdataplane.UnableToProvisionReason) {
log.Debug(logger, "unable to provision controlplane, requeueing")
return ctrl.Result{Requeue: true}, nil
}

conditionOld, foundOld := k8sutils.GetCondition(kcfggateway.KonnectGatewayControlPlaneProgrammedType, oldGwConditionsAware)
if !foundOld || conditionOld.Status == metav1.ConditionTrue {
gwConditionAware.setProgrammed(metav1.ConditionFalse)
if err := r.patchStatus(ctx, &gateway, oldGateway); err != nil {
return ctrl.Result{}, err
}
log.Debug(logger, "KonnectGatewayControlplane not ready yet")
}
return ctrl.Result{}, nil // requeue will be triggered by the update of the controlplane status
}
// if the controlplane wasn't ready before this reconciliation loop and now is ready, log this event
if !k8sutils.HasConditionTrue(kcfggateway.KonnectGatewayControlPlaneProgrammedType, oldGwConditionsAware) {
log.Debug(logger, "KonnectGatewayControlplane is ready")
}
// This should never happen as the ControlPlane at this point is always != nil.
// Nevertheless, this kind of check makes the Gateway controller bulletproof.
if konnectControlPlane == nil {
return ctrl.Result{}, errors.New("unexpected error, KonnectGatewayControlPlane is nil. Returning to avoid panic")
}

konnectExtension := r.provisionKonnectExtension(ctx, logger, &gateway, konnectControlPlane)
// Set the KonnectExtensionReadyType Condition to False. This happens only if:
// * the new status is false and there was no KonnectExtensionReadyType condition in the gateway
// * the new status is false and the previous status was true
if condition, found := k8sutils.GetCondition(kcfggateway.KonnectExtensionReadyType, gwConditionAware); found && condition.Status != metav1.ConditionTrue {
if condition.Reason == string(kcfgdataplane.UnableToProvisionReason) {
log.Debug(logger, "unable to provision KonnectExtension, requeueing")
return ctrl.Result{Requeue: true}, nil
}

conditionOld, foundOld := k8sutils.GetCondition(kcfggateway.KonnectExtensionReadyType, oldGwConditionsAware)
if !foundOld || conditionOld.Status == metav1.ConditionTrue {
gwConditionAware.setProgrammed(metav1.ConditionFalse)
if err := r.patchStatus(ctx, &gateway, oldGateway); err != nil {
return ctrl.Result{}, err
}
log.Debug(logger, "KonnectExtension not ready yet")
}
return ctrl.Result{}, nil // requeue will be triggered by the update of the controlplane status
}
// if the KonnectExtension wasn't ready before this reconciliation loop and now is ready, log this event
if !k8sutils.HasConditionTrue(kcfggateway.KonnectExtensionReadyType, oldGwConditionsAware) {
log.Debug(logger, "KonnectExtension is ready")
}
// This should never happen as the KonnectExtension at this point is always != nil.
// Nevertheless, this kind of check makes the Gateway controller bulletproof.
if konnectExtension == nil {
return ctrl.Result{}, errors.New("unexpected error, KonnectExtension is nil. Returning to avoid panic")
}

// Patch DataPlane with respective KonnectExtension reference.
if err := r.Get(ctx, client.ObjectKeyFromObject(dataplane), dataplane); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get latest dataplane before patching with konnect info: %w", err)
}
if configured := lo.ContainsBy(
dataplane.Spec.Extensions,
func(e commonv1alpha1.ExtensionRef) bool {
return e.Group == konnectExtension.GroupVersionKind().Group &&
e.Kind == konnectExtension.Kind &&
e.Name == konnectExtension.Name
},
); !configured {
dataplane.Spec.Extensions = append(dataplane.Spec.Extensions, commonv1alpha1.ExtensionRef{
Group: konnectExtension.GroupVersionKind().Group,
Kind: konnectExtension.Kind,
NamespacedRef: commonv1alpha1.NamespacedRef{
Name: konnectExtension.Name,
Namespace: &konnectExtension.Namespace,
},
})
if err := r.Update(ctx, dataplane); err != nil {
if k8serrors.IsConflict(err) {
log.Debug(logger, "conflict found when updating DataPlane, retrying")
return ctrl.Result{RequeueAfter: ctrlconsts.RequeueWithoutBackoff}, nil
}
return ctrl.Result{}, fmt.Errorf("failed to update DataPlane with KonnectExtension to make it work as Hybrid: %w", err)
}
}
} else {
if !isHybridGateway {
// Provision controlplane creates a controlplane and adds the ControlPlaneReady condition to the Gateway status
// if the controlplane is ready, the ControlPlaneReady status is set to true, otherwise false.
controlplane := r.provisionControlPlane(ctx, logger, &gateway, gatewayConfig)
Expand Down Expand Up @@ -544,6 +517,7 @@ func (r *Reconciler) provisionDataPlane(
logger logr.Logger,
gateway *gwtypes.Gateway,
gatewayConfig *GatewayConfiguration,
konnectExtension *konnectv1alpha2.KonnectExtension,
) (*operatorv1beta1.DataPlane, error) {
logger = logger.WithName("dataplaneProvisioning")

Expand Down Expand Up @@ -577,7 +551,7 @@ func (r *Reconciler) provisionDataPlane(
return nil, err
}
if count == 0 {
dataplane, err := r.createDataPlane(ctx, gateway, gatewayConfig)
dataplane, err := r.createDataPlane(ctx, gateway, gatewayConfig, konnectExtension)
if err != nil {
errWrap := fmt.Errorf("dataplane creation failed - error: %w", err)
k8sutils.SetCondition(
Expand Down Expand Up @@ -614,7 +588,7 @@ func (r *Reconciler) provisionDataPlane(
return nil, errWrap
}

expectedDataPlaneOptions.Extensions = extensions.MergeExtensionsForDataPlane(gatewayConfig.Spec.Extensions, expectedDataPlaneOptions.Extensions)
expectedDataPlaneOptions.Extensions = extensions.MergeExtensionsForDataPlane(gatewayConfig.Spec.Extensions, konnectExtension)

if !dataPlaneSpecDeepEqual(&dataplane.Spec.DataPlaneOptions, expectedDataPlaneOptions, isGatewayHybrid(gatewayConfig)) {
log.Trace(logger, "dataplane config is out of date")
Expand Down
3 changes: 2 additions & 1 deletion controller/gateway/controller_reconciler_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (r *Reconciler) createDataPlane(
ctx context.Context,
gateway *gwtypes.Gateway,
gatewayConfig *GatewayConfiguration,
konnectExtension *konnectv1alpha2.KonnectExtension,
) (*operatorv1beta1.DataPlane, error) {
dataplane := &operatorv1beta1.DataPlane{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -66,7 +67,7 @@ func (r *Reconciler) createDataPlane(
return nil, err
}

dataplane.Spec.Extensions = extensions.MergeExtensions(gatewayConfig.Spec.Extensions, dataplane)
dataplane.Spec.Extensions = extensions.MergeExtensionsForDataPlane(gatewayConfig.Spec.Extensions, konnectExtension)

k8sutils.SetOwnerForObject(dataplane, gateway)
gatewayutils.LabelObjectAsGatewayManaged(dataplane)
Expand Down
3 changes: 2 additions & 1 deletion controller/pkg/extensions/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
operatorv2beta1 "github.com/kong/kong-operator/api/gateway-operator/v2beta1"
kcfgkonnect "github.com/kong/kong-operator/api/konnect"
extensionserrors "github.com/kong/kong-operator/controller/pkg/extensions/errors"
"github.com/kong/kong-operator/controller/pkg/extensions/processor"
"github.com/kong/kong-operator/controller/pkg/patch"
gwtypes "github.com/kong/kong-operator/internal/types"
k8sutils "github.com/kong/kong-operator/pkg/utils/kubernetes"
Expand Down Expand Up @@ -47,7 +48,7 @@ type Extendable interface {
// of an API server error), the resource should be requeued. For misconfiguration errors, the resource does not
// need to be requeued, and feedback is provided via resource status conditions.
// - err: an error in case of failure.
func ApplyExtensions[t ExtendableT](ctx context.Context, cl client.Client, o t, konnectEnabled bool, processor Processor) (stop bool, res ctrl.Result, err error) {
func ApplyExtensions[t ExtendableT](ctx context.Context, cl client.Client, o t, konnectEnabled bool, processor processor.Processor) (stop bool, res ctrl.Result, err error) {
// extensionsCondition can be nil. In that case, no extensions are referenced by the object.
extensionsCondition := validateExtensions(o)
if extensionsCondition == nil {
Expand Down
4 changes: 2 additions & 2 deletions controller/pkg/extensions/konnect/controlplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

konnectv1alpha2 "github.com/kong/kong-operator/api/konnect/v1alpha2"
"github.com/kong/kong-operator/controller/pkg/extensions"
"github.com/kong/kong-operator/controller/pkg/extensions/processor"
managercfg "github.com/kong/kong-operator/ingress-controller/pkg/manager/config"
gwtypes "github.com/kong/kong-operator/internal/types"
)
Expand All @@ -29,7 +29,7 @@ type ControlPlaneKonnectExtensionProcessor struct {
}

// Compile-time check to ensure ControlPlaneKonnectExtensionProcessor implements the extensions.ExtensionProcessor interface.
var _ extensions.Processor = (*ControlPlaneKonnectExtensionProcessor)(nil)
var _ processor.Processor = (*ControlPlaneKonnectExtensionProcessor)(nil)

// Process extracts the KonnectExtension from the ControlPlane and generates the KonnectExtensionConfig.
// It returns true if a KonnectExtension was found and processed, false otherwise.
Expand Down
6 changes: 3 additions & 3 deletions controller/pkg/extensions/konnect/dataplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

operatorv1beta1 "github.com/kong/kong-operator/api/gateway-operator/v1beta1"
konnectv1alpha2 "github.com/kong/kong-operator/api/konnect/v1alpha2"
"github.com/kong/kong-operator/controller/pkg/extensions"
"github.com/kong/kong-operator/controller/pkg/extensions/processor"
"github.com/kong/kong-operator/internal/utils/config"
"github.com/kong/kong-operator/pkg/consts"
k8sutils "github.com/kong/kong-operator/pkg/utils/kubernetes"
Expand All @@ -21,8 +21,8 @@ import (
// DataPlaneKonnectExtensionProcessor processes Konnect extensions for DataPlane resources.
type DataPlaneKonnectExtensionProcessor struct{}

// Compile-time check to ensure DataPlaneKonnectExtensionProcessor implements the extensions.ExtensionProcessor interface.
var _ extensions.Processor = (*DataPlaneKonnectExtensionProcessor)(nil)
// Compile-time check to ensure DataPlaneKonnectExtensionProcessor implements the processor.Processor interface.
var _ processor.Processor = (*DataPlaneKonnectExtensionProcessor)(nil)

// Process gets the DataPlane as argument, and in case it references a KonnectExtension, it
// fetches the referenced extension and applies the necessary changes to the DataPlane spec.
Expand Down
23 changes: 23 additions & 0 deletions controller/pkg/extensions/konnect/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package konnect

import (
"github.com/samber/lo"

commonv1alpha1 "github.com/kong/kong-operator/api/common/v1alpha1"
konnectv1alpha2 "github.com/kong/kong-operator/api/konnect/v1alpha2"
)

// KonnectExtensionToExtensionRef converts a KonnectExtension to the corresponding ExtensionRef.
func KonnectExtensionToExtensionRef(extension *konnectv1alpha2.KonnectExtension) *commonv1alpha1.ExtensionRef {
if extension == nil {
return nil
}
return &commonv1alpha1.ExtensionRef{
Group: konnectv1alpha2.SchemeGroupVersion.Group,
Kind: konnectv1alpha2.KonnectExtensionKind,
NamespacedRef: commonv1alpha1.NamespacedRef{
Name: extension.Name,
Namespace: lo.ToPtr(extension.Namespace),
},
}
}
76 changes: 76 additions & 0 deletions controller/pkg/extensions/konnect/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package konnect

import (
"testing"

"github.com/samber/lo"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

commonv1alpha1 "github.com/kong/kong-operator/api/common/v1alpha1"
konnectv1alpha2 "github.com/kong/kong-operator/api/konnect/v1alpha2"
)

func TestKonnectExtensionToExtensionRef(t *testing.T) {
tests := []struct {
name string
extension *konnectv1alpha2.KonnectExtension
want *commonv1alpha1.ExtensionRef
}{
{
name: "nil extension returns nil",
extension: nil,
want: nil,
},
{
name: "converts extension with name and namespace",
extension: &konnectv1alpha2.KonnectExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "test-extension",
Namespace: "default",
},
},
want: &commonv1alpha1.ExtensionRef{
Group: konnectv1alpha2.SchemeGroupVersion.Group,
Kind: konnectv1alpha2.KonnectExtensionKind,
NamespacedRef: commonv1alpha1.NamespacedRef{
Name: "test-extension",
Namespace: lo.ToPtr("default"),
},
},
},
{
name: "converts extension with additional fields (only name and namespace used)",
extension: &konnectv1alpha2.KonnectExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "complex-extension",
Namespace: "production",
Labels: map[string]string{
"app": "test",
},
Annotations: map[string]string{
"description": "test extension",
},
},
Spec: konnectv1alpha2.KonnectExtensionSpec{
// Fields here should not affect the conversion
},
},
want: &commonv1alpha1.ExtensionRef{
Group: konnectv1alpha2.SchemeGroupVersion.Group,
Kind: konnectv1alpha2.KonnectExtensionKind,
NamespacedRef: commonv1alpha1.NamespacedRef{
Name: "complex-extension",
Namespace: lo.ToPtr("production"),
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := KonnectExtensionToExtensionRef(tt.extension)
assert.Equal(t, tt.want, got)
})
}
}
Loading
Loading