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

provisioner: Set SupportedVersion condition on GatewayClass #6147

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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 changelogs/unreleased/6147-sunjayBhatia-small.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Gateway API provisioner now checks `gateway.networking.k8s.io/bundle-version` annotation on Gateway CRDs and sets SupportedVersion status condition on GatewayClass if annotation value matches supported Gateway API version. Best-effort support is provided if version does not match.
8 changes: 8 additions & 0 deletions examples/gateway-provisioner/01-roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ rules:
- list
- update
- watch
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
Expand Down
8 changes: 8 additions & 0 deletions examples/render/contour-gateway-provisioner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19808,6 +19808,14 @@ rules:
- list
- update
- watch
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
Expand Down
139 changes: 92 additions & 47 deletions internal/provisioner/controller/gatewayclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apiextensions_v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
sunjayBhatia marked this conversation as resolved.
Show resolved Hide resolved
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -37,6 +38,11 @@
gatewayapi_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)

const (
gatewayAPIBundleVersionAnnotation = "gateway.networking.k8s.io/bundle-version"
gatewayAPICRDBundleSupportedVersion = "v1.0.0"
sunjayBhatia marked this conversation as resolved.
Show resolved Hide resolved
)

// gatewayClassReconciler reconciles GatewayClass objects.
type gatewayClassReconciler struct {
gatewayController gatewayapi_v1beta1.GatewayController
Expand Down Expand Up @@ -141,19 +147,25 @@
return ctrl.Result{}, nil
}

// Collect various status conditions here so we can update using
// setConditions.
statusConditions := map[string]metav1.Condition{}

statusConditions[string(gatewayapi_v1.GatewayClassConditionStatusSupportedVersion)] = r.getSupportedVersionCondition(ctx)

ok, params, err := r.isValidParametersRef(ctx, gatewayClass.Spec.ParametersRef)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error checking gateway class's parametersRef: %w", err)
}
if !ok {
if err := r.setAcceptedCondition(
ctx,
gatewayClass,
metav1.ConditionFalse,
gatewayapi_v1.GatewayClassReasonInvalidParameters,
"Invalid ParametersRef, must be a reference to an existing namespaced projectcontour.io/ContourDeployment resource",
); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to set gateway class %s Accepted condition: %w", req, err)
statusConditions[string(gatewayapi_v1.GatewayClassConditionStatusAccepted)] = metav1.Condition{
Type: string(gatewayapi_v1.GatewayClassConditionStatusAccepted),
Status: metav1.ConditionFalse,
Reason: string(gatewayapi_v1.GatewayClassReasonInvalidParameters),
Message: "Invalid ParametersRef, must be a reference to an existing namespaced projectcontour.io/ContourDeployment resource",
}
if err := r.setConditions(ctx, gatewayClass, statusConditions); err != nil {
return ctrl.Result{}, err

Check warning on line 168 in internal/provisioner/controller/gatewayclass.go

View check run for this annotation

Codecov / codecov/patch

internal/provisioner/controller/gatewayclass.go#L168

Added line #L168 was not covered by tests
}

return ctrl.Result{}, nil
Expand Down Expand Up @@ -227,70 +239,103 @@
}

if len(invalidParamsMessages) > 0 {
if err := r.setAcceptedCondition(
ctx,
gatewayClass,
metav1.ConditionFalse,
gatewayapi_v1.GatewayClassReasonInvalidParameters,
strings.Join(invalidParamsMessages, "; "),
); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to set gateway class %s Accepted condition: %w", req, err)
statusConditions[string(gatewayapi_v1.GatewayClassConditionStatusAccepted)] = metav1.Condition{
Type: string(gatewayapi_v1.GatewayClassConditionStatusAccepted),
Status: metav1.ConditionFalse,
Reason: string(gatewayapi_v1.GatewayClassReasonInvalidParameters),
Message: strings.Join(invalidParamsMessages, "; "),
}
if err := r.setConditions(ctx, gatewayClass, statusConditions); err != nil {
return ctrl.Result{}, err

Check warning on line 249 in internal/provisioner/controller/gatewayclass.go

View check run for this annotation

Codecov / codecov/patch

internal/provisioner/controller/gatewayclass.go#L249

Added line #L249 was not covered by tests
}

return ctrl.Result{}, nil
}
}

if err := r.setAcceptedCondition(ctx, gatewayClass, metav1.ConditionTrue, gatewayapi_v1.GatewayClassReasonAccepted, "GatewayClass has been accepted by the controller"); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to set gateway class %s Accepted condition: %w", req, err)
statusConditions[string(gatewayapi_v1.GatewayClassConditionStatusAccepted)] = metav1.Condition{
Type: string(gatewayapi_v1.GatewayClassConditionStatusAccepted),
Status: metav1.ConditionTrue,
Reason: string(gatewayapi_v1.GatewayClassReasonAccepted),
Message: "GatewayClass has been accepted by the controller",
}
if err := r.setConditions(ctx, gatewayClass, statusConditions); err != nil {
return ctrl.Result{}, err

Check warning on line 263 in internal/provisioner/controller/gatewayclass.go

View check run for this annotation

Codecov / codecov/patch

internal/provisioner/controller/gatewayclass.go#L263

Added line #L263 was not covered by tests
}

