diff --git a/conformance/tests/gateway-infrastructure.go b/conformance/tests/gateway-infrastructure.go new file mode 100644 index 0000000000..6712209bbc --- /dev/null +++ b/conformance/tests/gateway-infrastructure.go @@ -0,0 +1,119 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + v1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +func init() { + ConformanceTests = append(ConformanceTests, GatewayInfrastructure) +} + +var GatewayInfrastructure = suite.ConformanceTest{ + ShortName: "GatewayInfrastructure", + Description: "Propagation of metadata from Gateway infrastructure to generated components", + Features: []features.SupportedFeature{ + features.SupportGateway, + features.SupportGatewayInfrastructurePropagation, + }, + Manifests: []string{ + "tests/gateway-infrastructure.yaml", + }, + Test: func(t *testing.T, s *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + + kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, []string{ns}) + + gwNN := types.NamespacedName{ + Name: "gateway-with-infrastructure-metadata", + Namespace: "gateway-conformance-infra", + } + ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.DefaultTestTimeout) + defer cancel() + + t.Logf("waiting for namespace %s and Gateway %s to be ready for testing", gwNN.Namespace, gwNN.Name) + kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) + + t.Logf("retrieving Gateway %s/%s", gwNN.Namespace, gwNN.Name) + currentGW := &v1.Gateway{} + err := s.Client.Get(ctx, gwNN, currentGW) + require.NoError(t, err, "error getting Gateway: %v", err) + t.Logf("verifying that the Gateway %s/%s is accepted with infrastructure declared", gwNN.Namespace, gwNN.Name) + kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ + Type: string(v1.GatewayConditionAccepted), + Status: metav1.ConditionTrue, + }) + + t.Logf("verifying that generated resources for Gateway %s/%s have the proper gateway name label", gwNN.Namespace, gwNN.Name) + // Don't check services because implementations may have special filtering logic (e.g. for LB annotations) + // Instead, check service accounts for the gateway-name label first and then + // fallback to Pod if that fails + annotations := make(map[string]string, len(currentGW.Spec.Infrastructure.Annotations)) + labels := make(map[string]string, len(currentGW.Spec.Infrastructure.Labels)) + // Need to translate from (Annotation|Label)Key to string + for k, v := range currentGW.Spec.Infrastructure.Annotations { + annotations[string(k)] = string(v) + } + + for k, v := range currentGW.Spec.Infrastructure.Labels { + labels[string(k)] = string(v) + } + var foundResource bool + saList := corev1.ServiceAccountList{} + podList := corev1.PodList{} + serviceList := corev1.ServiceList{} + err = s.Client.List(ctx, &saList, client.MatchingLabels{"gateway.networking.k8s.io/gateway-name": gwNN.Name}, client.InNamespace(ns)) + require.NoError(t, err, "error listing ServiceAccounts") + err = s.Client.List(ctx, &podList, client.MatchingLabels{"gateway.networking.k8s.io/gateway-name": gwNN.Name}, client.InNamespace(ns)) + require.NoError(t, err, "error listing Pods") + err = s.Client.List(ctx, &serviceList, client.MatchingLabels{"gateway.networking.k8s.io/gateway-name": gwNN.Name}, client.InNamespace(ns)) + require.NoError(t, err, "error listing Services") + if len(saList.Items) > 0 { + foundResource = true + sa := saList.Items[0] + require.Subsetf(t, sa.Labels, labels, "expected Pod label set %v to contain all Gateway infrastructure labels %v", sa.Labels, labels) + require.Subsetf(t, sa.Annotations, annotations, "expected Pod annotation set %v to contain all Gateway infrastructure annotations %v", sa.Annotations, annotations) + } + if len(podList.Items) > 0 { + foundResource = true + pod := podList.Items[0] + require.Subsetf(t, pod.Labels, labels, "expected Pod label set %v to contain all Gateway infrastructure labels %v", pod.Labels, labels) + require.Subsetf(t, pod.Annotations, annotations, "expected Pod annotation set %v to contain all Gateway infrastructure annotations %v", pod.Annotations, annotations) + } + if len(serviceList.Items) > 0 { + foundResource = true + service := serviceList.Items[0] + require.Subsetf(t, service.Labels, labels, "expected Pod label set %v to contain all Gateway infrastructure labels %v", service.Labels, labels) + require.Subsetf(t, service.Annotations, annotations, "expected Pod annotation set %v to contain all Gateway infrastructure annotations %v", service.Annotations, annotations) + } + + require.True(t, foundResource, "expected to find a ServiceAccount, Pod, or Service with the gateway-name label") + }, +} diff --git a/conformance/tests/gateway-infrastructure.yaml b/conformance/tests/gateway-infrastructure.yaml new file mode 100644 index 0000000000..7ff2fc4c48 --- /dev/null +++ b/conformance/tests/gateway-infrastructure.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway-with-infrastructure-metadata + namespace: gateway-conformance-infra +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + infrastructure: + annotations: + key1: value1 + labels: + key2: value2 + listeners: + - name: http + port: 8080 + protocol: HTTP diff --git a/geps/gep-1867/index.md b/geps/gep-1867/index.md index 67e327acfb..893787bc5a 100644 --- a/geps/gep-1867/index.md +++ b/geps/gep-1867/index.md @@ -1,6 +1,6 @@ # GEP-1867: Per-Gateway Infrastructure -* Status: Experimental +* Status: Standard * Issue: [#1867](https://github.com/kubernetes-sigs/gateway-api/issues/1867) ## Overview diff --git a/geps/gep-1867/metadata.yaml b/geps/gep-1867/metadata.yaml index 5aacf2e331..fb150e544a 100644 --- a/geps/gep-1867/metadata.yaml +++ b/geps/gep-1867/metadata.yaml @@ -2,7 +2,7 @@ apiVersion: internal.gateway.networking.k8s.io/v1alpha1 kind: GEPDetails number: 1867 name: Per-Gateway Infrastructure -status: Experimental +status: Standard authors: - howardjohn relationships: diff --git a/pkg/features/features.go b/pkg/features/features.go index c9e3c1c52d..3f1ee91c64 100644 --- a/pkg/features/features.go +++ b/pkg/features/features.go @@ -58,6 +58,10 @@ const ( // SupportGatewayHTTPListenerIsolation option indicates support for the isolation // of HTTP listeners. SupportGatewayHTTPListenerIsolation SupportedFeature = "GatewayHTTPListenerIsolation" + + // SupportGatewayInfrastructureAnnotations option indicates support for + // spec.infrastructure.annotations and spec.infrastrucutre.labels + SupportGatewayInfrastructurePropagation SupportedFeature = "GatewayInfrastructurePropagation" ) // GatewayExtendedFeatures are extra generic features that implementations may @@ -66,6 +70,7 @@ var GatewayExtendedFeatures = sets.New( SupportGatewayPort8080, SupportGatewayStaticAddresses, SupportGatewayHTTPListenerIsolation, + SupportGatewayInfrastructurePropagation, ) // -----------------------------------------------------------------------------