From eae4c99604a2d3c21b9ee5dbf4fd2cc3a8470a47 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Thu, 1 May 2025 19:22:22 +0000 Subject: [PATCH 01/20] Add inferencepool_lifecycle test. --- conformance/conformance.go | 115 +++++++++++--- conformance/embed.go | 2 +- .../resources/manifests/manifests.yaml | 6 +- .../tests/basic/inferencepool_accepted.yaml | 33 ++++- .../tests/basic/inferencepool_lifecycle.yaml | 58 ++++++++ .../basic/inferencepool_lifecycle_test.go | 140 ++++++++++++++++++ conformance/utils/kubernetes/helpers.go | 129 ++++++++++++++-- 7 files changed, 442 insertions(+), 41 deletions(-) create mode 100644 conformance/tests/basic/inferencepool_lifecycle.yaml create mode 100644 conformance/tests/basic/inferencepool_lifecycle_test.go diff --git a/conformance/conformance.go b/conformance/conformance.go index 20d80fde5..63447720c 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -19,13 +19,19 @@ limitations under the License. package conformance import ( + "context" "fmt" "io/fs" "os" + "reflect" "testing" + "time" "github.com/stretchr/testify/require" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" clientset "k8s.io/client-go/kubernetes" // Import runtime package for scheme creation @@ -41,6 +47,7 @@ import ( confapis "sigs.k8s.io/gateway-api/conformance/apis/v1" // Report struct definition confconfig "sigs.k8s.io/gateway-api/conformance/utils/config" confflags "sigs.k8s.io/gateway-api/conformance/utils/flags" + apikubernetes "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" confsuite "sigs.k8s.io/gateway-api/conformance/utils/suite" "sigs.k8s.io/gateway-api/pkg/features" // Using core features definitions if applicable @@ -58,6 +65,12 @@ import ( inferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2" ) +// Constants for the shared Gateway +const ( + SharedGatewayName = "gateway-conformance-app" // Name of the Gateway in manifests.yaml + SharedGatewayNamespace = "gateway-conformance-infra" // Namespace of the Gateway +) + // GatewayLayerProfileName defines the name for the conformance profile that tests // the Gateway API layer aspects of the Inference Extension (e.g., InferencePool, InferenceModel CRDs). // Future profiles will cover EPP and ModelServer layers. @@ -83,23 +96,42 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { cfg, err := config.GetConfig() require.NoError(t, err, "error loading Kubernetes config") - // Initialize client options. The scheme must include Gateway API types - // and the Inference Extension types. - clientOptions := client.Options{} - scheme := clientOptions.Scheme - if scheme == nil { - // If default options don't provide a scheme, create one using runtime.NewScheme(). - scheme = runtime.NewScheme() - clientOptions.Scheme = scheme + scheme := runtime.NewScheme() + + t.Log("Registering API types with scheme...") + // Register Gateway API v1 types (including HTTPRoute) + require.NoError(t, gatewayv1.Install(scheme), "Failed to install gatewayv1 types into scheme") + + // TODO: DELETE ME before sumit - Debugging for HTTPRoute + httpRouteGVKs, _, httpRouteGVKsErr := scheme.ObjectKinds(&gatewayv1.HTTPRoute{}) + t.Logf("CONFORMANCE.GO DEBUG: Type of HTTPRoute instance used in conformance.go DefaultOptions: %s", reflect.TypeOf(&gatewayv1.HTTPRoute{}).String()) + if httpRouteGVKsErr != nil { + t.Logf("Error when trying to get GVKs for HTTPRoute from scheme: %v.", httpRouteGVKsErr) + } else if len(httpRouteGVKs) == 0 { + t.Logf("CRITICAL: Scheme does NOT know about HTTPRoute GVK after gatewayv1.Install call.") + } else { + t.Logf("SUCCESS: Scheme knows about HTTPRoute GVKs: %v", httpRouteGVKs) } - // Register necessary API Types - require.NoError(t, gatewayv1.Install(scheme)) // Add core Gateway API types - // Add the Inference Extension API types to the scheme using the correct import alias - require.NoError(t, inferencev1alpha2.Install(scheme)) - require.NoError(t, apiextensionsv1.AddToScheme(scheme)) // Needed for CRD checks + // Register apiextensions/v1 for CRD checks + require.NoError(t, apiextensionsv1.AddToScheme(scheme), "Failed to add apiextensionsv1 types to scheme") + + // Register Inference Extension API types + t.Logf("Attempting to install inferencev1alpha2 types (like InferencePool) into scheme from package: %s", inferencev1alpha2.GroupName) + require.NoError(t, inferencev1alpha2.Install(scheme), "Failed to install inferencev1alpha2 types into scheme.") - // Create the Kubernetes clients + // TODO: DELETE ME before sumit - Debugging for InferencePool + inferencePoolGVKs, _, inferencePoolGVKsErr := scheme.ObjectKinds(&inferencev1alpha2.InferencePool{}) + t.Logf("CONFORMANCE.GO DEBUG: Type of InferencePool instance used in conformance.go DefaultOptions: %s", reflect.TypeOf(&inferencev1alpha2.InferencePool{}).String()) + if inferencePoolGVKsErr != nil || len(inferencePoolGVKs) == 0 { + t.Logf("CRITICAL: Scheme does NOT know about InferencePool GVK after inferencev1alpha2.Install call. Error: %v, GVKs: %v", inferencePoolGVKsErr, inferencePoolGVKs) + } else { + t.Logf("SUCCESS: Scheme knows about InferencePool GVKs: %v", inferencePoolGVKs) + } + t.Logf("CONFORMANCE.GO DEBUG: Scheme object address in DefaultOptions: %p", scheme) + // End Debugging + + clientOptions := client.Options{Scheme: scheme} c, err := client.New(cfg, clientOptions) require.NoError(t, err, "error initializing Kubernetes client") cs, err := clientset.NewForConfig(cfg) @@ -124,15 +156,19 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { inferenceExtensionVersion := "v0.3.0" _ = inferenceExtensionVersion // Avoid unused variable error until implemented - // Create ConformanceOptions + baseManifestsValue := "resources/manifests/manifests.yaml" + t.Logf("Explicitly setting BaseManifests path to: %q", baseManifestsValue) + opts := confsuite.ConformanceOptions{ Client: c, + ClientOptions: clientOptions, Clientset: cs, RestConfig: cfg, GatewayClassName: *confflags.GatewayClassName, + BaseManifests: baseManifestsValue, Debug: *confflags.ShowDebug, CleanupBaseResources: *confflags.CleanupBaseResources, - SupportedFeatures: sets.New[features.FeatureName](), // Initialize empty, will be populated below + SupportedFeatures: sets.New[features.FeatureName](), TimeoutConfig: confconfig.DefaultTimeoutConfig(), SkipTests: skipTests, ExemptFeatures: exemptFeatures, @@ -140,7 +176,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { Mode: *confflags.Mode, Implementation: implementation, ConformanceProfiles: conformanceProfiles, - ManifestFS: []fs.FS{&Manifests}, // Assumes embed.go defines `Manifests` + ManifestFS: []fs.FS{&Manifests}, ReportOutputPath: *confflags.ReportOutput, SkipProvisionalTests: *confflags.SkipProvisionalTests, // TODO: Add the inference extension specific fields to ConformanceOptions struct if needed, @@ -173,6 +209,7 @@ func RunConformance(t *testing.T) { // RunConformanceWithOptions runs the Inference Extension conformance tests with specific options. func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) { t.Logf("Running Inference Extension conformance tests with GatewayClass %s", opts.GatewayClassName) + t.Logf("CONFORMANCE.GO RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) // Register the GatewayLayerProfile with the suite runner. // In the future, other profiles (EPP, ModelServer) will also be registered here, @@ -183,13 +220,47 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) cSuite, err := confsuite.NewConformanceTestSuite(opts) require.NoError(t, err, "error initializing conformance suite") - t.Log("Setting up Inference Extension conformance tests") - // Setup requires the list of tests, which is populated by the init() functions - // triggered by the blank imports at the top of this file. + t.Log("Setting up Inference Extension conformance tests (applying base manifests)") cSuite.Setup(t, tests.ConformanceTests) - t.Log("Running Inference Extension conformance tests") - // Run the tests. + // TODO: Move gateway setup to a helper method. + sharedGwNN := types.NamespacedName{Name: SharedGatewayName, Namespace: SharedGatewayNamespace} + t.Logf("Attempting to directly fetch Gateway %s/%s after cSuite.Setup()", sharedGwNN.Namespace, sharedGwNN.Name) + gw := &gatewayv1.Gateway{} + var getErr error + + // TODO: Change to a configurable value for retries. + for i := 0; i < 5; i++ { + getErr = cSuite.Client.Get(context.TODO(), sharedGwNN, gw) + if getErr == nil { + t.Logf("Successfully fetched Gateway %s/%s directly after setup. Spec.GatewayClassName: %s", + sharedGwNN.Namespace, sharedGwNN.Name, gw.Spec.GatewayClassName) + break + } + if apierrors.IsNotFound(getErr) { + t.Logf("Gateway %s/%s not found immediately after setup (attempt %d/5). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1) + time.Sleep(1 * time.Second) + } else { + t.Logf("Error fetching Gateway %s/%s directly after setup (attempt %d/5): %v. This might not be a 'Not Found' error.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, getErr) + break + } + } + require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s directly after cSuite.Setup(), even after retries. It should have been created by applying base manifests.", sharedGwNN.Namespace, sharedGwNN.Name) + + t.Logf("Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) + apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ + Type: string(gatewayv1.GatewayConditionAccepted), + Status: metav1.ConditionTrue, + }) + apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ + Type: string(gatewayv1.GatewayConditionProgrammed), + Status: metav1.ConditionTrue, + }) + _, err = apikubernetes.WaitForGatewayAddress(t, cSuite.Client, cSuite.TimeoutConfig, apikubernetes.NewGatewayRef(sharedGwNN)) + require.NoErrorf(t, err, "shared gateway %s/%s did not get an address", SharedGatewayNamespace, SharedGatewayName) + t.Logf("Shared Gateway %s/%s is ready", SharedGatewayNamespace, SharedGatewayName) + + t.Log("Running Inference Extension conformance tests against all registered tests") err = cSuite.Run(t, tests.ConformanceTests) require.NoError(t, err, "error running conformance tests") diff --git a/conformance/embed.go b/conformance/embed.go index f7fa64c93..c9175db1d 100644 --- a/conformance/embed.go +++ b/conformance/embed.go @@ -21,5 +21,5 @@ import "embed" // Manifests embeds the contents of the conformance/resources directory making // the YAML files within them available to the test suite at runtime. // -//go:embed resources/* tests/* +//go:embed resources tests/* var Manifests embed.FS diff --git a/conformance/resources/manifests/manifests.yaml b/conformance/resources/manifests/manifests.yaml index 7b43b784f..a72775024 100644 --- a/conformance/resources/manifests/manifests.yaml +++ b/conformance/resources/manifests/manifests.yaml @@ -25,12 +25,12 @@ metadata: --- # A basic Gateway resource that allows HTTPRoutes from the same namespace. # Tests can use this as a parent reference for routes that target InferencePools. -# Using a simple echo server instead of an actual model server to simplify the test -# execution, this design may need to be revised based on the test case needs. +# Using a simple echo server instead of an actual model server to simplify the test +# execution, this design may need to be revised based on the test case needs. apiVersion: gateway.networking.k8s.io/v1 # Using v1 as per latest Gateway API standard kind: Gateway metadata: - name: same-namespace + name: gateway-conformance-app namespace: gateway-conformance-infra spec: # The conformance suite runner will replace this placeholder diff --git a/conformance/tests/basic/inferencepool_accepted.yaml b/conformance/tests/basic/inferencepool_accepted.yaml index 8ae327d8a..a95641506 100644 --- a/conformance/tests/basic/inferencepool_accepted.yaml +++ b/conformance/tests/basic/inferencepool_accepted.yaml @@ -1,7 +1,8 @@ # Basic InferencePool for acceptance testing. -# This manifest defines the minimal required fields to create a valid -# InferencePool resource, which the InferencePoolAccepted test will use -# to verify that the controller recognizes and accepts the resource. +# This manifest defines the minimal required fields to create valid +# InferencePool and HTTPRoute resources, which the InferencePoolAccepted +# test will use to verify that the controller recognizes and accepts the resource. +# --- InferencePool Definition --- apiVersion: inference.networking.x-k8s.io/v1alpha2 kind: InferencePool @@ -24,4 +25,30 @@ spec: # --- Extension Reference --- # GKE-specific configuration reference. extensionRef: + # group: "" # Optional + # kind: Service # Optional name: infra-backend-v1-epp + +--- +# --- HTTPRoute Definition --- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-for-inferencepool-accepted + namespace: gateway-conformance-app-backend +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-conformance-app # Name of the shared Gateway from maniffests.yaml + namespace: gateway-conformance-infra # Namespace of the shared Gateway + sectionName: http + rules: + - backendRefs: + - group: inference.networking.x-k8s.io/v1alpha2 # InferencePool API group + kind: InferencePool + name: inferencepool-basic-accepted # Name of the InferencePool this route points to + matches: + - path: + type: PathPrefix + value: /accepted-pool-test diff --git a/conformance/tests/basic/inferencepool_lifecycle.yaml b/conformance/tests/basic/inferencepool_lifecycle.yaml new file mode 100644 index 000000000..7c6ba7546 --- /dev/null +++ b/conformance/tests/basic/inferencepool_lifecycle.yaml @@ -0,0 +1,58 @@ +# conformance/tests/basic/inferencepool_lifecycle.yaml +# This file includes both the InferencePool and the HTTPRoute + +# --- InferencePool Definition --- +apiVersion: inference.networking.x-k8s.io/v1alpha2 +kind: InferencePool +metadata: + # This name must match the 'poolName' variable defined in the + # conformance/tests/basic/inferencepool_lifecycle_test.go test file. + name: inferencepool-lifecycle-test + # This namespace should be one created by the base manifests and + # match the conformance/tests/basic/inferencepool_lifecycle_test.go test file. + namespace: gateway-conformance-infra +spec: + # --- Selector (Required) --- + # Selects the Pods belonging to this pool. + # This 'app' label value must match what is asserted in the Go test. + selector: + app: "lifecycle-test-app" + + # --- Target Port (Required) --- + # The port the model server container listens on. + targetPortNumber: 3000 + + # --- Extension Reference (Required) --- + # Configuration for the Endpoint Picker. + extensionRef: + # group: "" # Optional, defaults to "" (core API group) + # kind: Service # Optional, defaults to "Service" + name: infra-backend-v1-epp + # portNumber: 1234 # Optional + # failureMode: "FailClose" # Optional, defaults to "FailClose" + +--- +# --- HTTPRoute Definition --- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-for-inferencepool-lifecycle-test + # HTTPRoute is now also in the 'default' namespace for this test. + namespace: gateway-conformance-infra +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: test-gateway # Name of the shared Gateway + namespace: gateway-conformance-infra # Namespace of the shared Gateway + sectionName: http # Assuming the shared Gateway has an HTTP listener named 'http' + rules: + - backendRefs: + - group: inference.networking.k8s.io # Your InferencePool API group + kind: InferencePool + name: inferencepool-basic-accepted # Name of the InferencePool this route points to + # Namespace for backendRef is omitted as HTTPRoute and InferencePool are in the same namespace ('default') + matches: + - path: + type: PathPrefix + value: /accepted-pool-test diff --git a/conformance/tests/basic/inferencepool_lifecycle_test.go b/conformance/tests/basic/inferencepool_lifecycle_test.go new file mode 100644 index 000000000..0ee7d266e --- /dev/null +++ b/conformance/tests/basic/inferencepool_lifecycle_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2025 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, +WITHOUTHOUT 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 basic + +import ( + "context" + "testing" + "time" // Added for polling interval + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" // Added for IsNotFound + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" + + inferenceapi "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2" // Adjust API version if needed + "sigs.k8s.io/gateway-api-inference-extension/conformance/tests" + infrakubernetes "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes" +) + +func init() { + tests.ConformanceTests = append(tests.ConformanceTests, InferencePoolLifecycle) +} + +var InferencePoolLifecycle = suite.ConformanceTest{ + ShortName: "InferencePoolLifecycle", + Description: "Tests basic CRUD (Create, Read, Update, Delete) operations for an InferencePool resource, assuming current API definition.", + Manifests: []string{"tests/basic/inferencepool_lifecycle.yaml"}, + Features: []features.FeatureName{}, + Test: func(t *testing.T, s *suite.ConformanceTestSuite) { + poolName := "inferencepool-lifecycle-test" + // Hardcode the namespace for now, aligning with the manifest. + const hardcodedNamespace = "default" + poolNN := types.NamespacedName{Name: poolName, Namespace: "default"} + + t.Run("Step 1 & 2: Create and Read InferencePool", func(t *testing.T) { + acceptedCondition := metav1.Condition{ + Type: string(gatewayv1.GatewayConditionAccepted), + Status: metav1.ConditionTrue, + Reason: "", + } + // Assuming InferencePoolMustHaveCondition uses appropriate timeouts from s.TimeoutConfig internally + // (e.g. DefaultTestTimeout or a more specific one if defined for generic resource conditions) + infrakubernetes.InferencePoolMustHaveCondition(t, s.Client, s.TimeoutConfig, poolNN, acceptedCondition) + + createdPool := &inferenceapi.InferencePool{} + err := s.Client.Get(context.TODO(), poolNN, createdPool) + require.NoErrorf(t, err, "Failed to get InferencePool %s/%s", poolNN.Namespace, poolNN.Name) + + // Verify fields based on the current API (using .Spec.Selector) + // TODO: https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/766 + // API Discrepancy: InferencePoolSpec pod selector field name mismatch between API + // Proposal 002 and current Go type definition + require.NotNil(t, createdPool.Spec.Selector, "Selector should not be nil") + // Assuming LabelKey and LabelValue are effectively strings for this map access + require.Equal(t, inferenceapi.LabelValue("lifecycle-test-app"), createdPool.Spec.Selector[inferenceapi.LabelKey("app")], + "Selector 'app' label does not match manifest") + + t.Logf("Successfully created and read InferencePool %s/%s", poolNN.Namespace, poolNN.Name) + }) + + t.Run("Step 3: Update InferencePool", func(t *testing.T) { + originalPool := &inferenceapi.InferencePool{} + err := s.Client.Get(context.TODO(), poolNN, originalPool) + require.NoErrorf(t, err, "Failed to get InferencePool %s for update", poolNN.String()) + + updatedPool := originalPool.DeepCopy() + // Update based on the current API (using .Spec.Selector) + updatedPool.Spec.Selector = map[inferenceapi.LabelKey]inferenceapi.LabelValue{ + inferenceapi.LabelKey("app"): inferenceapi.LabelValue("lifecycle-test-app-updated"), + } + + err = s.Client.Update(context.TODO(), updatedPool) + require.NoErrorf(t, err, "Failed to update InferencePool %s", poolNN.String()) + + fetchedAfterUpdate := &inferenceapi.InferencePool{} + // Use DefaultTestTimeout for the overall wait and a 1-second poll interval for Eventually. + require.Eventually(t, func() bool { + if err := s.Client.Get(context.TODO(), poolNN, fetchedAfterUpdate); err != nil { + return false + } + if fetchedAfterUpdate.Spec.Selector == nil { + return false + } + // Check based on the current API (using .Spec.Selector) + return fetchedAfterUpdate.Spec.Selector[inferenceapi.LabelKey("app")] == inferenceapi.LabelValue("lifecycle-test-app-updated") + }, s.TimeoutConfig.DefaultTestTimeout, 1*time.Second, "Failed to observe updated Selector") + + require.NotNil(t, fetchedAfterUpdate.Spec.Selector, "Updated Selector should not be nil") + require.Equal(t, inferenceapi.LabelValue("lifecycle-test-app-updated"), fetchedAfterUpdate.Spec.Selector[inferenceapi.LabelKey("app")], + "Selector 'app' label was not updated as expected") + t.Logf("Successfully updated InferencePool %s/%s", poolNN.Namespace, poolNN.Name) + }) + + t.Run("Step 4: Delete InferencePool", func(t *testing.T) { + t.Logf("Attempting to explicitly delete InferencePool %s/%s for verification", poolNN.Namespace, poolNN.Name) + poolToDelete := &inferenceapi.InferencePool{ + ObjectMeta: metav1.ObjectMeta{ + Name: poolName, + Namespace: hardcodedNamespace, + }, + } + err := s.Client.Delete(context.TODO(), poolToDelete) + if err != nil && !apierrors.IsNotFound(err) { + require.NoErrorf(t, err, "Failed to delete InferencePool %s", poolNN.String()) + } + + deletedPool := &inferenceapi.InferencePool{} + // Use DeleteTimeout for the overall wait and a 1-second poll interval for Eventually. + require.Eventually(t, func() bool { + fetchErr := s.Client.Get(context.TODO(), poolNN, deletedPool) + if fetchErr != nil { + if apierrors.IsNotFound(fetchErr) { + return true + } + } + return false + }, s.TimeoutConfig.DeleteTimeout, 1*time.Second, "InferencePool %s not deleted within timeout", poolNN.String()) + + t.Logf("Successfully verified deletion of InferencePool %s/%s", poolNN.Namespace, poolNN.Name) + }) + }, +} diff --git a/conformance/utils/kubernetes/helpers.go b/conformance/utils/kubernetes/helpers.go index 3d517863d..e4bbf3d68 100644 --- a/conformance/utils/kubernetes/helpers.go +++ b/conformance/utils/kubernetes/helpers.go @@ -19,31 +19,136 @@ limitations under the License. package kubernetes import ( + "context" + "fmt" + "reflect" "testing" + "time" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" + // Import the Inference Extension API types + inferenceapi "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2" // Adjust if your API version is different + // Import necessary utilities from the core Gateway API conformance suite "sigs.k8s.io/gateway-api/conformance/utils/config" + // We will implement the condition checking logic directly or use a local helper + // to avoid direct dependency on unexported/differently packaged Gateway API utils if issues persist. ) +// checkCondition is a helper function similar to findConditionInList or CheckCondition +// from the Gateway API conformance utilities. +// It checks if the expectedCondition is present in the conditions list. +// If expectedCondition.Reason is an empty string, it matches any reason. +func checkCondition(t *testing.T, conditions []metav1.Condition, expectedCondition metav1.Condition) bool { + t.Helper() + for _, cond := range conditions { + if cond.Type == expectedCondition.Type { + if cond.Status == expectedCondition.Status { + if expectedCondition.Reason == "" || cond.Reason == expectedCondition.Reason { + return true + } + t.Logf("Condition %s found with Status %s, but Reason %s did not match expected %s", + expectedCondition.Type, cond.Status, cond.Reason, expectedCondition.Reason) + } else { + t.Logf("Condition %s found, but Status %s did not match expected %s", + expectedCondition.Type, cond.Status, expectedCondition.Status) + } + } + } + t.Logf("Condition %s with Status %s (and Reason %s if specified) not found in conditions list: %+v", + expectedCondition.Type, expectedCondition.Status, expectedCondition.Reason, conditions) + return false +} + // InferencePoolMustHaveCondition waits for the specified InferencePool resource -// to exist and report the expected status condition. -// This is a placeholder and needs full implementation. -// -// TODO: Implement the actual logic for this helper function. -// It should fetch the InferencePool using the provided client and check its -// Status.Conditions field, polling until the condition is met or a timeout occurs. -// like HTTPRouteMustHaveCondition. +// to exist and report the expected status condition within one of its parent statuses. +// It polls the InferencePool's status until the condition is met or the timeout occurs. func InferencePoolMustHaveCondition(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, poolNN types.NamespacedName, expectedCondition metav1.Condition) { t.Helper() // Marks this function as a test helper - // Placeholder implementation: Log and skip the check. - t.Logf("Verification for InferencePool condition (%s=%s) on %s - Placeholder: Skipping check.", - expectedCondition.Type, expectedCondition.Status, poolNN.String()) + var lastObservedPool *inferenceapi.InferencePool + var lastError error + var conditionFound bool + var interval time.Duration = 5 // pull interval for status checks. + + // TODO: Make retry interval configurable. + waitErr := wait.PollUntilContextTimeout(context.Background(), interval*time.Second, timeoutConfig.DefaultTestTimeout, true, func(ctx context.Context) (bool, error) { + pool := &inferenceapi.InferencePool{} // This is the type instance used for Get + err := c.Get(ctx, poolNN, pool) + if err != nil { + if apierrors.IsNotFound(err) { + t.Logf("InferencePool %s not found yet. Retrying.", poolNN.String()) + lastError = err + return false, nil + } + t.Logf("Error fetching InferencePool %s (type: %s): %v. Retrying.", poolNN.String(), reflect.TypeOf(pool).String(), err) + lastError = err + return false, nil + } + lastObservedPool = pool + lastError = nil + conditionFound = false + + if len(pool.Status.Parents) == 0 { + t.Logf("InferencePool %s has no parent statuses reported yet.", poolNN.String()) + return false, nil + } + + for _, parentStatus := range pool.Status.Parents { + if checkCondition(t, parentStatus.Conditions, expectedCondition) { + conditionFound = true + return true, nil + } + } + return false, nil + }) + + if waitErr != nil || !conditionFound { + debugMsg := "" + if waitErr != nil { + debugMsg += fmt.Sprintf(" Polling error: %v.", waitErr) + } + if lastError != nil { + debugMsg += fmt.Sprintf(" Last error during fetching: %v.", lastError) + } + + if lastObservedPool != nil { + debugMsg += "\nLast observed InferencePool status:" + if len(lastObservedPool.Status.Parents) == 0 { + debugMsg += " (No parent statuses reported)" + } + for i, parentStatus := range lastObservedPool.Status.Parents { + debugMsg += fmt.Sprintf("\n Parent %d (Gateway: %s/%s):", i, parentStatus.GatewayRef.Namespace, parentStatus.GatewayRef.Name) + if len(parentStatus.Conditions) == 0 { + debugMsg += " (No conditions reported for this parent)" + } + for _, cond := range parentStatus.Conditions { + debugMsg += fmt.Sprintf("\n - Type: %s, Status: %s, Reason: %s, Message: %s", cond.Type, cond.Status, cond.Reason, cond.Message) + } + } + } else if lastError == nil || !apierrors.IsNotFound(lastError) { + debugMsg += "\nInferencePool was not found or not observed successfully during polling." + } + + finalMsg := fmt.Sprintf("timed out or condition not met for InferencePool %s to have condition Type=%s, Status=%s", + poolNN.String(), expectedCondition.Type, expectedCondition.Status) + if expectedCondition.Reason != "" { + finalMsg += fmt.Sprintf(", Reason='%s'", expectedCondition.Reason) + } + finalMsg += "." + debugMsg + require.FailNow(t, finalMsg) + } - // Skip the test using this helper until it's fully implemented. - t.Skip("InferencePoolMustHaveCondition helper not yet implemented") + logMsg := fmt.Sprintf("InferencePool %s successfully has condition Type=%s, Status=%s", + poolNN.String(), expectedCondition.Type, expectedCondition.Status) + if expectedCondition.Reason != "" { + logMsg += fmt.Sprintf(", Reason='%s'", expectedCondition.Reason) + } + t.Log(logMsg) } From 404f522751a825ed98d5dc8cba8dc20534c5a79e Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Fri, 2 May 2025 00:06:56 +0000 Subject: [PATCH 02/20] Resolve setup issues and enable InferencePool test --- conformance/conformance.go | 74 ++++----- .../resources/manifests/manifests.yaml | 16 +- .../tests/basic/inferencepool_accepted.yaml | 47 +++++- .../tests/basic/inferencepool_lifecycle.yaml | 58 -------- .../basic/inferencepool_lifecycle_test.go | 140 ------------------ 5 files changed, 82 insertions(+), 253 deletions(-) delete mode 100644 conformance/tests/basic/inferencepool_lifecycle.yaml delete mode 100644 conformance/tests/basic/inferencepool_lifecycle_test.go diff --git a/conformance/conformance.go b/conformance/conformance.go index 63447720c..dba109a0a 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -23,7 +23,6 @@ import ( "fmt" "io/fs" "os" - "reflect" "testing" "time" @@ -33,23 +32,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" clientset "k8s.io/client-go/kubernetes" + clientsetscheme "k8s.io/client-go/kubernetes/scheme" // Import runtime package for scheme creation "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" + k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/yaml" // Import necessary types and utilities from the core Gateway API conformance suite. - // Assumes sigs.k8s.io/gateway-api is a dependency in the go.mod. gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" // Import core Gateway API types confapis "sigs.k8s.io/gateway-api/conformance/apis/v1" // Report struct definition confconfig "sigs.k8s.io/gateway-api/conformance/utils/config" confflags "sigs.k8s.io/gateway-api/conformance/utils/flags" apikubernetes "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" confsuite "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" // Using core features definitions if applicable + "sigs.k8s.io/gateway-api/pkg/features" // Import the test definitions package to access the ConformanceTests slice "sigs.k8s.io/gateway-api-inference-extension/conformance/tests" @@ -76,13 +75,12 @@ const ( // Future profiles will cover EPP and ModelServer layers. const GatewayLayerProfileName confsuite.ConformanceProfileName = "Gateway" -var InferenceCoreFeatures = sets.New[features.FeatureName]() // Placeholder - Populate with actual features specific to this profile or manage features per profile +// InferenceCoreFeatures defines the core features that implementations +// of the "Gateway" profile for the Inference Extension MUST support. +var InferenceCoreFeatures = sets.New( + features.SupportGateway, // This is needed to ensure manifest gets applied during setup. +) -// GatewayLayerProfile defines the conformance profile for the Gateway API layer -// of the Inference Extension. -// In future iterations, we will add constants and ConformanceProfile structs for -// EPPProfileName ("EPP") and ModelServerProfileName ("ModelServer") -// to cover their respective conformance layers. var GatewayLayerProfile = confsuite.ConformanceProfile{ Name: GatewayLayerProfileName, CoreFeatures: InferenceCoreFeatures, @@ -93,44 +91,23 @@ var GatewayLayerProfile = confsuite.ConformanceProfile{ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { t.Helper() - cfg, err := config.GetConfig() + cfg, err := k8sconfig.GetConfig() require.NoError(t, err, "error loading Kubernetes config") scheme := runtime.NewScheme() t.Log("Registering API types with scheme...") - // Register Gateway API v1 types (including HTTPRoute) + // Add core Kubernetes types (like Secret, Service, etc.) to the scheme + require.NoError(t, clientsetscheme.AddToScheme(scheme), "Failed to add core Kubernetes types to scheme") // CORE FIX + // Add Gateway API types require.NoError(t, gatewayv1.Install(scheme), "Failed to install gatewayv1 types into scheme") - - // TODO: DELETE ME before sumit - Debugging for HTTPRoute - httpRouteGVKs, _, httpRouteGVKsErr := scheme.ObjectKinds(&gatewayv1.HTTPRoute{}) - t.Logf("CONFORMANCE.GO DEBUG: Type of HTTPRoute instance used in conformance.go DefaultOptions: %s", reflect.TypeOf(&gatewayv1.HTTPRoute{}).String()) - if httpRouteGVKsErr != nil { - t.Logf("Error when trying to get GVKs for HTTPRoute from scheme: %v.", httpRouteGVKsErr) - } else if len(httpRouteGVKs) == 0 { - t.Logf("CRITICAL: Scheme does NOT know about HTTPRoute GVK after gatewayv1.Install call.") - } else { - t.Logf("SUCCESS: Scheme knows about HTTPRoute GVKs: %v", httpRouteGVKs) - } - - // Register apiextensions/v1 for CRD checks + // Add APIExtensions types (for CRDs) require.NoError(t, apiextensionsv1.AddToScheme(scheme), "Failed to add apiextensionsv1 types to scheme") // Register Inference Extension API types t.Logf("Attempting to install inferencev1alpha2 types (like InferencePool) into scheme from package: %s", inferencev1alpha2.GroupName) require.NoError(t, inferencev1alpha2.Install(scheme), "Failed to install inferencev1alpha2 types into scheme.") - // TODO: DELETE ME before sumit - Debugging for InferencePool - inferencePoolGVKs, _, inferencePoolGVKsErr := scheme.ObjectKinds(&inferencev1alpha2.InferencePool{}) - t.Logf("CONFORMANCE.GO DEBUG: Type of InferencePool instance used in conformance.go DefaultOptions: %s", reflect.TypeOf(&inferencev1alpha2.InferencePool{}).String()) - if inferencePoolGVKsErr != nil || len(inferencePoolGVKs) == 0 { - t.Logf("CRITICAL: Scheme does NOT know about InferencePool GVK after inferencev1alpha2.Install call. Error: %v, GVKs: %v", inferencePoolGVKsErr, inferencePoolGVKs) - } else { - t.Logf("SUCCESS: Scheme knows about InferencePool GVKs: %v", inferencePoolGVKs) - } - t.Logf("CONFORMANCE.GO DEBUG: Scheme object address in DefaultOptions: %p", scheme) - // End Debugging - clientOptions := client.Options{Scheme: scheme} c, err := client.New(cfg, clientOptions) require.NoError(t, err, "error initializing Kubernetes client") @@ -188,6 +165,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { // Populate SupportedFeatures based on the GatewayLayerProfile. // Since all features are mandatory for this profile, add all defined core features. if opts.ConformanceProfiles.Has(GatewayLayerProfileName) { + t.Logf("CONFORMANCE.GO: Populating SupportedFeatures from GatewayLayerProfile.CoreFeatures (%v)", GatewayLayerProfile.CoreFeatures.UnsortedList()) for feature := range GatewayLayerProfile.CoreFeatures { opts.SupportedFeatures.Insert(feature) } @@ -197,6 +175,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { for feature := range opts.ExemptFeatures { opts.SupportedFeatures.Delete(feature) } + t.Logf("CONFORMANCE.GO: Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) return opts } @@ -220,34 +199,35 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) cSuite, err := confsuite.NewConformanceTestSuite(opts) require.NoError(t, err, "error initializing conformance suite") - t.Log("Setting up Inference Extension conformance tests (applying base manifests)") + t.Log("Setting up Inference Extension conformance tests (applying base manifests via cSuite.Setup)") cSuite.Setup(t, tests.ConformanceTests) - // TODO: Move gateway setup to a helper method. + // TODO: Move gateway setup validation to a helper method. + t.Logf("CONFORMANCE.GO: Attempting to fetch Gateway %s/%s after cSuite.Setup().", SharedGatewayNamespace, SharedGatewayName) sharedGwNN := types.NamespacedName{Name: SharedGatewayName, Namespace: SharedGatewayNamespace} t.Logf("Attempting to directly fetch Gateway %s/%s after cSuite.Setup()", sharedGwNN.Namespace, sharedGwNN.Name) gw := &gatewayv1.Gateway{} var getErr error - - // TODO: Change to a configurable value for retries. - for i := 0; i < 5; i++ { + // TODO: Confirm what is a reasonable wait time here. + maxRetries := 10 + for i := 0; i < maxRetries; i++ { getErr = cSuite.Client.Get(context.TODO(), sharedGwNN, gw) if getErr == nil { - t.Logf("Successfully fetched Gateway %s/%s directly after setup. Spec.GatewayClassName: %s", + t.Logf("CONFORMANCE.GO: Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s", sharedGwNN.Namespace, sharedGwNN.Name, gw.Spec.GatewayClassName) break } if apierrors.IsNotFound(getErr) { - t.Logf("Gateway %s/%s not found immediately after setup (attempt %d/5). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1) + t.Logf("CONFORMANCE.GO: Gateway %s/%s not found (attempt %d/%d). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries) time.Sleep(1 * time.Second) } else { - t.Logf("Error fetching Gateway %s/%s directly after setup (attempt %d/5): %v. This might not be a 'Not Found' error.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, getErr) + t.Logf("CONFORMANCE.GO: Error fetching Gateway %s/%s (attempt %d/%d): %v.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries, getErr) break } } - require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s directly after cSuite.Setup(), even after retries. It should have been created by applying base manifests.", sharedGwNN.Namespace, sharedGwNN.Name) + require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s after cSuite.Setup(), even after retries. It should have been created if the Applier worked with the correct manifest.", sharedGwNN.Namespace, sharedGwNN.Name) - t.Logf("Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) + t.Logf("CONFORMANCE.GO: Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ Type: string(gatewayv1.GatewayConditionAccepted), Status: metav1.ConditionTrue, @@ -258,7 +238,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) }) _, err = apikubernetes.WaitForGatewayAddress(t, cSuite.Client, cSuite.TimeoutConfig, apikubernetes.NewGatewayRef(sharedGwNN)) require.NoErrorf(t, err, "shared gateway %s/%s did not get an address", SharedGatewayNamespace, SharedGatewayName) - t.Logf("Shared Gateway %s/%s is ready", SharedGatewayNamespace, SharedGatewayName) + t.Logf("CONFORMANCE.GO: Shared Gateway %s/%s is ready.", SharedGatewayNamespace, SharedGatewayName) t.Log("Running Inference Extension conformance tests against all registered tests") err = cSuite.Run(t, tests.ConformanceTests) diff --git a/conformance/resources/manifests/manifests.yaml b/conformance/resources/manifests/manifests.yaml index a72775024..6a5d035f4 100644 --- a/conformance/resources/manifests/manifests.yaml +++ b/conformance/resources/manifests/manifests.yaml @@ -22,12 +22,20 @@ metadata: labels: gateway-conformance: backend +--- +# Namespace for simple web server backends. This is expected by +# the upstream conformance suite's Setup method. +apiVersion: v1 +kind: Namespace +metadata: + name: gateway-conformance-web-backend + labels: + gateway-conformance: web-backend + --- # A basic Gateway resource that allows HTTPRoutes from the same namespace. # Tests can use this as a parent reference for routes that target InferencePools. -# Using a simple echo server instead of an actual model server to simplify the test -# execution, this design may need to be revised based on the test case needs. -apiVersion: gateway.networking.k8s.io/v1 # Using v1 as per latest Gateway API standard +apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: gateway-conformance-app @@ -42,7 +50,7 @@ spec: protocol: HTTP allowedRoutes: namespaces: - from: Same # Restrict to same namespace initially for simplicity + from: All kinds: # Allows HTTPRoutes to attach, which can then reference InferencePools. - group: gateway.networking.k8s.io diff --git a/conformance/tests/basic/inferencepool_accepted.yaml b/conformance/tests/basic/inferencepool_accepted.yaml index a95641506..ac41d6a2d 100644 --- a/conformance/tests/basic/inferencepool_accepted.yaml +++ b/conformance/tests/basic/inferencepool_accepted.yaml @@ -2,8 +2,45 @@ # This manifest defines the minimal required fields to create valid # InferencePool and HTTPRoute resources, which the InferencePoolAccepted # test will use to verify that the controller recognizes and accepts the resource. -# --- InferencePool Definition --- +# --- Minimal Backend Deployment (using agnhost echo server) --- +# This Deployment provides Pods for the InferencePool to select. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: infra-backend-v1-deployment + namespace: gateway-conformance-app-backend + labels: + app: infra-backend-v1 +spec: + replicas: 1 + selector: + matchLabels: + app: infra-backend-v1 + template: + metadata: + labels: + app: infra-backend-v1 + spec: + containers: + - name: agnhost-echo + image: k8s.gcr.io/e2e-test-images/agnhost:2.39 + args: + - serve-hostname + - --http-port=8080 + ports: + - name: http + containerPort: + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 5 + failureThreshold: 2 + +--- +# --- InferencePool Definition --- apiVersion: inference.networking.x-k8s.io/v1alpha2 kind: InferencePool metadata: @@ -19,8 +56,8 @@ spec: app: "infra-backend-v1" # --- Target Port (Required) --- - # The port the model server container listens on. - targetPortNumber: 3000 + # The port the model server container (agnhost in this case) listens on. + targetPortNumber: 8080 # Matches agnhost's http-port # --- Extension Reference --- # GKE-specific configuration reference. @@ -45,9 +82,11 @@ spec: sectionName: http rules: - backendRefs: - - group: inference.networking.x-k8s.io/v1alpha2 # InferencePool API group + - group: inference.networking.x-k8s.io # InferencePool API group kind: InferencePool name: inferencepool-basic-accepted # Name of the InferencePool this route points to + # namespace: gateway-conformance-app-backend - is omitted since it is in the same namespace as HTTPRoute + port: 8080 # Matching the InferencePool's targetPortNumber matches: - path: type: PathPrefix diff --git a/conformance/tests/basic/inferencepool_lifecycle.yaml b/conformance/tests/basic/inferencepool_lifecycle.yaml deleted file mode 100644 index 7c6ba7546..000000000 --- a/conformance/tests/basic/inferencepool_lifecycle.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# conformance/tests/basic/inferencepool_lifecycle.yaml -# This file includes both the InferencePool and the HTTPRoute - -# --- InferencePool Definition --- -apiVersion: inference.networking.x-k8s.io/v1alpha2 -kind: InferencePool -metadata: - # This name must match the 'poolName' variable defined in the - # conformance/tests/basic/inferencepool_lifecycle_test.go test file. - name: inferencepool-lifecycle-test - # This namespace should be one created by the base manifests and - # match the conformance/tests/basic/inferencepool_lifecycle_test.go test file. - namespace: gateway-conformance-infra -spec: - # --- Selector (Required) --- - # Selects the Pods belonging to this pool. - # This 'app' label value must match what is asserted in the Go test. - selector: - app: "lifecycle-test-app" - - # --- Target Port (Required) --- - # The port the model server container listens on. - targetPortNumber: 3000 - - # --- Extension Reference (Required) --- - # Configuration for the Endpoint Picker. - extensionRef: - # group: "" # Optional, defaults to "" (core API group) - # kind: Service # Optional, defaults to "Service" - name: infra-backend-v1-epp - # portNumber: 1234 # Optional - # failureMode: "FailClose" # Optional, defaults to "FailClose" - ---- -# --- HTTPRoute Definition --- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: httproute-for-inferencepool-lifecycle-test - # HTTPRoute is now also in the 'default' namespace for this test. - namespace: gateway-conformance-infra -spec: - parentRefs: - - group: gateway.networking.k8s.io - kind: Gateway - name: test-gateway # Name of the shared Gateway - namespace: gateway-conformance-infra # Namespace of the shared Gateway - sectionName: http # Assuming the shared Gateway has an HTTP listener named 'http' - rules: - - backendRefs: - - group: inference.networking.k8s.io # Your InferencePool API group - kind: InferencePool - name: inferencepool-basic-accepted # Name of the InferencePool this route points to - # Namespace for backendRef is omitted as HTTPRoute and InferencePool are in the same namespace ('default') - matches: - - path: - type: PathPrefix - value: /accepted-pool-test diff --git a/conformance/tests/basic/inferencepool_lifecycle_test.go b/conformance/tests/basic/inferencepool_lifecycle_test.go deleted file mode 100644 index 0ee7d266e..000000000 --- a/conformance/tests/basic/inferencepool_lifecycle_test.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright 2025 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, -WITHOUTHOUT 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 basic - -import ( - "context" - "testing" - "time" // Added for polling interval - - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" // Added for IsNotFound - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" - - inferenceapi "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2" // Adjust API version if needed - "sigs.k8s.io/gateway-api-inference-extension/conformance/tests" - infrakubernetes "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes" -) - -func init() { - tests.ConformanceTests = append(tests.ConformanceTests, InferencePoolLifecycle) -} - -var InferencePoolLifecycle = suite.ConformanceTest{ - ShortName: "InferencePoolLifecycle", - Description: "Tests basic CRUD (Create, Read, Update, Delete) operations for an InferencePool resource, assuming current API definition.", - Manifests: []string{"tests/basic/inferencepool_lifecycle.yaml"}, - Features: []features.FeatureName{}, - Test: func(t *testing.T, s *suite.ConformanceTestSuite) { - poolName := "inferencepool-lifecycle-test" - // Hardcode the namespace for now, aligning with the manifest. - const hardcodedNamespace = "default" - poolNN := types.NamespacedName{Name: poolName, Namespace: "default"} - - t.Run("Step 1 & 2: Create and Read InferencePool", func(t *testing.T) { - acceptedCondition := metav1.Condition{ - Type: string(gatewayv1.GatewayConditionAccepted), - Status: metav1.ConditionTrue, - Reason: "", - } - // Assuming InferencePoolMustHaveCondition uses appropriate timeouts from s.TimeoutConfig internally - // (e.g. DefaultTestTimeout or a more specific one if defined for generic resource conditions) - infrakubernetes.InferencePoolMustHaveCondition(t, s.Client, s.TimeoutConfig, poolNN, acceptedCondition) - - createdPool := &inferenceapi.InferencePool{} - err := s.Client.Get(context.TODO(), poolNN, createdPool) - require.NoErrorf(t, err, "Failed to get InferencePool %s/%s", poolNN.Namespace, poolNN.Name) - - // Verify fields based on the current API (using .Spec.Selector) - // TODO: https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/766 - // API Discrepancy: InferencePoolSpec pod selector field name mismatch between API - // Proposal 002 and current Go type definition - require.NotNil(t, createdPool.Spec.Selector, "Selector should not be nil") - // Assuming LabelKey and LabelValue are effectively strings for this map access - require.Equal(t, inferenceapi.LabelValue("lifecycle-test-app"), createdPool.Spec.Selector[inferenceapi.LabelKey("app")], - "Selector 'app' label does not match manifest") - - t.Logf("Successfully created and read InferencePool %s/%s", poolNN.Namespace, poolNN.Name) - }) - - t.Run("Step 3: Update InferencePool", func(t *testing.T) { - originalPool := &inferenceapi.InferencePool{} - err := s.Client.Get(context.TODO(), poolNN, originalPool) - require.NoErrorf(t, err, "Failed to get InferencePool %s for update", poolNN.String()) - - updatedPool := originalPool.DeepCopy() - // Update based on the current API (using .Spec.Selector) - updatedPool.Spec.Selector = map[inferenceapi.LabelKey]inferenceapi.LabelValue{ - inferenceapi.LabelKey("app"): inferenceapi.LabelValue("lifecycle-test-app-updated"), - } - - err = s.Client.Update(context.TODO(), updatedPool) - require.NoErrorf(t, err, "Failed to update InferencePool %s", poolNN.String()) - - fetchedAfterUpdate := &inferenceapi.InferencePool{} - // Use DefaultTestTimeout for the overall wait and a 1-second poll interval for Eventually. - require.Eventually(t, func() bool { - if err := s.Client.Get(context.TODO(), poolNN, fetchedAfterUpdate); err != nil { - return false - } - if fetchedAfterUpdate.Spec.Selector == nil { - return false - } - // Check based on the current API (using .Spec.Selector) - return fetchedAfterUpdate.Spec.Selector[inferenceapi.LabelKey("app")] == inferenceapi.LabelValue("lifecycle-test-app-updated") - }, s.TimeoutConfig.DefaultTestTimeout, 1*time.Second, "Failed to observe updated Selector") - - require.NotNil(t, fetchedAfterUpdate.Spec.Selector, "Updated Selector should not be nil") - require.Equal(t, inferenceapi.LabelValue("lifecycle-test-app-updated"), fetchedAfterUpdate.Spec.Selector[inferenceapi.LabelKey("app")], - "Selector 'app' label was not updated as expected") - t.Logf("Successfully updated InferencePool %s/%s", poolNN.Namespace, poolNN.Name) - }) - - t.Run("Step 4: Delete InferencePool", func(t *testing.T) { - t.Logf("Attempting to explicitly delete InferencePool %s/%s for verification", poolNN.Namespace, poolNN.Name) - poolToDelete := &inferenceapi.InferencePool{ - ObjectMeta: metav1.ObjectMeta{ - Name: poolName, - Namespace: hardcodedNamespace, - }, - } - err := s.Client.Delete(context.TODO(), poolToDelete) - if err != nil && !apierrors.IsNotFound(err) { - require.NoErrorf(t, err, "Failed to delete InferencePool %s", poolNN.String()) - } - - deletedPool := &inferenceapi.InferencePool{} - // Use DeleteTimeout for the overall wait and a 1-second poll interval for Eventually. - require.Eventually(t, func() bool { - fetchErr := s.Client.Get(context.TODO(), poolNN, deletedPool) - if fetchErr != nil { - if apierrors.IsNotFound(fetchErr) { - return true - } - } - return false - }, s.TimeoutConfig.DeleteTimeout, 1*time.Second, "InferencePool %s not deleted within timeout", poolNN.String()) - - t.Logf("Successfully verified deletion of InferencePool %s/%s", poolNN.Namespace, poolNN.Name) - }) - }, -} From d396ecb4a9eb4c45cc2ed754f051df6c84e6809c Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 17:38:41 +0000 Subject: [PATCH 03/20] correct Lint error Multiplication of durations --- conformance/utils/kubernetes/helpers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conformance/utils/kubernetes/helpers.go b/conformance/utils/kubernetes/helpers.go index e4bbf3d68..803d43a61 100644 --- a/conformance/utils/kubernetes/helpers.go +++ b/conformance/utils/kubernetes/helpers.go @@ -75,10 +75,10 @@ func InferencePoolMustHaveCondition(t *testing.T, c client.Client, timeoutConfig var lastObservedPool *inferenceapi.InferencePool var lastError error var conditionFound bool - var interval time.Duration = 5 // pull interval for status checks. + var interval time.Duration = 5 * time.Second // pull interval for status checks. // TODO: Make retry interval configurable. - waitErr := wait.PollUntilContextTimeout(context.Background(), interval*time.Second, timeoutConfig.DefaultTestTimeout, true, func(ctx context.Context) (bool, error) { + waitErr := wait.PollUntilContextTimeout(context.Background(), interval, timeoutConfig.DefaultTestTimeout, true, func(ctx context.Context) (bool, error) { pool := &inferenceapi.InferencePool{} // This is the type instance used for Get err := c.Get(ctx, poolNN, pool) if err != nil { From 0455c479b20adab8194cb902356a71b8a6f2d413 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 22:29:12 +0000 Subject: [PATCH 04/20] Fix missing containerPort, is missing --- conformance/tests/basic/inferencepool_accepted.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/tests/basic/inferencepool_accepted.yaml b/conformance/tests/basic/inferencepool_accepted.yaml index ac41d6a2d..90931e330 100644 --- a/conformance/tests/basic/inferencepool_accepted.yaml +++ b/conformance/tests/basic/inferencepool_accepted.yaml @@ -30,7 +30,7 @@ spec: - --http-port=8080 ports: - name: http - containerPort: + containerPort: 8080 readinessProbe: httpGet: path: / From b531e66539c60d842e722ad6410f3132992e1523 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 22:35:59 +0000 Subject: [PATCH 05/20] change gateway name from "gateway-conformance-app" to "conformance-gateway" --- conformance/conformance.go | 2 +- conformance/resources/manifests/manifests.yaml | 2 +- conformance/tests/basic/inferencepool_accepted.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index dba109a0a..56d464a8a 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -66,7 +66,7 @@ import ( // Constants for the shared Gateway const ( - SharedGatewayName = "gateway-conformance-app" // Name of the Gateway in manifests.yaml + SharedGatewayName = "conformance-gateway" // Name of the Gateway in manifests.yaml SharedGatewayNamespace = "gateway-conformance-infra" // Namespace of the Gateway ) diff --git a/conformance/resources/manifests/manifests.yaml b/conformance/resources/manifests/manifests.yaml index 6a5d035f4..190a8845e 100644 --- a/conformance/resources/manifests/manifests.yaml +++ b/conformance/resources/manifests/manifests.yaml @@ -38,7 +38,7 @@ metadata: apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: - name: gateway-conformance-app + name: conformance-gateway namespace: gateway-conformance-infra spec: # The conformance suite runner will replace this placeholder diff --git a/conformance/tests/basic/inferencepool_accepted.yaml b/conformance/tests/basic/inferencepool_accepted.yaml index 90931e330..ecee5b3ca 100644 --- a/conformance/tests/basic/inferencepool_accepted.yaml +++ b/conformance/tests/basic/inferencepool_accepted.yaml @@ -77,7 +77,7 @@ spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway - name: gateway-conformance-app # Name of the shared Gateway from maniffests.yaml + name: conformance-gateway # Name of the shared Gateway from maniffests.yaml namespace: gateway-conformance-infra # Namespace of the shared Gateway sectionName: http rules: From 5510e699d12e5051d52ecbf9b508e7aff3e20d7a Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 22:46:34 +0000 Subject: [PATCH 06/20] clarify why K8s types are needed. --- conformance/conformance.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 56d464a8a..3e5519919 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -97,8 +97,8 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { scheme := runtime.NewScheme() t.Log("Registering API types with scheme...") - // Add core Kubernetes types (like Secret, Service, etc.) to the scheme - require.NoError(t, clientsetscheme.AddToScheme(scheme), "Failed to add core Kubernetes types to scheme") // CORE FIX + // Register core K8s types (like v1.Secret for certs) to scheme, needed by client to create/manage these resources. + require.NoError(t, clientsetscheme.AddToScheme(scheme), "Failed to add core Kubernetes types to scheme") // Add Gateway API types require.NoError(t, gatewayv1.Install(scheme), "Failed to install gatewayv1 types into scheme") // Add APIExtensions types (for CRDs) From 04c97985e7310f3cdf2089349c57bb95bb821967 Mon Sep 17 00:00:00 2001 From: sina chavoshi Date: Mon, 5 May 2025 15:48:01 -0700 Subject: [PATCH 07/20] Update conformance/conformance.go Co-authored-by: Lior Lieberman --- conformance/conformance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 3e5519919..4e579f2af 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -105,7 +105,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { require.NoError(t, apiextensionsv1.AddToScheme(scheme), "Failed to add apiextensionsv1 types to scheme") // Register Inference Extension API types - t.Logf("Attempting to install inferencev1alpha2 types (like InferencePool) into scheme from package: %s", inferencev1alpha2.GroupName) + t.Logf("Attempting to install inferencev1alpha2 types into scheme from package: %s", inferencev1alpha2.GroupName) require.NoError(t, inferencev1alpha2.Install(scheme), "Failed to install inferencev1alpha2 types into scheme.") clientOptions := client.Options{Scheme: scheme} From 9427f311a9238520b5a356f3141a6edaa6cb3b95 Mon Sep 17 00:00:00 2001 From: sina chavoshi Date: Mon, 5 May 2025 15:48:24 -0700 Subject: [PATCH 08/20] Update conformance/conformance.go Co-authored-by: Lior Lieberman --- conformance/conformance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 4e579f2af..9a5d6cc91 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -134,7 +134,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { _ = inferenceExtensionVersion // Avoid unused variable error until implemented baseManifestsValue := "resources/manifests/manifests.yaml" - t.Logf("Explicitly setting BaseManifests path to: %q", baseManifestsValue) + t.Logf("applying base manifests from: %q", baseManifestsValue) opts := confsuite.ConformanceOptions{ Client: c, From 093d3ad94b91ebf977eceb74df4137cb1d99e7e0 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 23:00:58 +0000 Subject: [PATCH 09/20] remove for loop when adding SupportedFeatures --- conformance/conformance.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 9a5d6cc91..26835c647 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -172,9 +172,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { } // Remove any features explicitly exempted via flags. - for feature := range opts.ExemptFeatures { - opts.SupportedFeatures.Delete(feature) - } + opts.SupportedFeatures = opts.SupportedFeatures.Insert(GatewayLayerProfile.CoreFeatures.UnsortedList()...) t.Logf("CONFORMANCE.GO: Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) return opts From e3c838115d83019f3b26bd3788b345937039ab00 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 23:04:59 +0000 Subject: [PATCH 10/20] remove exessive logging --- conformance/conformance.go | 1 - 1 file changed, 1 deletion(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 26835c647..8aab518c0 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -134,7 +134,6 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { _ = inferenceExtensionVersion // Avoid unused variable error until implemented baseManifestsValue := "resources/manifests/manifests.yaml" - t.Logf("applying base manifests from: %q", baseManifestsValue) opts := confsuite.ConformanceOptions{ Client: c, From c9a8f7719874fcc87a573b8a297b8fb02b287670 Mon Sep 17 00:00:00 2001 From: sina chavoshi Date: Mon, 5 May 2025 16:09:31 -0700 Subject: [PATCH 11/20] Update conformance/conformance.go Co-authored-by: Lior Lieberman --- conformance/conformance.go | 1 - 1 file changed, 1 deletion(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 8aab518c0..3e24c8377 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -196,7 +196,6 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) cSuite, err := confsuite.NewConformanceTestSuite(opts) require.NoError(t, err, "error initializing conformance suite") - t.Log("Setting up Inference Extension conformance tests (applying base manifests via cSuite.Setup)") cSuite.Setup(t, tests.ConformanceTests) // TODO: Move gateway setup validation to a helper method. From 5ac19ffdf8b359414aaebd09ef49e581366b1fbd Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 23:27:41 +0000 Subject: [PATCH 12/20] move excess debug logs behind debug flag. --- conformance/conformance.go | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 3e24c8377..077786832 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -164,15 +164,25 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { // Populate SupportedFeatures based on the GatewayLayerProfile. // Since all features are mandatory for this profile, add all defined core features. if opts.ConformanceProfiles.Has(GatewayLayerProfileName) { - t.Logf("CONFORMANCE.GO: Populating SupportedFeatures from GatewayLayerProfile.CoreFeatures (%v)", GatewayLayerProfile.CoreFeatures.UnsortedList()) - for feature := range GatewayLayerProfile.CoreFeatures { - opts.SupportedFeatures.Insert(feature) + if opts.Debug { + t.Logf("CONFORMANCE.GO: Populating SupportedFeatures with GatewayLayerProfile.CoreFeatures: %v", GatewayLayerProfile.CoreFeatures.UnsortedList()) + } + if GatewayLayerProfile.CoreFeatures.Len() > 0 { + opts.SupportedFeatures = opts.SupportedFeatures.Insert(GatewayLayerProfile.CoreFeatures.UnsortedList()...) } } // Remove any features explicitly exempted via flags. - opts.SupportedFeatures = opts.SupportedFeatures.Insert(GatewayLayerProfile.CoreFeatures.UnsortedList()...) - t.Logf("CONFORMANCE.GO: Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) + if opts.ExemptFeatures.Len() > 0 { + if opts.Debug { + t.Logf("CONFORMANCE.GO: Removing ExemptFeatures from SupportedFeatures: %v", opts.ExemptFeatures.UnsortedList()) + } + opts.SupportedFeatures = opts.SupportedFeatures.Delete(opts.ExemptFeatures.UnsortedList()...) + } + + if opts.Debug { + t.Logf("CONFORMANCE.GO: Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) + } return opts } @@ -185,7 +195,9 @@ func RunConformance(t *testing.T) { // RunConformanceWithOptions runs the Inference Extension conformance tests with specific options. func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) { t.Logf("Running Inference Extension conformance tests with GatewayClass %s", opts.GatewayClassName) - t.Logf("CONFORMANCE.GO RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) + if opts.Debug { + t.Logf("CONFORMANCE.GO RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) + } // Register the GatewayLayerProfile with the suite runner. // In the future, other profiles (EPP, ModelServer) will also be registered here, @@ -201,10 +213,8 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) // TODO: Move gateway setup validation to a helper method. t.Logf("CONFORMANCE.GO: Attempting to fetch Gateway %s/%s after cSuite.Setup().", SharedGatewayNamespace, SharedGatewayName) sharedGwNN := types.NamespacedName{Name: SharedGatewayName, Namespace: SharedGatewayNamespace} - t.Logf("Attempting to directly fetch Gateway %s/%s after cSuite.Setup()", sharedGwNN.Namespace, sharedGwNN.Name) gw := &gatewayv1.Gateway{} var getErr error - // TODO: Confirm what is a reasonable wait time here. maxRetries := 10 for i := 0; i < maxRetries; i++ { getErr = cSuite.Client.Get(context.TODO(), sharedGwNN, gw) @@ -214,16 +224,20 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) break } if apierrors.IsNotFound(getErr) { - t.Logf("CONFORMANCE.GO: Gateway %s/%s not found (attempt %d/%d). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries) + if opts.Debug { + t.Logf("CONFORMANCE.GO: Gateway %s/%s not found (attempt %d/%d). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries) + } time.Sleep(1 * time.Second) } else { t.Logf("CONFORMANCE.GO: Error fetching Gateway %s/%s (attempt %d/%d): %v.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries, getErr) break } } - require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s after cSuite.Setup(), even after retries. It should have been created if the Applier worked with the correct manifest.", sharedGwNN.Namespace, sharedGwNN.Name) + require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s after cSuite.Setup(), even after retries.", sharedGwNN.Namespace, sharedGwNN.Name) - t.Logf("CONFORMANCE.GO: Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) + if opts.Debug { + t.Logf("CONFORMANCE.GO: Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) + } apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ Type: string(gatewayv1.GatewayConditionAccepted), Status: metav1.ConditionTrue, From 3b8df3bf0208810bf087d8c4719f4979c0c4b7ab Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 23:34:19 +0000 Subject: [PATCH 13/20] remove CONFORMANCE.GO prefix from logs. --- conformance/conformance.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 077786832..d41089ca2 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -165,7 +165,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { // Since all features are mandatory for this profile, add all defined core features. if opts.ConformanceProfiles.Has(GatewayLayerProfileName) { if opts.Debug { - t.Logf("CONFORMANCE.GO: Populating SupportedFeatures with GatewayLayerProfile.CoreFeatures: %v", GatewayLayerProfile.CoreFeatures.UnsortedList()) + t.Logf("Populating SupportedFeatures with GatewayLayerProfile.CoreFeatures: %v", GatewayLayerProfile.CoreFeatures.UnsortedList()) } if GatewayLayerProfile.CoreFeatures.Len() > 0 { opts.SupportedFeatures = opts.SupportedFeatures.Insert(GatewayLayerProfile.CoreFeatures.UnsortedList()...) @@ -175,13 +175,13 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { // Remove any features explicitly exempted via flags. if opts.ExemptFeatures.Len() > 0 { if opts.Debug { - t.Logf("CONFORMANCE.GO: Removing ExemptFeatures from SupportedFeatures: %v", opts.ExemptFeatures.UnsortedList()) + t.Logf("Removing ExemptFeatures from SupportedFeatures: %v", opts.ExemptFeatures.UnsortedList()) } opts.SupportedFeatures = opts.SupportedFeatures.Delete(opts.ExemptFeatures.UnsortedList()...) } if opts.Debug { - t.Logf("CONFORMANCE.GO: Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) + t.Logf("Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) } return opts @@ -211,7 +211,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) cSuite.Setup(t, tests.ConformanceTests) // TODO: Move gateway setup validation to a helper method. - t.Logf("CONFORMANCE.GO: Attempting to fetch Gateway %s/%s after cSuite.Setup().", SharedGatewayNamespace, SharedGatewayName) + t.Logf("Attempting to fetch Gateway %s/%s after cSuite.Setup().", SharedGatewayNamespace, SharedGatewayName) sharedGwNN := types.NamespacedName{Name: SharedGatewayName, Namespace: SharedGatewayNamespace} gw := &gatewayv1.Gateway{} var getErr error @@ -219,24 +219,24 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) for i := 0; i < maxRetries; i++ { getErr = cSuite.Client.Get(context.TODO(), sharedGwNN, gw) if getErr == nil { - t.Logf("CONFORMANCE.GO: Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s", + t.Logf("Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s", sharedGwNN.Namespace, sharedGwNN.Name, gw.Spec.GatewayClassName) break } if apierrors.IsNotFound(getErr) { if opts.Debug { - t.Logf("CONFORMANCE.GO: Gateway %s/%s not found (attempt %d/%d). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries) + t.Logf("Gateway %s/%s not found (attempt %d/%d). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries) } time.Sleep(1 * time.Second) } else { - t.Logf("CONFORMANCE.GO: Error fetching Gateway %s/%s (attempt %d/%d): %v.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries, getErr) + t.Logf("Error fetching Gateway %s/%s (attempt %d/%d): %v.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries, getErr) break } } require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s after cSuite.Setup(), even after retries.", sharedGwNN.Namespace, sharedGwNN.Name) if opts.Debug { - t.Logf("CONFORMANCE.GO: Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) + t.Logf("Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) } apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ Type: string(gatewayv1.GatewayConditionAccepted), @@ -248,7 +248,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) }) _, err = apikubernetes.WaitForGatewayAddress(t, cSuite.Client, cSuite.TimeoutConfig, apikubernetes.NewGatewayRef(sharedGwNN)) require.NoErrorf(t, err, "shared gateway %s/%s did not get an address", SharedGatewayNamespace, SharedGatewayName) - t.Logf("CONFORMANCE.GO: Shared Gateway %s/%s is ready.", SharedGatewayNamespace, SharedGatewayName) + t.Logf("Shared Gateway %s/%s is ready.", SharedGatewayNamespace, SharedGatewayName) t.Log("Running Inference Extension conformance tests against all registered tests") err = cSuite.Run(t, tests.ConformanceTests) From bde61b88a3043308d6367b456252f32b7d9514ce Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Mon, 5 May 2025 23:51:04 +0000 Subject: [PATCH 14/20] change the pull logic and use default value from GatewayMustHaveAddress --- conformance/conformance.go | 54 ++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index d41089ca2..862e97dbb 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -20,6 +20,7 @@ package conformance import ( "context" + "errors" "fmt" "io/fs" "os" @@ -31,6 +32,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" clientsetscheme "k8s.io/client-go/kubernetes/scheme" @@ -213,27 +215,51 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) // TODO: Move gateway setup validation to a helper method. t.Logf("Attempting to fetch Gateway %s/%s after cSuite.Setup().", SharedGatewayNamespace, SharedGatewayName) sharedGwNN := types.NamespacedName{Name: SharedGatewayName, Namespace: SharedGatewayNamespace} - gw := &gatewayv1.Gateway{} - var getErr error - maxRetries := 10 - for i := 0; i < maxRetries; i++ { - getErr = cSuite.Client.Get(context.TODO(), sharedGwNN, gw) - if getErr == nil { + gw := &gatewayv1.Gateway{} // This gw instance will be populated by the poll function + + // Define polling interval + // TODO: Make this configurable using a local TimeoutConfig. + pollingInterval := 5 * time.Second + // Use the GatewayMustHaveAddress timeout from the suite's TimeoutConfig for the Gateway object to appear + waitForGatewayCreationTimeout := opts.TimeoutConfig.GatewayMustHaveAddress + + if opts.Debug { + t.Logf("Waiting up to %v for Gateway object %s/%s to appear after manifest application...", waitForGatewayCreationTimeout, SharedGatewayNamespace, SharedGatewayName) + } + + ctx := context.TODO() + pollErr := wait.PollUntilContextTimeout(ctx, pollingInterval, waitForGatewayCreationTimeout, true, func(pollCtx context.Context) (bool, error) { + fetchErr := cSuite.Client.Get(pollCtx, sharedGwNN, gw) + if fetchErr == nil { t.Logf("Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s", - sharedGwNN.Namespace, sharedGwNN.Name, gw.Spec.GatewayClassName) - break + gw.Namespace, gw.Name, gw.Spec.GatewayClassName) + return true, nil } - if apierrors.IsNotFound(getErr) { + if apierrors.IsNotFound(fetchErr) { if opts.Debug { - t.Logf("Gateway %s/%s not found (attempt %d/%d). Retrying in 1s...", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries) + t.Logf("Gateway %s/%s not found, still waiting...", sharedGwNN.Namespace, sharedGwNN.Name) } - time.Sleep(1 * time.Second) + return false, nil // Not found, continue polling + } + // For any other error, stop polling and return this error + t.Logf("Error fetching Gateway %s/%s: %v. Halting polling for this attempt.", sharedGwNN.Namespace, sharedGwNN.Name, fetchErr) + return false, fetchErr + }) + + // Check if polling timed out or an error occurred during polling + if pollErr != nil { + var failureMessage string + if errors.Is(pollErr, context.DeadlineExceeded) { + failureMessage = fmt.Sprintf("Timed out after %v waiting for Gateway object %s/%s to appear in the API server.", + waitForGatewayCreationTimeout, SharedGatewayNamespace, SharedGatewayName) } else { - t.Logf("Error fetching Gateway %s/%s (attempt %d/%d): %v.", sharedGwNN.Namespace, sharedGwNN.Name, i+1, maxRetries, getErr) - break + failureMessage = fmt.Sprintf("Error while waiting for Gateway object %s/%s to appear: %v.", + SharedGatewayNamespace, SharedGatewayName, pollErr) } + finalMessage := fmt.Sprintf("%s The Gateway object should have been created by the base manifest application via cSuite.Setup().", failureMessage) + require.FailNow(t, finalMessage) // Use FailNow to stop if the Gateway isn't found. } - require.NoErrorf(t, getErr, "Failed to fetch Gateway %s/%s after cSuite.Setup(), even after retries.", sharedGwNN.Namespace, sharedGwNN.Name) + // If pollErr is nil, the 'gw' variable is now populated with the fetched Gateway. if opts.Debug { t.Logf("Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) From 57d68fdd8c691bf8dcbf3fd0ea6dc7e725f9efab Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Tue, 6 May 2025 00:02:43 +0000 Subject: [PATCH 15/20] fix mt.Sprintf can be replaced with string concatenation --- conformance/conformance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 862e97dbb..4c3a32190 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -256,7 +256,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) failureMessage = fmt.Sprintf("Error while waiting for Gateway object %s/%s to appear: %v.", SharedGatewayNamespace, SharedGatewayName, pollErr) } - finalMessage := fmt.Sprintf("%s The Gateway object should have been created by the base manifest application via cSuite.Setup().", failureMessage) + finalMessage := failureMessage + " The Gateway object should have been created by the base manifest application via cSuite.Setup()." require.FailNow(t, finalMessage) // Use FailNow to stop if the Gateway isn't found. } // If pollErr is nil, the 'gw' variable is now populated with the fetched Gateway. From fa93b155c465dfd9001adeaa5e09b1c26ab15cad Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Wed, 7 May 2025 17:21:31 +0000 Subject: [PATCH 16/20] add a function for logDebug --- conformance/conformance.go | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 4c3a32190..29110ce99 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -88,6 +88,14 @@ var GatewayLayerProfile = confsuite.ConformanceProfile{ CoreFeatures: InferenceCoreFeatures, } +// logDebugf conditionally logs a debug message if debug mode is enabled. +func logDebugf(t *testing.T, debug bool, format string, args ...any) { + if debug { + t.Helper() + t.Logf(format, args...) + } +} + // DefaultOptions parses command line flags and sets up the suite options. // Adapted from the core Gateway API conformance suite. func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { @@ -166,9 +174,7 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { // Populate SupportedFeatures based on the GatewayLayerProfile. // Since all features are mandatory for this profile, add all defined core features. if opts.ConformanceProfiles.Has(GatewayLayerProfileName) { - if opts.Debug { - t.Logf("Populating SupportedFeatures with GatewayLayerProfile.CoreFeatures: %v", GatewayLayerProfile.CoreFeatures.UnsortedList()) - } + logDebugf(t, opts.Debug, "Populating SupportedFeatures with GatewayLayerProfile.CoreFeatures: %v", GatewayLayerProfile.CoreFeatures.UnsortedList()) if GatewayLayerProfile.CoreFeatures.Len() > 0 { opts.SupportedFeatures = opts.SupportedFeatures.Insert(GatewayLayerProfile.CoreFeatures.UnsortedList()...) } @@ -176,15 +182,11 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { // Remove any features explicitly exempted via flags. if opts.ExemptFeatures.Len() > 0 { - if opts.Debug { - t.Logf("Removing ExemptFeatures from SupportedFeatures: %v", opts.ExemptFeatures.UnsortedList()) - } + logDebugf(t, opts.Debug, "Removing ExemptFeatures from SupportedFeatures: %v", opts.ExemptFeatures.UnsortedList()) opts.SupportedFeatures = opts.SupportedFeatures.Delete(opts.ExemptFeatures.UnsortedList()...) } - if opts.Debug { - t.Logf("Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) - } + logDebugf(t, opts.Debug, "Final opts.SupportedFeatures: %v", opts.SupportedFeatures.UnsortedList()) return opts } @@ -196,10 +198,9 @@ func RunConformance(t *testing.T) { // RunConformanceWithOptions runs the Inference Extension conformance tests with specific options. func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) { + t.Helper() t.Logf("Running Inference Extension conformance tests with GatewayClass %s", opts.GatewayClassName) - if opts.Debug { - t.Logf("CONFORMANCE.GO RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) - } + logDebugf(t, opts.Debug, "CONFORMANCE.GO RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) // Register the GatewayLayerProfile with the suite runner. // In the future, other profiles (EPP, ModelServer) will also be registered here, @@ -223,9 +224,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) // Use the GatewayMustHaveAddress timeout from the suite's TimeoutConfig for the Gateway object to appear waitForGatewayCreationTimeout := opts.TimeoutConfig.GatewayMustHaveAddress - if opts.Debug { - t.Logf("Waiting up to %v for Gateway object %s/%s to appear after manifest application...", waitForGatewayCreationTimeout, SharedGatewayNamespace, SharedGatewayName) - } + logDebugf(t, opts.Debug, "Waiting up to %v for Gateway object %s/%s to appear after manifest application...", waitForGatewayCreationTimeout, SharedGatewayNamespace, SharedGatewayName) ctx := context.TODO() pollErr := wait.PollUntilContextTimeout(ctx, pollingInterval, waitForGatewayCreationTimeout, true, func(pollCtx context.Context) (bool, error) { @@ -236,9 +235,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) return true, nil } if apierrors.IsNotFound(fetchErr) { - if opts.Debug { - t.Logf("Gateway %s/%s not found, still waiting...", sharedGwNN.Namespace, sharedGwNN.Name) - } + logDebugf(t, opts.Debug, "Gateway %s/%s not found, still waiting...", sharedGwNN.Namespace, sharedGwNN.Name) return false, nil // Not found, continue polling } // For any other error, stop polling and return this error @@ -261,9 +258,8 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) } // If pollErr is nil, the 'gw' variable is now populated with the fetched Gateway. - if opts.Debug { - t.Logf("Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) - } + logDebugf(t, opts.Debug, "Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) + apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ Type: string(gatewayv1.GatewayConditionAccepted), Status: metav1.ConditionTrue, From 06f29ccb5861d79d19c5e7e9e4c570f3e47a199f Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Wed, 7 May 2025 17:35:28 +0000 Subject: [PATCH 17/20] factor out ensureGatewayAvailableAndReady --- conformance/conformance.go | 80 ++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 29110ce99..0f31f02fb 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -213,33 +213,60 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) cSuite.Setup(t, tests.ConformanceTests) - // TODO: Move gateway setup validation to a helper method. - t.Logf("Attempting to fetch Gateway %s/%s after cSuite.Setup().", SharedGatewayNamespace, SharedGatewayName) sharedGwNN := types.NamespacedName{Name: SharedGatewayName, Namespace: SharedGatewayNamespace} + + // Validate Gateway setup. + ensureGatewayAvailableAndReady(t, cSuite.Client, opts, sharedGwNN) + t.Log("Running Inference Extension conformance tests against all registered tests") + err = cSuite.Run(t, tests.ConformanceTests) + require.NoError(t, err, "error running conformance tests") + + // Generate and write the report if requested. + if opts.ReportOutputPath != "" { + t.Log("Generating Inference Extension conformance report") + report, err := cSuite.Report() // Use the existing report generation logic. + require.NoError(t, err, "error generating conformance report") + + // TODO: Modify the report struct here if channel, version need to be modified. + // Example (requires adding fields to confapis.ConformanceReport): + // report.GatewayAPIInferenceExtensionChannel = opts.GatewayAPIInferenceExtensionChannel + // report.GatewayAPIInferenceExtensionVersion = opts.GatewayAPIInferenceExtensionVersion + + err = writeReport(t.Logf, *report, opts.ReportOutputPath) + require.NoError(t, err, "error writing conformance report") + } +} + +// ensureGatewayAvailableAndReady polls for the specified Gateway to exist and become ready +// with an address and programmed conditions. +func ensureGatewayAvailableAndReady(t *testing.T, k8sClient client.Client, opts confsuite.ConformanceOptions, gatewayNN types.NamespacedName) { + t.Helper() + + t.Logf("Attempting to fetch Gateway %s/%s.", gatewayNN.Namespace, gatewayNN.Name) gw := &gatewayv1.Gateway{} // This gw instance will be populated by the poll function // Define polling interval - // TODO: Make this configurable using a local TimeoutConfig. + // TODO: Make this configurable using a local TimeoutConfig (from ConformanceOptions perhaps) pollingInterval := 5 * time.Second // Use the GatewayMustHaveAddress timeout from the suite's TimeoutConfig for the Gateway object to appear waitForGatewayCreationTimeout := opts.TimeoutConfig.GatewayMustHaveAddress - logDebugf(t, opts.Debug, "Waiting up to %v for Gateway object %s/%s to appear after manifest application...", waitForGatewayCreationTimeout, SharedGatewayNamespace, SharedGatewayName) + logDebugf(t, opts.Debug, "Waiting up to %v for Gateway object %s/%s to appear after manifest application...", waitForGatewayCreationTimeout, gatewayNN.Namespace, gatewayNN.Name) ctx := context.TODO() pollErr := wait.PollUntilContextTimeout(ctx, pollingInterval, waitForGatewayCreationTimeout, true, func(pollCtx context.Context) (bool, error) { - fetchErr := cSuite.Client.Get(pollCtx, sharedGwNN, gw) + fetchErr := k8sClient.Get(pollCtx, gatewayNN, gw) if fetchErr == nil { t.Logf("Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s", gw.Namespace, gw.Name, gw.Spec.GatewayClassName) return true, nil } if apierrors.IsNotFound(fetchErr) { - logDebugf(t, opts.Debug, "Gateway %s/%s not found, still waiting...", sharedGwNN.Namespace, sharedGwNN.Name) + logDebugf(t, opts.Debug, "Gateway %s/%s not found, still waiting...", gatewayNN.Namespace, gatewayNN.Name) return false, nil // Not found, continue polling } // For any other error, stop polling and return this error - t.Logf("Error fetching Gateway %s/%s: %v. Halting polling for this attempt.", sharedGwNN.Namespace, sharedGwNN.Name, fetchErr) + t.Logf("Error fetching Gateway %s/%s: %v. Halting polling for this attempt.", gatewayNN.Namespace, gatewayNN.Name, fetchErr) return false, fetchErr }) @@ -248,48 +275,27 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) var failureMessage string if errors.Is(pollErr, context.DeadlineExceeded) { failureMessage = fmt.Sprintf("Timed out after %v waiting for Gateway object %s/%s to appear in the API server.", - waitForGatewayCreationTimeout, SharedGatewayNamespace, SharedGatewayName) + waitForGatewayCreationTimeout, gatewayNN.Namespace, gatewayNN.Name) } else { failureMessage = fmt.Sprintf("Error while waiting for Gateway object %s/%s to appear: %v.", - SharedGatewayNamespace, SharedGatewayName, pollErr) + gatewayNN.Namespace, gatewayNN.Name, pollErr) } - finalMessage := failureMessage + " The Gateway object should have been created by the base manifest application via cSuite.Setup()." + finalMessage := failureMessage + " The Gateway object should have been created by the base manifest application." require.FailNow(t, finalMessage) // Use FailNow to stop if the Gateway isn't found. } - // If pollErr is nil, the 'gw' variable is now populated with the fetched Gateway. - - logDebugf(t, opts.Debug, "Waiting for shared Gateway %s/%s to be ready", SharedGatewayNamespace, SharedGatewayName) - apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ + logDebugf(t, opts.Debug, "Waiting for shared Gateway %s/%s to be ready", gatewayNN.Namespace, gatewayNN.Name) + apikubernetes.GatewayMustHaveCondition(t, k8sClient, opts.TimeoutConfig, gatewayNN, metav1.Condition{ Type: string(gatewayv1.GatewayConditionAccepted), Status: metav1.ConditionTrue, }) - apikubernetes.GatewayMustHaveCondition(t, cSuite.Client, cSuite.TimeoutConfig, sharedGwNN, metav1.Condition{ + apikubernetes.GatewayMustHaveCondition(t, k8sClient, opts.TimeoutConfig, gatewayNN, metav1.Condition{ Type: string(gatewayv1.GatewayConditionProgrammed), Status: metav1.ConditionTrue, }) - _, err = apikubernetes.WaitForGatewayAddress(t, cSuite.Client, cSuite.TimeoutConfig, apikubernetes.NewGatewayRef(sharedGwNN)) - require.NoErrorf(t, err, "shared gateway %s/%s did not get an address", SharedGatewayNamespace, SharedGatewayName) - t.Logf("Shared Gateway %s/%s is ready.", SharedGatewayNamespace, SharedGatewayName) - - t.Log("Running Inference Extension conformance tests against all registered tests") - err = cSuite.Run(t, tests.ConformanceTests) - require.NoError(t, err, "error running conformance tests") - - // Generate and write the report if requested. - if opts.ReportOutputPath != "" { - t.Log("Generating Inference Extension conformance report") - report, err := cSuite.Report() // Use the existing report generation logic. - require.NoError(t, err, "error generating conformance report") - - // TODO: Modify the report struct here if channel, version need to be modified. - // Example (requires adding fields to confapis.ConformanceReport): - // report.GatewayAPIInferenceExtensionChannel = opts.GatewayAPIInferenceExtensionChannel - // report.GatewayAPIInferenceExtensionVersion = opts.GatewayAPIInferenceExtensionVersion - - err = writeReport(t.Logf, *report, opts.ReportOutputPath) - require.NoError(t, err, "error writing conformance report") - } + _, err := apikubernetes.WaitForGatewayAddress(t, k8sClient, opts.TimeoutConfig, apikubernetes.NewGatewayRef(gatewayNN)) + require.NoErrorf(t, err, "shared gateway %s/%s did not get an address", gatewayNN.Namespace, gatewayNN.Name) + t.Logf("Shared Gateway %s/%s is ready.", gatewayNN.Namespace, gatewayNN.Name) } // writeReport writes the generated conformance report to the specified output file or logs it. From da2e9ef6671a051b11aa8fca6ed3bada92152668 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Wed, 7 May 2025 17:39:35 +0000 Subject: [PATCH 18/20] removed todo comment in helper.go --- conformance/utils/kubernetes/helpers.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/conformance/utils/kubernetes/helpers.go b/conformance/utils/kubernetes/helpers.go index 803d43a61..af7d5a2a4 100644 --- a/conformance/utils/kubernetes/helpers.go +++ b/conformance/utils/kubernetes/helpers.go @@ -37,8 +37,6 @@ import ( // Import necessary utilities from the core Gateway API conformance suite "sigs.k8s.io/gateway-api/conformance/utils/config" - // We will implement the condition checking logic directly or use a local helper - // to avoid direct dependency on unexported/differently packaged Gateway API utils if issues persist. ) // checkCondition is a helper function similar to findConditionInList or CheckCondition From 5e1d8e9c82fe53462087aa9f91e5f1faff978343 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Wed, 7 May 2025 17:41:30 +0000 Subject: [PATCH 19/20] remove CONFORMANCE.GO from log --- conformance/conformance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 0f31f02fb..9888efafb 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -200,7 +200,7 @@ func RunConformance(t *testing.T) { func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) { t.Helper() t.Logf("Running Inference Extension conformance tests with GatewayClass %s", opts.GatewayClassName) - logDebugf(t, opts.Debug, "CONFORMANCE.GO RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) + logDebugf(t, opts.Debug, "RunConformanceWithOptions: BaseManifests path being used by opts: %q", opts.BaseManifests) // Register the GatewayLayerProfile with the suite runner. // In the future, other profiles (EPP, ModelServer) will also be registered here, From d8577b00946db6d88442385296ece76a72d34f23 Mon Sep 17 00:00:00 2001 From: Sina Chavoshi Date: Fri, 9 May 2025 20:15:36 +0000 Subject: [PATCH 20/20] error messages, should not be capitalized or end with punctuation --- conformance/conformance.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conformance/conformance.go b/conformance/conformance.go index 9888efafb..1e847fd62 100644 --- a/conformance/conformance.go +++ b/conformance/conformance.go @@ -108,15 +108,15 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions { t.Log("Registering API types with scheme...") // Register core K8s types (like v1.Secret for certs) to scheme, needed by client to create/manage these resources. - require.NoError(t, clientsetscheme.AddToScheme(scheme), "Failed to add core Kubernetes types to scheme") + require.NoError(t, clientsetscheme.AddToScheme(scheme), "failed to add core Kubernetes types to scheme") // Add Gateway API types - require.NoError(t, gatewayv1.Install(scheme), "Failed to install gatewayv1 types into scheme") + require.NoError(t, gatewayv1.Install(scheme), "failed to install gatewayv1 types into scheme") // Add APIExtensions types (for CRDs) - require.NoError(t, apiextensionsv1.AddToScheme(scheme), "Failed to add apiextensionsv1 types to scheme") + require.NoError(t, apiextensionsv1.AddToScheme(scheme), "failed to add apiextensionsv1 types to scheme") // Register Inference Extension API types t.Logf("Attempting to install inferencev1alpha2 types into scheme from package: %s", inferencev1alpha2.GroupName) - require.NoError(t, inferencev1alpha2.Install(scheme), "Failed to install inferencev1alpha2 types into scheme.") + require.NoError(t, inferencev1alpha2.Install(scheme), "failed to install inferencev1alpha2 types into scheme") clientOptions := client.Options{Scheme: scheme} c, err := client.New(cfg, clientOptions) @@ -238,7 +238,7 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions) } // ensureGatewayAvailableAndReady polls for the specified Gateway to exist and become ready -// with an address and programmed conditions. +// with an address and programmed condition. func ensureGatewayAvailableAndReady(t *testing.T, k8sClient client.Client, opts confsuite.ConformanceOptions, gatewayNN types.NamespacedName) { t.Helper()