return ctrl.Result{}, nil
}

func (r *gatewayClassReconciler) setAcceptedCondition(
ctx context.Context,
gatewayClass *gatewayapi_v1beta1.GatewayClass,
status metav1.ConditionStatus,
reason gatewayapi_v1beta1.GatewayClassConditionReason,
message string,
) error {
currentAcceptedCondition := metav1.Condition{
Type: string(gatewayapi_v1.GatewayClassConditionStatusAccepted),
Status: status,
ObservedGeneration: gatewayClass.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(reason),
Message: message,
}
var newConds []metav1.Condition
for _, cond := range gatewayClass.Status.Conditions {
if cond.Type == string(gatewayapi_v1.GatewayClassConditionStatusAccepted) {
if cond.Status == status {
func (r *gatewayClassReconciler) setConditions(ctx context.Context, gatewayClass *gatewayapi_v1beta1.GatewayClass, newConds map[string]metav1.Condition) error {
var unchangedConds, updatedConds []metav1.Condition
for _, existing := range gatewayClass.Status.Conditions {
if cond, ok := newConds[existing.Type]; ok {
if existing.Status == cond.Status {
// If status hasn't changed, don't change the condition, just
// update the generation.
currentAcceptedCondition = cond
currentAcceptedCondition.ObservedGeneration = gatewayClass.Generation
changed := existing
changed.ObservedGeneration = gatewayClass.Generation
updatedConds = append(updatedConds, changed)
delete(newConds, cond.Type)
}

continue
} else {
unchangedConds = append(unchangedConds, existing)
}

newConds = append(newConds, cond)
}

r.log.WithValues("gatewayclass-name", gatewayClass.Name).Info(fmt.Sprintf("setting gateway class's Accepted condition to %s", status))
transitionTime := metav1.Now()
for _, c := range newConds {
r.log.WithValues("gatewayclass-name", gatewayClass.Name).Info(fmt.Sprintf("setting gateway class's %s condition to %s", c.Type, c.Status))
c.ObservedGeneration = gatewayClass.Generation
c.LastTransitionTime = transitionTime
updatedConds = append(updatedConds, c)
}

// nolint:gocritic
gatewayClass.Status.Conditions = append(newConds, currentAcceptedCondition)
gatewayClass.Status.Conditions = append(unchangedConds, updatedConds...)

if err := r.client.Status().Update(ctx, gatewayClass); err != nil {
return fmt.Errorf("failed to set gatewayclass %s accepted condition: %w", gatewayClass.Name, err)
return fmt.Errorf("failed to update gateway class %s status: %w", gatewayClass.Name, err)

Check warning on line 298 in internal/provisioner/controller/gatewayclass.go

View check run for this annotation

Codecov / codecov/patch

internal/provisioner/controller/gatewayclass.go#L298

Added line #L298 was not covered by tests
}

return nil
}

func (r *gatewayClassReconciler) getSupportedVersionCondition(ctx context.Context) metav1.Condition {
cond := metav1.Condition{
Type: string(gatewayapi_v1.GatewayClassConditionStatusSupportedVersion),
// Assume false until we get to the happy case.
Status: metav1.ConditionFalse,
Reason: string(gatewayapi_v1.GatewayClassReasonUnsupportedVersion),
}
gatewayClassCRD := &apiextensions_v1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "gatewayclasses." + gatewayapi_v1.GroupName,
sunjayBhatia marked this conversation as resolved.
Show resolved Hide resolved
},
}
if err := r.client.Get(ctx, client.ObjectKeyFromObject(gatewayClassCRD), gatewayClassCRD); err != nil {
errorMsg := "Error fetching gatewayclass CRD resource to validate supported version"
r.log.Error(err, errorMsg)
cond.Message = fmt.Sprintf("%s: %s. Resources will be reconciled with best-effort.", errorMsg, err)
return cond
}

version, ok := gatewayClassCRD.Annotations[gatewayAPIBundleVersionAnnotation]
if !ok {
cond.Message = fmt.Sprintf("Bundle version annotation %s not found. Resources will be reconciled with best-effort.", gatewayAPIBundleVersionAnnotation)
return cond
}
if version != gatewayAPICRDBundleSupportedVersion {
cond.Message = fmt.Sprintf("Gateway API CRD bundle version %s is not supported. Resources will be reconciled with best-effort.", version)
return cond
}

// No errors found, we can return true.
cond.Status = metav1.ConditionTrue
cond.Reason = string(gatewayapi_v1.GatewayClassReasonSupportedVersion)
cond.Message = fmt.Sprintf("Gateway API CRD bundle version %s is supported", gatewayAPICRDBundleSupportedVersion)
sunjayBhatia marked this conversation as resolved.
Show resolved Hide resolved
return cond
}

// isValidParametersRef returns true if the provided ParametersReference is
// to a ContourDeployment resource that exists.
func (r *gatewayClassReconciler) isValidParametersRef(ctx context.Context, ref *gatewayapi_v1beta1.ParametersReference) (bool, *contour_api_v1alpha1.ContourDeployment, error) {
Expand Down
Loading
Loading