@@ -19,30 +19,38 @@ limitations under the License.
1919package conformance
2020
2121import (
22+ "context"
23+ "errors"
2224 "fmt"
2325 "io/fs"
2426 "os"
2527 "testing"
28+ "time"
2629
2730 "github.com/stretchr/testify/require"
2831 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
32+ apierrors "k8s.io/apimachinery/pkg/api/errors"
33+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+ "k8s.io/apimachinery/pkg/types"
35+ "k8s.io/apimachinery/pkg/util/wait"
2936 clientset "k8s.io/client-go/kubernetes"
37+ clientsetscheme "k8s.io/client-go/kubernetes/scheme"
3038
3139 // Import runtime package for scheme creation
3240 "k8s.io/apimachinery/pkg/runtime"
3341 "k8s.io/apimachinery/pkg/util/sets"
3442 "sigs.k8s.io/controller-runtime/pkg/client"
35- "sigs.k8s.io/controller-runtime/pkg/client/config"
43+ k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config"
3644 "sigs.k8s.io/yaml"
3745
3846 // Import necessary types and utilities from the core Gateway API conformance suite.
39- // Assumes sigs.k8s.io/gateway-api is a dependency in the go.mod.
4047 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" // Import core Gateway API types
4148 confapis "sigs.k8s.io/gateway-api/conformance/apis/v1" // Report struct definition
4249 confconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
4350 confflags "sigs.k8s.io/gateway-api/conformance/utils/flags"
51+ apikubernetes "sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
4452 confsuite "sigs.k8s.io/gateway-api/conformance/utils/suite"
45- "sigs.k8s.io/gateway-api/pkg/features" // Using core features definitions if applicable
53+ "sigs.k8s.io/gateway-api/pkg/features"
4654
4755 // Import the test definitions package to access the ConformanceTests slice
4856 "sigs.k8s.io/gateway-api-inference-extension/conformance/tests"
@@ -58,48 +66,59 @@ import (
5866 inferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2"
5967)
6068
69+ // Constants for the shared Gateway
70+ const (
71+ SharedGatewayName = "conformance-gateway" // Name of the Gateway in manifests.yaml
72+ SharedGatewayNamespace = "gateway-conformance-infra" // Namespace of the Gateway
73+ )
74+
6175// GatewayLayerProfileName defines the name for the conformance profile that tests
6276// the Gateway API layer aspects of the Inference Extension (e.g., InferencePool, InferenceModel CRDs).
6377// Future profiles will cover EPP and ModelServer layers.
6478const GatewayLayerProfileName confsuite.ConformanceProfileName = "Gateway"
6579
66- var InferenceCoreFeatures = sets .New [features.FeatureName ]() // Placeholder - Populate with actual features specific to this profile or manage features per profile
80+ // InferenceCoreFeatures defines the core features that implementations
81+ // of the "Gateway" profile for the Inference Extension MUST support.
82+ var InferenceCoreFeatures = sets .New (
83+ features .SupportGateway , // This is needed to ensure manifest gets applied during setup.
84+ )
6785
68- // GatewayLayerProfile defines the conformance profile for the Gateway API layer
69- // of the Inference Extension.
70- // In future iterations, we will add constants and ConformanceProfile structs for
71- // EPPProfileName ("EPP") and ModelServerProfileName ("ModelServer")
72- // to cover their respective conformance layers.
7386var GatewayLayerProfile = confsuite.ConformanceProfile {
7487 Name : GatewayLayerProfileName ,
7588 CoreFeatures : InferenceCoreFeatures ,
7689}
7790
91+ // logDebugf conditionally logs a debug message if debug mode is enabled.
92+ func logDebugf (t * testing.T , debug bool , format string , args ... any ) {
93+ if debug {
94+ t .Helper ()
95+ t .Logf (format , args ... )
96+ }
97+ }
98+
7899// DefaultOptions parses command line flags and sets up the suite options.
79100// Adapted from the core Gateway API conformance suite.
80101func DefaultOptions (t * testing.T ) confsuite.ConformanceOptions {
81102 t .Helper ()
82103
83- cfg , err := config .GetConfig ()
104+ cfg , err := k8sconfig .GetConfig ()
84105 require .NoError (t , err , "error loading Kubernetes config" )
85106
86- // Initialize client options. The scheme must include Gateway API types
87- // and the Inference Extension types.
88- clientOptions := client. Options {}
89- scheme := clientOptions . Scheme
90- if scheme == nil {
91- // If default options don't provide a scheme, create one using runtime.NewScheme().
92- scheme = runtime . NewScheme ( )
93- clientOptions . Scheme = scheme
94- }
107+ scheme := runtime . NewScheme ()
108+
109+ t . Log ( "Registering API types with scheme..." )
110+ // Register core K8s types (like v1.Secret for certs) to scheme, needed by client to create/manage these resources.
111+ require . NoError ( t , clientsetscheme . AddToScheme ( scheme ), "failed to add core Kubernetes types to scheme" )
112+ // Add Gateway API types
113+ require . NoError ( t , gatewayv1 . Install ( scheme ), "failed to install gatewayv1 types into scheme" )
114+ // Add APIExtensions types (for CRDs)
115+ require . NoError ( t , apiextensionsv1 . AddToScheme ( scheme ), "failed to add apiextensionsv1 types to scheme" )
95116
96- // Register necessary API Types
97- require .NoError (t , gatewayv1 .Install (scheme )) // Add core Gateway API types
98- // Add the Inference Extension API types to the scheme using the correct import alias
99- require .NoError (t , inferencev1alpha2 .Install (scheme ))
100- require .NoError (t , apiextensionsv1 .AddToScheme (scheme )) // Needed for CRD checks
117+ // Register Inference Extension API types
118+ t .Logf ("Attempting to install inferencev1alpha2 types into scheme from package: %s" , inferencev1alpha2 .GroupName )
119+ require .NoError (t , inferencev1alpha2 .Install (scheme ), "failed to install inferencev1alpha2 types into scheme" )
101120
102- // Create the Kubernetes clients
121+ clientOptions := client. Options { Scheme : scheme }
103122 c , err := client .New (cfg , clientOptions )
104123 require .NoError (t , err , "error initializing Kubernetes client" )
105124 cs , err := clientset .NewForConfig (cfg )
@@ -124,23 +143,26 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions {
124143 inferenceExtensionVersion := "v0.3.0"
125144 _ = inferenceExtensionVersion // Avoid unused variable error until implemented
126145
127- // Create ConformanceOptions
146+ baseManifestsValue := "resources/manifests/manifests.yaml"
147+
128148 opts := confsuite.ConformanceOptions {
129149 Client : c ,
150+ ClientOptions : clientOptions ,
130151 Clientset : cs ,
131152 RestConfig : cfg ,
132153 GatewayClassName : * confflags .GatewayClassName ,
154+ BaseManifests : baseManifestsValue ,
133155 Debug : * confflags .ShowDebug ,
134156 CleanupBaseResources : * confflags .CleanupBaseResources ,
135- SupportedFeatures : sets .New [features.FeatureName ](), // Initialize empty, will be populated below
157+ SupportedFeatures : sets .New [features.FeatureName ](),
136158 TimeoutConfig : confconfig .DefaultTimeoutConfig (),
137159 SkipTests : skipTests ,
138160 ExemptFeatures : exemptFeatures ,
139161 RunTest : * confflags .RunTest ,
140162 Mode : * confflags .Mode ,
141163 Implementation : implementation ,
142164 ConformanceProfiles : conformanceProfiles ,
143- ManifestFS : []fs.FS {& Manifests }, // Assumes embed.go defines `Manifests`
165+ ManifestFS : []fs.FS {& Manifests },
144166 ReportOutputPath : * confflags .ReportOutput ,
145167 SkipProvisionalTests : * confflags .SkipProvisionalTests ,
146168 // TODO: Add the inference extension specific fields to ConformanceOptions struct if needed,
@@ -152,16 +174,20 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions {
152174 // Populate SupportedFeatures based on the GatewayLayerProfile.
153175 // Since all features are mandatory for this profile, add all defined core features.
154176 if opts .ConformanceProfiles .Has (GatewayLayerProfileName ) {
155- for feature := range GatewayLayerProfile .CoreFeatures {
156- opts .SupportedFeatures .Insert (feature )
177+ logDebugf (t , opts .Debug , "Populating SupportedFeatures with GatewayLayerProfile.CoreFeatures: %v" , GatewayLayerProfile .CoreFeatures .UnsortedList ())
178+ if GatewayLayerProfile .CoreFeatures .Len () > 0 {
179+ opts .SupportedFeatures = opts .SupportedFeatures .Insert (GatewayLayerProfile .CoreFeatures .UnsortedList ()... )
157180 }
158181 }
159182
160183 // Remove any features explicitly exempted via flags.
161- for feature := range opts .ExemptFeatures {
162- opts .SupportedFeatures .Delete (feature )
184+ if opts .ExemptFeatures .Len () > 0 {
185+ logDebugf (t , opts .Debug , "Removing ExemptFeatures from SupportedFeatures: %v" , opts .ExemptFeatures .UnsortedList ())
186+ opts .SupportedFeatures = opts .SupportedFeatures .Delete (opts .ExemptFeatures .UnsortedList ()... )
163187 }
164188
189+ logDebugf (t , opts .Debug , "Final opts.SupportedFeatures: %v" , opts .SupportedFeatures .UnsortedList ())
190+
165191 return opts
166192}
167193
@@ -172,7 +198,9 @@ func RunConformance(t *testing.T) {
172198
173199// RunConformanceWithOptions runs the Inference Extension conformance tests with specific options.
174200func RunConformanceWithOptions (t * testing.T , opts confsuite.ConformanceOptions ) {
201+ t .Helper ()
175202 t .Logf ("Running Inference Extension conformance tests with GatewayClass %s" , opts .GatewayClassName )
203+ logDebugf (t , opts .Debug , "RunConformanceWithOptions: BaseManifests path being used by opts: %q" , opts .BaseManifests )
176204
177205 // Register the GatewayLayerProfile with the suite runner.
178206 // In the future, other profiles (EPP, ModelServer) will also be registered here,
@@ -183,13 +211,13 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions)
183211 cSuite , err := confsuite .NewConformanceTestSuite (opts )
184212 require .NoError (t , err , "error initializing conformance suite" )
185213
186- t .Log ("Setting up Inference Extension conformance tests" )
187- // Setup requires the list of tests, which is populated by the init() functions
188- // triggered by the blank imports at the top of this file.
189214 cSuite .Setup (t , tests .ConformanceTests )
190215
191- t .Log ("Running Inference Extension conformance tests" )
192- // Run the tests.
216+ sharedGwNN := types.NamespacedName {Name : SharedGatewayName , Namespace : SharedGatewayNamespace }
217+
218+ // Validate Gateway setup.
219+ ensureGatewayAvailableAndReady (t , cSuite .Client , opts , sharedGwNN )
220+ t .Log ("Running Inference Extension conformance tests against all registered tests" )
193221 err = cSuite .Run (t , tests .ConformanceTests )
194222 require .NoError (t , err , "error running conformance tests" )
195223
@@ -209,6 +237,67 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions)
209237 }
210238}
211239
240+ // ensureGatewayAvailableAndReady polls for the specified Gateway to exist and become ready
241+ // with an address and programmed condition.
242+ func ensureGatewayAvailableAndReady (t * testing.T , k8sClient client.Client , opts confsuite.ConformanceOptions , gatewayNN types.NamespacedName ) {
243+ t .Helper ()
244+
245+ t .Logf ("Attempting to fetch Gateway %s/%s." , gatewayNN .Namespace , gatewayNN .Name )
246+ gw := & gatewayv1.Gateway {} // This gw instance will be populated by the poll function
247+
248+ // Define polling interval
249+ // TODO: Make this configurable using a local TimeoutConfig (from ConformanceOptions perhaps)
250+ pollingInterval := 5 * time .Second
251+ // Use the GatewayMustHaveAddress timeout from the suite's TimeoutConfig for the Gateway object to appear
252+ waitForGatewayCreationTimeout := opts .TimeoutConfig .GatewayMustHaveAddress
253+
254+ logDebugf (t , opts .Debug , "Waiting up to %v for Gateway object %s/%s to appear after manifest application..." , waitForGatewayCreationTimeout , gatewayNN .Namespace , gatewayNN .Name )
255+
256+ ctx := context .TODO ()
257+ pollErr := wait .PollUntilContextTimeout (ctx , pollingInterval , waitForGatewayCreationTimeout , true , func (pollCtx context.Context ) (bool , error ) {
258+ fetchErr := k8sClient .Get (pollCtx , gatewayNN , gw )
259+ if fetchErr == nil {
260+ t .Logf ("Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s" ,
261+ gw .Namespace , gw .Name , gw .Spec .GatewayClassName )
262+ return true , nil
263+ }
264+ if apierrors .IsNotFound (fetchErr ) {
265+ logDebugf (t , opts .Debug , "Gateway %s/%s not found, still waiting..." , gatewayNN .Namespace , gatewayNN .Name )
266+ return false , nil // Not found, continue polling
267+ }
268+ // For any other error, stop polling and return this error
269+ t .Logf ("Error fetching Gateway %s/%s: %v. Halting polling for this attempt." , gatewayNN .Namespace , gatewayNN .Name , fetchErr )
270+ return false , fetchErr
271+ })
272+
273+ // Check if polling timed out or an error occurred during polling
274+ if pollErr != nil {
275+ var failureMessage string
276+ if errors .Is (pollErr , context .DeadlineExceeded ) {
277+ failureMessage = fmt .Sprintf ("Timed out after %v waiting for Gateway object %s/%s to appear in the API server." ,
278+ waitForGatewayCreationTimeout , gatewayNN .Namespace , gatewayNN .Name )
279+ } else {
280+ failureMessage = fmt .Sprintf ("Error while waiting for Gateway object %s/%s to appear: %v." ,
281+ gatewayNN .Namespace , gatewayNN .Name , pollErr )
282+ }
283+ finalMessage := failureMessage + " The Gateway object should have been created by the base manifest application."
284+ require .FailNow (t , finalMessage ) // Use FailNow to stop if the Gateway isn't found.
285+ }
286+
287+ logDebugf (t , opts .Debug , "Waiting for shared Gateway %s/%s to be ready" , gatewayNN .Namespace , gatewayNN .Name )
288+ apikubernetes .GatewayMustHaveCondition (t , k8sClient , opts .TimeoutConfig , gatewayNN , metav1.Condition {
289+ Type : string (gatewayv1 .GatewayConditionAccepted ),
290+ Status : metav1 .ConditionTrue ,
291+ })
292+ apikubernetes .GatewayMustHaveCondition (t , k8sClient , opts .TimeoutConfig , gatewayNN , metav1.Condition {
293+ Type : string (gatewayv1 .GatewayConditionProgrammed ),
294+ Status : metav1 .ConditionTrue ,
295+ })
296+ _ , err := apikubernetes .WaitForGatewayAddress (t , k8sClient , opts .TimeoutConfig , apikubernetes .NewGatewayRef (gatewayNN ))
297+ require .NoErrorf (t , err , "shared gateway %s/%s did not get an address" , gatewayNN .Namespace , gatewayNN .Name )
298+ t .Logf ("Shared Gateway %s/%s is ready." , gatewayNN .Namespace , gatewayNN .Name )
299+ }
300+
212301// writeReport writes the generated conformance report to the specified output file or logs it.
213302// Adapted from the core Gateway API suite.
214303func writeReport (logf func (string , ... any ), report confapis.ConformanceReport , output string ) error {
0 commit comments