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

[API Gateway] API Gateway Binding Logic #2142

Merged
merged 29 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
880fbdb
initial commit
May 17, 2023
9a27f48
Add additional TODO
May 17, 2023
0a4e51f
Add some basic lifecycle unit tests
May 17, 2023
dbd6e78
split up implementation
May 17, 2023
d5416c0
Add more tests and fix some bugs
May 17, 2023
9a14142
remove one parallel call in a loop
May 17, 2023
cd76271
Fix binding
May 17, 2023
efee1e6
Add resolvedRefs statuses for routes
May 17, 2023
556d444
Fix issue with empty parent ref that k8s doesn't like
May 17, 2023
59ca79d
Fix up updates/status ordering
May 18, 2023
9cf8b62
Add basic gateway status setting
May 18, 2023
9e422f7
Finish up first pass on gateway statuses
May 18, 2023
91d23d0
Re-organize and begin adding comments
May 18, 2023
43ba733
More comments
May 18, 2023
f750791
More comments
May 18, 2023
8fd878a
More comments
May 18, 2023
a5bfb13
More comments
May 18, 2023
625ea18
More comments
May 18, 2023
0886a5c
Add file that wasn't saved
May 18, 2023
a680b89
Add utils unit tests
May 18, 2023
6326785
Add more tests
May 18, 2023
b4d46d2
Final tests
May 18, 2023
4c23638
Merge branch 'api-gateways' of https://github.com/hashicorp/consul-k8…
May 18, 2023
64d5e13
Merge branch 'api-gateways' of https://github.com/hashicorp/consul-k8…
May 18, 2023
8208015
Fix tests
May 18, 2023
c9d7a6c
Fix up gateway annotation with binding logic
May 18, 2023
71e7b33
Update doc comments for linter
May 18, 2023
a875ca8
Add forgotten file
May 19, 2023
31abb4d
Fix block in tests due to buffered channel size and better handle con…
May 19, 2023
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 charts/consul/templates/connect-inject-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,18 @@ rules:
- gateway.networking.k8s.io
resources:
- gatewayclasses/finalizers
- gateways/finalizers
- httproutes/finalizers
- tcproutes/finalizers
verbs:
- update
- apiGroups:
- gateway.networking.k8s.io
resources:
- gatewayclasses/status
- gateways/status
- httproutes/status
- tcproutes/status
verbs:
- get
- patch
Expand Down
38 changes: 38 additions & 0 deletions control-plane/api-gateway/binding/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package binding

import (
"encoding/json"

gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
)

const (
group = "api-gateway.consul.hashicorp.com"
annotationConfigKey = "api-gateway.consul.hashicorp.com/config"
)

func serializeGatewayClassConfig(gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayClassConfig) (*v1alpha1.GatewayClassConfig, bool) {
if gwcc == nil {
return nil, false
}

if gw.Annotations == nil {
gw.Annotations = make(map[string]string)
}

if annotatedConfig, ok := gw.Annotations[annotationConfigKey]; ok {
var config v1alpha1.GatewayClassConfig
if err := json.Unmarshal([]byte(annotatedConfig), &config.Spec); err == nil {
// if we can unmarshal the gateway, return it
return &config, false
}
}

// otherwise if we failed to unmarshal or there was no annotation, marshal it onto
// the gateway
marshaled, _ := json.Marshal(gwcc.Spec)
gw.Annotations[annotationConfigKey] = string(marshaled)
return gwcc, true
}
203 changes: 203 additions & 0 deletions control-plane/api-gateway/binding/annotations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package binding

import (
"encoding/json"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
)

