Skip to content

Commit

Permalink
Set SupportedVersion on GatewayClass (#1301)
Browse files Browse the repository at this point in the history
Problem: NGF does not set the SupportedVersion condition 
on the GatewayClass.

Solution: Set the SupportedVersion condition on the GatewayClass.

This PR adds a new controller that watches the metadata of CRDs.
CRD events are filtered using a custom predicate that inspects the 
gateway.networking.k8s.io/bundle-version annotation. CRDs without 
this annotation will be ignored. Updates to this annotation will trigger 
a state change and build a new graph. This required some changes to 
how we determine if the state has changed in the changeTrackingUpdater.
  • Loading branch information
kate-osborn authored Dec 5, 2023
1 parent d27dde5 commit d6bbdba
Show file tree
Hide file tree
Showing 35 changed files with 1,434 additions and 218 deletions.
7 changes: 7 additions & 0 deletions conformance/provisioner/provisioner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ rules:
- gatewayclasses/status
verbs:
- update
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
Expand Down
7 changes: 7 additions & 0 deletions deploy/helm-chart/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ rules:
- get
- update
{{- end }}
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down
7 changes: 7 additions & 0 deletions deploy/manifests/nginx-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ rules:
- create
- get
- update
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- list
- watch
---
# Source: nginx-gateway-fabric/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
Expand Down
22 changes: 12 additions & 10 deletions docs/developer/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ To create a new release, follow these steps:
3. Test the main branch for release-readiness. For that, use the `edge` containers, which are built from the main
branch, and the [example applications](/examples).
4. If a problem is found, prepare a fix PR, merge it into the main branch and return to the previous step.
5. Create a release branch with a name that follows the `release-X.Y` format.
6. Prepare and merge a PR into the release branch to update the repo files for the release:
5. If the supported Gateway API minor version has changed since the last release, test NGINX Gateway Fabric with the previous version of the Gateway API CRDs.
6. If a compatibility issue is found, add a note to the release notes explaining that the previous version is not supported.
7. Create a release branch following the `release-X.Y` naming convention.
8. Prepare and merge a PR into the release branch to update the repo files for the release:
1. Update the Helm [Chart.yaml](/deploy/helm-chart/Chart.yaml): the `appVersion` to `X.Y.Z`, the icon and source
URLs to point at `vX.Y.Z`, and bump the `version`.
2. Adjust the `VERSION` variable in the [Makefile](/Makefile) and the `TAG` in the
Expand All @@ -52,17 +54,17 @@ To create a new release, follow these steps:
draft of the full changelog. This draft can be found under
the [GitHub releases](https://github.com/nginxinc/nginx-gateway-fabric/releases) after the release branch is
created. Use the previous changelog entries for formatting and content guidance.
7. Create and push the release tag in the format `vX.Y.Z`. As a result, the CI/CD pipeline will:
9. Create and push the release tag in the format `vX.Y.Z`. As a result, the CI/CD pipeline will:
- Build NGF container images with the release tag `X.Y.Z` and push it to the registry.
- Package and publish the Helm chart to the registry.
- Create a GitHub release with an autogenerated changelog and attached release artifacts.
8. Prepare and merge a PR into the main branch to update the [README](/README.md) to include the information about
the latest release and also the [changelog](/CHANGELOG.md). Also update any installation instructions to ensure
that the supported Gateway API and NGF versions are correct. Specifically, helm README and `site/content/includes/installation/install-gateway-api-resources.md`.
9. Close the issue created in Step 1.
10. Ensure that the [associated milestone](https://github.com/nginxinc/nginx-gateway-fabric/milestones) is closed.
11. Verify that published artifacts in the release can be installed properly.
12. Submit the `conformance-profile.yaml` artifact from the release to the [Gateway API repo](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports).
10. Prepare and merge a PR into the main branch to update the [README](/README.md) to include the information about
the latest release and also the [changelog](/CHANGELOG.md). Also update any installation instructions to ensure
that the supported Gateway API and NGF versions are correct. Specifically, helm README and `site/content/includes/installation/install-gateway-api-resources.md`.
11. Close the issue created in Step 1.
12. Ensure that the [associated milestone](https://github.com/nginxinc/nginx-gateway-fabric/milestones) is closed.
13. Verify that published artifacts in the release can be installed properly.
14. Submit the `conformance-profile.yaml` artifact from the release to the [Gateway API repo](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports).
- Create a fork of the repository
- Name the file `nginxinc-nginx-gateway-fabric.yaml` and set `gatewayAPIVersion` in the file to the
supported version by NGF. Also update the site source if necessary (see following example).
Expand Down
85 changes: 84 additions & 1 deletion internal/framework/conditions/conditions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package conditions

import (
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "sigs.k8s.io/gateway-api/apis/v1"
)
Expand All @@ -24,7 +26,40 @@ type Condition struct {
Message string
}

// NewDefaultGatewayClassConditions returns the default Conditions that must be present in the status of a GatewayClass.
// DeduplicateConditions removes duplicate conditions based on the condition type.
// The last condition wins. The order of conditions is preserved.
func DeduplicateConditions(conds []Condition) []Condition {
type elem struct {
cond Condition
reverseIdx int
}

uniqueElems := make(map[string]elem)

idx := 0
for i := len(conds) - 1; i >= 0; i-- {
if _, exist := uniqueElems[conds[i].Type]; exist {
continue
}

uniqueElems[conds[i].Type] = elem{
cond: conds[i],
reverseIdx: idx,
}
idx++
}

result := make([]Condition, len(uniqueElems))

for _, el := range uniqueElems {
result[len(result)-el.reverseIdx-1] = el.cond
}

return result
}

// NewDefaultGatewayClassConditions returns Conditions that indicate that the GatewayClass is accepted and that the
// Gateway API CRD versions are supported.
func NewDefaultGatewayClassConditions() []Condition {
return []Condition{
{
Expand All @@ -33,6 +68,54 @@ func NewDefaultGatewayClassConditions() []Condition {
Reason: string(v1.GatewayClassReasonAccepted),
Message: "GatewayClass is accepted",
},
{
Type: string(v1.GatewayClassConditionStatusSupportedVersion),
Status: metav1.ConditionTrue,
Reason: string(v1.GatewayClassReasonSupportedVersion),
Message: "Gateway API CRD versions are supported",
},
}
}

// NewGatewayClassSupportedVersionBestEffort returns a Condition that indicates that the GatewayClass is accepted,
// but the Gateway API CRD versions are not supported. This means NGF will attempt to generate configuration,
// but it does not guarantee support.
func NewGatewayClassSupportedVersionBestEffort(recommendedVersion string) []Condition {
return []Condition{
{
Type: string(v1.GatewayClassConditionStatusSupportedVersion),
Status: metav1.ConditionFalse,
Reason: string(v1.GatewayClassReasonUnsupportedVersion),
Message: fmt.Sprintf(
"Gateway API CRD versions are not recommended. Recommended version is %s",
recommendedVersion,
),
},
}
}

// NewGatewayClassUnsupportedVersion returns Conditions that indicate that the GatewayClass is not accepted because
// the Gateway API CRD versions are not supported. NGF will not generate configuration in this case.
func NewGatewayClassUnsupportedVersion(recommendedVersion string) []Condition {
return []Condition{
{
Type: string(v1.GatewayClassConditionStatusAccepted),
Status: metav1.ConditionFalse,
Reason: string(v1.GatewayClassReasonUnsupportedVersion),
Message: fmt.Sprintf(
"Gateway API CRD versions are not supported. Please install version %s",
recommendedVersion,
),
},
{
Type: string(v1.GatewayClassConditionStatusSupportedVersion),
Status: metav1.ConditionFalse,
Reason: string(v1.GatewayClassReasonUnsupportedVersion),
Message: fmt.Sprintf(
"Gateway API CRD versions are not supported. Please install version %s",
recommendedVersion,
),
},
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import (

. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions"
)

func TestDeduplicateConditions(t *testing.T) {
g := NewWithT(t)

conds := []conditions.Condition{
conds := []Condition{
{
Type: "Type1",
Status: metav1.ConditionTrue,
Expand Down Expand Up @@ -40,7 +38,7 @@ func TestDeduplicateConditions(t *testing.T) {
},
}

expected := []conditions.Condition{
expected := []Condition{
{
Type: "Type1",
Status: metav1.ConditionFalse,
Expand Down
2 changes: 1 addition & 1 deletion internal/framework/controller/index/endpointslice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestServiceNameIndexFunc(t *testing.T) {
func TestServiceNameIndexFuncPanics(t *testing.T) {
defer func() {
g := NewWithT(t)
g.Expect(recover()).ShouldNot(BeNil())
g.Expect(recover()).ToNot(BeNil())
}()

ServiceNameIndexFunc(&v1.Namespace{})
Expand Down
39 changes: 39 additions & 0 deletions internal/framework/controller/predicate/annotation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package predicate

import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// AnnotationPredicate implements a predicate function based on the Annotation.
//
// This predicate will skip the following events:
// 1. Create events that do not contain the Annotation.
// 2. Update events where the Annotation value has not changed.
type AnnotationPredicate struct {
predicate.Funcs
Annotation string
}

// Create filters CreateEvents based on the Annotation.
func (cp AnnotationPredicate) Create(e event.CreateEvent) bool {
if e.Object == nil {
return false
}

_, ok := e.Object.GetAnnotations()[cp.Annotation]
return ok
}

// Update filters UpdateEvents based on the Annotation.
func (cp AnnotationPredicate) Update(e event.UpdateEvent) bool {
if e.ObjectOld == nil || e.ObjectNew == nil {
// this case should not happen
return false
}

oldAnnotationVal := e.ObjectOld.GetAnnotations()[cp.Annotation]
newAnnotationVal := e.ObjectNew.GetAnnotations()[cp.Annotation]

return oldAnnotationVal != newAnnotationVal
}
Loading

0 comments on commit d6bbdba

Please sign in to comment.