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

feat: managed gateways config pushed when accepted #5662

Merged
merged 1 commit into from
Feb 27, 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ Adding a new version? You'll need three changes:

## Unreleased

### Added

- Managed Gateways now trigger gateway reconciliation loops, but do not get their
status updated, they only become part of the configuration published to Kong.
[#5662](https:/Kong/kubernetes-ingress-controller/pull/5662)

### Fixed

- When managed Kong gateways are OSS edition, KIC will not apply licenses to
Expand Down
37 changes: 19 additions & 18 deletions internal/controllers/gateway/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (r *GatewayReconciler) gatewayHasMatchingGatewayClass(obj client.Object) bo
r.Log.Error(err, "Could not retrieve gatewayclass", "gatewayclass", gateway.Spec.GatewayClassName)
return false
}
return isGatewayClassControlledAndUnmanaged(gatewayClass)
return isGatewayClassControlled(gatewayClass)
}

// gatewayClassMatchesController is a watch predicate which filters out events for gatewayclasses which
Expand All @@ -212,7 +212,7 @@ func (r *GatewayReconciler) gatewayClassMatchesController(obj client.Object) boo
)
return false
}
return isGatewayClassControlledAndUnmanaged(gatewayClass)
return isGatewayClassControlled(gatewayClass)
}

// listGatewaysForGatewayClass is a watch predicate which finds all the gateway objects reference
Expand Down Expand Up @@ -278,7 +278,7 @@ func (r *GatewayReconciler) listGatewaysForService(ctx context.Context, svc clie
r.Log.Error(err, "Failed to retrieve gateway class in watch predicates", "gatewayclass", gateway.Spec.GatewayClassName)
return
}
if isGatewayClassControlledAndUnmanaged(gatewayClass) {
if isGatewayClassControlled(gatewayClass) {
recs = append(recs, reconcile.Request{
NamespacedName: k8stypes.NamespacedName{
Namespace: gateway.Namespace,
Expand Down Expand Up @@ -417,37 +417,38 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{Requeue: false}, nil
}

// ensure that the GatewayClass matches the ControllerName and is unmanaged.
// ensure that the GatewayClass matches the ControllerName.
// This check has already been performed by predicates, but we need to ensure this condition
// as the reconciliation loop may be triggered by objects in which predicates we
// cannot check the ControllerName and the unmanaged mode (e.g., ReferenceGrants).
if !isGatewayClassControlledAndUnmanaged(gwc) {
// cannot check the ControllerName (e.g., ReferenceGrants).
if !isGatewayClassControlled(gwc) {
return reconcile.Result{}, nil
}

// reconciliation assumes unmanaged mode, in the future we may have a slot here for
// other gateway management modes.
result, err := r.reconcileUnmanagedGateway(ctx, log, gateway)
// reconcileUnmanagedGateway has side effects and modifies the referenced gateway object. dataplane updates must
// happen afterwards
if err == nil {
if isGatewayClassUnmanaged(gwc.Annotations) {
// The Gateway has to be reconciled by KIC only if it is unmanaged.
if result, err := r.reconcileUnmanagedGateway(ctx, log, gateway); err != nil {
return result, err
}
}

// If the Gateway has been accepted (by KIC or the managing controller), the dataplane update must be performed.
if isGatewayAccepted(gateway) {
if err := r.DataplaneClient.UpdateObject(gateway); err != nil {
debug(log, gateway, "Failed to update object in data-plane, requeueing")
return result, err
return ctrl.Result{}, err
}

referredSecretNames := listSecretNamesReferredByGateway(gateway)
if err := ctrlref.UpdateReferencesToSecret(
ctx, r.Client, r.ReferenceIndexers, r.DataplaneClient,
gateway, referredSecretNames); err != nil {
if apierrors.IsNotFound(err) {
result.Requeue = true
return result, nil
return ctrl.Result{Requeue: true}, nil
}
return result, err
}
}
return result, err
return ctrl.Result{}, nil
}

// reconcileUnmanagedGateway reconciles a Gateway that is configured for unmanaged mode,
Expand Down Expand Up @@ -503,7 +504,7 @@ func (r *GatewayReconciler) reconcileUnmanagedGateway(ctx context.Context, log l

// set the Gateway as scheduled to indicate that validation is complete and reconciliation work
// on the object is ready to begin.
if !isGatewayScheduled(gateway) {
if !isGatewayAccepted(gateway) {
info(log, gateway, "Marking gateway as accepted")
acceptedCondition := metav1.Condition{
Type: string(gatewayapi.GatewayConditionAccepted),
Expand Down
69 changes: 34 additions & 35 deletions internal/controllers/gateway/gateway_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,11 @@ func TestIsGatewayMarkedAsAccepted(t *testing.T) {
}},
},
}
assert.True(t, isGatewayScheduled(scheduledGateway))
assert.True(t, isGatewayAccepted(scheduledGateway))

t.Log("verifying scheduled check for gateway object which has not been scheduled")
unscheduledGateway := &gatewayapi.Gateway{}
assert.False(t, isGatewayScheduled(unscheduledGateway))
assert.False(t, isGatewayAccepted(unscheduledGateway))
}

func TestPruneStatusConditions(t *testing.T) {
Expand Down Expand Up @@ -307,7 +307,7 @@ func TestReconcileGatewaysIfClassMatches(t *testing.T) {
assert.Equal(t, expected, reconcileGatewaysIfClassMatches(gatewayClass, matching))
}

func TestIsGatewayControlledAndUnmanagedMode(t *testing.T) {
func TestIsGatewayControlled(t *testing.T) {
var testControllerName gatewayapi.GatewayController = "acme.io/gateway-controller"

testCases := []struct {
Expand All @@ -316,10 +316,10 @@ func TestIsGatewayControlledAndUnmanagedMode(t *testing.T) {
expectedResult bool
}{
{
name: "uncontrolled managed GatewayClass",
name: "uncontrolled GatewayClass",
GatewayClass: &gatewayapi.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: "uncontrolled-managed",
Name: "uncontrolled",
},
Spec: gatewayapi.GatewayClassSpec{
ControllerName: testControllerName,
Expand All @@ -328,54 +328,53 @@ func TestIsGatewayControlledAndUnmanagedMode(t *testing.T) {
expectedResult: false,
},
{
name: "uncontrolled unmanaged GatewayClass",
name: "controlled GatewayClass",
GatewayClass: &gatewayapi.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: "uncontrolled-unmanaged",
Annotations: map[string]string{
annotations.AnnotationPrefix + annotations.GatewayClassUnmanagedKey: annotations.GatewayClassUnmanagedAnnotationValuePlaceholder,
},
},
Spec: gatewayapi.GatewayClassSpec{
ControllerName: testControllerName,
},
},
expectedResult: false,
},
{
name: "controlled managed GatewayClass",
GatewayClass: &gatewayapi.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: "controlled-managed",
Name: "controlled",
},
Spec: gatewayapi.GatewayClassSpec{
ControllerName: GetControllerName(),
},
},
expectedResult: false,
expectedResult: true,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.expectedResult, isGatewayClassControlled(tc.GatewayClass))
})
}
}

func TestIsGatewayUnmanaged(t *testing.T) {
testCases := []struct {
name string
GatewayClassAnnotations map[string]string
expectedResult bool
}{
{
name: "controlled unmanaged GatewayClass",
GatewayClass: &gatewayapi.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: "controlled-unmanaged",
Annotations: map[string]string{
annotations.AnnotationPrefix + annotations.GatewayClassUnmanagedKey: annotations.GatewayClassUnmanagedAnnotationValuePlaceholder,
},
},
Spec: gatewayapi.GatewayClassSpec{
ControllerName: GetControllerName(),
},
name: "unmanaged GatewayClass",
GatewayClassAnnotations: map[string]string{
annotations.AnnotationPrefix + annotations.GatewayClassUnmanagedKey: annotations.GatewayClassUnmanagedAnnotationValuePlaceholder,
},
expectedResult: true,
},
{
name: "managed GatewayClass",
GatewayClassAnnotations: map[string]string{},
expectedResult: false,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.expectedResult, isGatewayClassControlledAndUnmanaged(tc.GatewayClass))
assert.Equal(t, tc.expectedResult, isGatewayClassUnmanaged(tc.GatewayClassAnnotations))
})
}
}
Expand Down
15 changes: 7 additions & 8 deletions internal/controllers/gateway/gateway_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ func setGatewayCondition(gateway *gatewayapi.Gateway, newCondition metav1.Condit
gateway.Status.Conditions = newConditions
}