func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) {
t.Parallel()

type args struct {
gw *gwv1beta1.Gateway
gwcc *v1alpha1.GatewayClassConfig
}
tests := []struct {
name string
args args
expectedDidUpdate bool
}{
{
name: "when gateway has not been annotated yet and annotations are nil",
args: args{
gw: &gwv1beta1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "my-gw",
},
Spec: gwv1beta1.GatewaySpec{},
Status: gwv1beta1.GatewayStatus{},
},
gwcc: &v1alpha1.GatewayClassConfig{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "the config",
},
Spec: v1alpha1.GatewayClassConfigSpec{
ServiceType: pointerTo(corev1.ServiceType("serviceType")),
NodeSelector: map[string]string{
"selector": "of node",
},
Tolerations: []v1.Toleration{
{
Key: "key",
Operator: "op",
Value: "120",
Effect: "to the moon",
TolerationSeconds: new(int64),
},
},
CopyAnnotations: v1alpha1.CopyAnnotationsSpec{
Service: []string{"service"},
},
},
},
},
expectedDidUpdate: true,
},
{
name: "when gateway has not been annotated yet but annotations are empty",
args: args{
gw: &gwv1beta1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "my-gw",
Annotations: make(map[string]string),
},
Spec: gwv1beta1.GatewaySpec{},
Status: gwv1beta1.GatewayStatus{},
},
gwcc: &v1alpha1.GatewayClassConfig{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "the config",
},
Spec: v1alpha1.GatewayClassConfigSpec{
ServiceType: pointerTo(corev1.ServiceType("serviceType")),
NodeSelector: map[string]string{
"selector": "of node",
},
Tolerations: []v1.Toleration{
{
Key: "key",
Operator: "op",
Value: "120",
Effect: "to the moon",
TolerationSeconds: new(int64),
},
},
CopyAnnotations: v1alpha1.CopyAnnotationsSpec{
Service: []string{"service"},
},
},
},
},
expectedDidUpdate: true,
},
{
name: "when gateway has been annotated",
args: args{
gw: &gwv1beta1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "my-gw",
Annotations: map[string]string{
annotationConfigKey: `{"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`,
},
},
Spec: gwv1beta1.GatewaySpec{},
Status: gwv1beta1.GatewayStatus{},
},
gwcc: &v1alpha1.GatewayClassConfig{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "the config",
},
Spec: v1alpha1.GatewayClassConfigSpec{
ServiceType: pointerTo(corev1.ServiceType("serviceType")),
NodeSelector: map[string]string{
"selector": "of node",
},
Tolerations: []v1.Toleration{
{
Key: "key",
Operator: "op",
Value: "120",
Effect: "to the moon",
TolerationSeconds: new(int64),
},
},
CopyAnnotations: v1alpha1.CopyAnnotationsSpec{
Service: []string{"service"},
},
},
},
},
expectedDidUpdate: false,
},
{
name: "when gateway has been annotated but the serialization was invalid",
args: args{
gw: &gwv1beta1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "my-gw",
Annotations: map[string]string{
// we remove the opening brace to make unmarshalling fail
annotationConfigKey: `"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`,
},
},
Spec: gwv1beta1.GatewaySpec{},
Status: gwv1beta1.GatewayStatus{},
},
gwcc: &v1alpha1.GatewayClassConfig{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "the config",
},
Spec: v1alpha1.GatewayClassConfigSpec{
ServiceType: pointerTo(corev1.ServiceType("serviceType")),
NodeSelector: map[string]string{
"selector": "of node",
},
Tolerations: []v1.Toleration{
{
Key: "key",
Operator: "op",
Value: "120",
Effect: "to the moon",
TolerationSeconds: new(int64),
},
},
CopyAnnotations: v1alpha1.CopyAnnotationsSpec{
Service: []string{"service"},
},
},
},
},
expectedDidUpdate: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, actualDidUpdate := serializeGatewayClassConfig(tt.args.gw, tt.args.gwcc)

if actualDidUpdate != tt.expectedDidUpdate {
t.Errorf("SerializeGatewayClassConfig() = %v, want %v", actualDidUpdate, tt.expectedDidUpdate)
}

var config v1alpha1.GatewayClassConfig
err := json.Unmarshal([]byte(tt.args.gw.Annotations[annotationConfigKey]), &config.Spec)
require.NoError(t, err)

if diff := cmp.Diff(config.Spec, tt.args.gwcc.Spec); diff != "" {
t.Errorf("Expected gwconfig spec to match serialized version (-want,+got):\n%s", diff)
}
})
}
}
Loading