Skip to content

Commit

Permalink
fix(konnect): handle KongRoute conflict on creation
Browse files Browse the repository at this point in the history
  • Loading branch information
pmalek committed Oct 17, 2024
1 parent 3c2e736 commit 1741f27
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 20 deletions.
1 change: 1 addition & 0 deletions controller/konnect/ops/kongroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type RoutesSDK interface {
CreateRoute(ctx context.Context, controlPlaneID string, route sdkkonnectcomp.RouteInput, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateRouteResponse, error)
UpsertRoute(ctx context.Context, req sdkkonnectops.UpsertRouteRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpsertRouteResponse, error)
DeleteRoute(ctx context.Context, controlPlaneID, routeID string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteRouteResponse, error)
ListRoute(ctx context.Context, request sdkkonnectops.ListRouteRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.ListRouteResponse, error)
}
74 changes: 74 additions & 0 deletions controller/konnect/ops/kongroute_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions controller/konnect/ops/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ func Create[
err = createControlPlane(ctx, sdk.GetControlPlaneSDK(), sdk.GetControlPlaneGroupSDK(), cl, ent)
case *configurationv1alpha1.KongService:
err = createService(ctx, sdk.GetServicesSDK(), ent)

// TODO: modify the create* operation wrappers to not set Programmed conditions and return
// a KonnectEntityCreatedButRelationsFailedError if the entity was created but its relations assignment failed.

case *configurationv1alpha1.KongRoute:
err = createRoute(ctx, sdk.GetRoutesSDK(), ent)
case *configurationv1.KongConsumer:
err = createConsumer(ctx, sdk.GetConsumersSDK(), sdk.GetConsumerGroupsSDK(), cl, ent)

// TODO: modify the create* operation wrappers to not set Programmed conditions and return
// a KonnectEntityCreatedButRelationsFailedError if the entity was created but its relations assignment failed.

case *configurationv1beta1.KongConsumerGroup:
err = createConsumerGroup(ctx, sdk.GetConsumerGroupsSDK(), ent)
case *configurationv1alpha1.KongPluginBinding:
Expand Down Expand Up @@ -122,7 +122,7 @@ func Create[

switch {
case errors.As(err, &errConflict) ||
// Service API returns 400 for conflicts
// ControlPlane resource APIs return 400 for conflicts
errSdkBody.Code == 3 && errSdkBody.Message == "data constraint error":
// If there was a conflict on the create request, we can assume the entity already exists.
// We'll get its Konnect ID by listing all entities of its type filtered by the Kubernetes object UID.
Expand All @@ -132,6 +132,8 @@ func Create[
id, err = getControlPlaneForUID(ctx, sdk.GetControlPlaneSDK(), ent)
case *configurationv1alpha1.KongService:
id, err = getKongServiceForUID(ctx, sdk.GetServicesSDK(), ent)
case *configurationv1alpha1.KongRoute:
id, err = getKongRouteForUID(ctx, sdk.GetRoutesSDK(), ent)
// ---------------------------------------------------------------------
// TODO: add other Konnect types
default:
Expand Down
86 changes: 75 additions & 11 deletions controller/konnect/ops/ops_kongroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,54 @@ import (
sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components"
sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations"
sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors"
"github.com/samber/lo"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"

configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1"
)

// getKongRouteForUID returns the Konnect ID of the KongRoute
// that matches the UID of the provided KongRoute.
func getKongRouteForUID(
ctx context.Context,
sdk RoutesSDK,
r *configurationv1alpha1.KongRoute,
) (string, error) {
reqList := sdkkonnectops.ListRouteRequest{
// NOTE: only filter on object's UID.
// Other fields like name might have changed in the meantime but that's OK.
// Those will be enforced via subsequent updates.
Tags: lo.ToPtr(UIDLabelForObject(r)),
ControlPlaneID: r.GetControlPlaneID(),
}

respList, err := sdk.ListRoute(ctx, reqList)
if err != nil {
return "", err
}

if respList == nil || respList.Object == nil {
return "", fmt.Errorf("failed listing %s: %w", r.GetTypeName(), ErrNilResponse)
}

var id string
for _, entry := range respList.Object.Data {
if entry.ID != nil && *entry.ID != "" {
id = *entry.ID
break
}
}

if id == "" {
return "", EntityWithMatchingUIDNotFoundError{
Entity: r,
}
}

return id, nil
}

func createRoute(
ctx context.Context,
sdk RoutesSDK,
Expand All @@ -25,16 +67,18 @@ func createRoute(
}

resp, err := sdk.CreateRoute(ctx, route.Status.Konnect.ControlPlaneID, kongRouteToSDKRouteInput(route))
// TODO: handle already exists
// Can't adopt it as it will cause conflicts between the controller
// that created that entity and already manages it, hm
// that created that entity and already manages it.
// TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460
if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, route); errWrap != nil {
SetKonnectEntityProgrammedConditionFalse(route, "FailedToCreate", errWrap.Error())
return errWrap
}

route.Status.Konnect.SetKonnectID(*resp.Route.ID)
SetKonnectEntityProgrammedCondition(route)
if resp == nil || resp.Route == nil || resp.Route.ID == nil {
return fmt.Errorf("failed creating %s: %w", route.GetTypeName(), ErrNilResponse)
}

route.SetKonnectID(*resp.Route.ID)

return nil
}
Expand All @@ -53,22 +97,42 @@ func updateRoute(
return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", route, client.ObjectKeyFromObject(route))
}

id := route.GetKonnectStatus().GetKonnectID()
_, err := sdk.UpsertRoute(ctx, sdkkonnectops.UpsertRouteRequest{
ControlPlaneID: cpID,
RouteID: route.Status.Konnect.ID,
RouteID: id,
Route: kongRouteToSDKRouteInput(route),
})

// TODO: handle already exists
// Can't adopt it as it will cause conflicts between the controller
// that created that entity and already manages it, hm
// that created that entity and already manages it.
// TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460
if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, route); errWrap != nil {
SetKonnectEntityProgrammedConditionFalse(route, "FailedToUpdate", errWrap.Error())
// Route update operation returns an SDKError instead of a NotFoundError.
var sdkError *sdkkonnecterrs.SDKError
if errors.As(errWrap, &sdkError) {
switch sdkError.StatusCode {
case 404:
logEntityNotFoundRecreating(ctx, route, id)
if err := createRoute(ctx, sdk, route); err != nil {
return FailedKonnectOpError[configurationv1alpha1.KongRoute]{
Op: UpdateOp,
Err: err,
}
}
// Create succeeded, createService sets the status so no need to do this here.
return nil
default:
return FailedKonnectOpError[configurationv1alpha1.KongRoute]{
Op: UpdateOp,
Err: sdkError,
}
}
}

return errWrap
}

SetKonnectEntityProgrammedCondition(route)

return nil
}

Expand Down
6 changes: 3 additions & 3 deletions controller/konnect/ops/ops_kongservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func getKongServiceForUID(
}

if respList == nil || respList.Object == nil {
return "", fmt.Errorf("failed listing KongServices: %w", ErrNilResponse)
return "", fmt.Errorf("failed listing %s: %w", svc.GetTypeName(), ErrNilResponse)
}

var id string
Expand Down Expand Up @@ -115,9 +115,9 @@ func updateService(
},
)

// TODO: handle already exists
// Can't adopt it as it will cause conflicts between the controller
// that created that entity and already manages it, hm
// that created that entity and already manages it.
// TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460
if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, svc); errWrap != nil {
// Service update operation returns an SDKError instead of a NotFoundError.
var sdkError *sdkkonnecterrs.SDKError
Expand Down
Loading

0 comments on commit 1741f27

Please sign in to comment.