// isGatewayScheduled returns boolean whether or not the gateway object was scheduled
// isGatewayAccepted returns boolean whether or not the gateway object was accepted
// previously by the gateway controller.
func isGatewayScheduled(gateway *gatewayapi.Gateway) bool {
func isGatewayAccepted(gateway *gatewayapi.Gateway) bool {
return util.CheckCondition(
gateway.Status.Conditions,
util.ConditionType(gatewayapi.GatewayConditionAccepted),
Expand All @@ -75,18 +75,17 @@ func isGatewayProgrammed(gateway *gatewayapi.Gateway) bool {
// Warning: this function is used for both GatewayClasses and Gateways.
// The former uses "true" as the value, whereas the latter uses "namespace/service" CSVs for the proxy services.

// isObjectUnmanaged returns boolean if the object is configured
// isGatewayClassUnmanaged returns boolean if the object is configured
// for unmanaged mode.
func isObjectUnmanaged(anns map[string]string) bool {
func isGatewayClassUnmanaged(anns map[string]string) bool {
annotationValue := annotations.ExtractUnmanagedGatewayClassMode(anns)
return annotationValue != ""
}

// isGatewayClassControlledAndUnmanaged returns boolean if the GatewayClass
// isGatewayClassControlled returns boolean if the GatewayClass
// is controlled by this controller and is configured for unmanaged mode.
func isGatewayClassControlledAndUnmanaged(gatewayClass *gatewayapi.GatewayClass) bool {
isUnmanaged := isObjectUnmanaged(gatewayClass.Annotations)
return gatewayClass.Spec.ControllerName == GetControllerName() && isUnmanaged
func isGatewayClassControlled(gatewayClass *gatewayapi.GatewayClass) bool {
return gatewayClass.Spec.ControllerName == GetControllerName()
}

// pruneGatewayStatusConds cleans out old status conditions if the Gateway currently has more
Expand Down
4 changes: 2 additions & 2 deletions internal/controllers/gateway/gatewayclass_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (r *GatewayClassReconciler) GatewayClassIsUnmanaged(obj client.Object) bool
return false
}

return isGatewayClassControlledAndUnmanaged(gatewayClass)
return isGatewayClassControlled(gatewayClass)
}

// -----------------------------------------------------------------------------
Expand All @@ -125,7 +125,7 @@ func (r *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request

log.V(util.DebugLevel).Info("Processing gatewayclass", "name", req.Name)

if isGatewayClassControlledAndUnmanaged(gwc) {
if isGatewayClassControlled(gwc) {
alreadyAccepted := util.CheckCondition(
gwc.Status.Conditions,
util.ConditionType(gatewayapi.GatewayClassConditionStatusAccepted),
Expand Down
Loading