@@ -19,30 +19,38 @@ limitations under the License.
19
19
package conformance
20
20
21
21
import (
22
+ "context"
23
+ "errors"
22
24
"fmt"
23
25
"io/fs"
24
26
"os"
25
27
"testing"
28
+ "time"
26
29
27
30
"github.com/stretchr/testify/require"
28
31
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"
29
36
clientset "k8s.io/client-go/kubernetes"
37
+ clientsetscheme "k8s.io/client-go/kubernetes/scheme"
30
38
31
39
// Import runtime package for scheme creation
32
40
"k8s.io/apimachinery/pkg/runtime"
33
41
"k8s.io/apimachinery/pkg/util/sets"
34
42
"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"
36
44
"sigs.k8s.io/yaml"
37
45
38
46
// 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.
40
47
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" // Import core Gateway API types
41
48
confapis "sigs.k8s.io/gateway-api/conformance/apis/v1" // Report struct definition
42
49
confconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
43
50
confflags "sigs.k8s.io/gateway-api/conformance/utils/flags"
51
+ apikubernetes "sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
44
52
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"
46
54
47
55
// Import the test definitions package to access the ConformanceTests slice
48
56
"sigs.k8s.io/gateway-api-inference-extension/conformance/tests"
@@ -58,48 +66,59 @@ import (
58
66
inferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2"
59
67
)
60
68
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
+
61
75
// GatewayLayerProfileName defines the name for the conformance profile that tests
62
76
// the Gateway API layer aspects of the Inference Extension (e.g., InferencePool, InferenceModel CRDs).
63
77
// Future profiles will cover EPP and ModelServer layers.
64
78
const GatewayLayerProfileName confsuite.ConformanceProfileName = "Gateway"
65
79
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
+ )
67
85
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.
73
86
var GatewayLayerProfile = confsuite.ConformanceProfile {
74
87
Name : GatewayLayerProfileName ,
75
88
CoreFeatures : InferenceCoreFeatures ,
76
89
}
77
90
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
+
78
99
// DefaultOptions parses command line flags and sets up the suite options.
79
100
// Adapted from the core Gateway API conformance suite.
80
101
func DefaultOptions (t * testing.T ) confsuite.ConformanceOptions {
81
102
t .Helper ()
82
103
83
- cfg , err := config .GetConfig ()
104
+ cfg , err := k8sconfig .GetConfig ()
84
105
require .NoError (t , err , "error loading Kubernetes config" )
85
106
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" )
95
116
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" )
101
120
102
- // Create the Kubernetes clients
121
+ clientOptions := client. Options { Scheme : scheme }
103
122
c , err := client .New (cfg , clientOptions )
104
123
require .NoError (t , err , "error initializing Kubernetes client" )
105
124
cs , err := clientset .NewForConfig (cfg )
@@ -124,23 +143,26 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions {
124
143
inferenceExtensionVersion := "v0.3.0"
125
144
_ = inferenceExtensionVersion // Avoid unused variable error until implemented
126
145
127
- // Create ConformanceOptions
146
+ baseManifestsValue := "resources/manifests/manifests.yaml"
147
+
128
148
opts := confsuite.ConformanceOptions {
129
149
Client : c ,
150
+ ClientOptions : clientOptions ,
130
151
Clientset : cs ,
131
152
RestConfig : cfg ,
132
153
GatewayClassName : * confflags .GatewayClassName ,
154
+ BaseManifests : baseManifestsValue ,
133
155
Debug : * confflags .ShowDebug ,
134
156
CleanupBaseResources : * confflags .CleanupBaseResources ,
135
- SupportedFeatures : sets .New [features.FeatureName ](), // Initialize empty, will be populated below
157
+ SupportedFeatures : sets .New [features.FeatureName ](),
136
158
TimeoutConfig : confconfig .DefaultTimeoutConfig (),
137
159
SkipTests : skipTests ,
138
160
ExemptFeatures : exemptFeatures ,
139
161
RunTest : * confflags .RunTest ,
140
162
Mode : * confflags .Mode ,
141
163
Implementation : implementation ,
142
164
ConformanceProfiles : conformanceProfiles ,
143
- ManifestFS : []fs.FS {& Manifests }, // Assumes embed.go defines `Manifests`
165
+ ManifestFS : []fs.FS {& Manifests },
144
166
ReportOutputPath : * confflags .ReportOutput ,
145
167
SkipProvisionalTests : * confflags .SkipProvisionalTests ,
146
168
// TODO: Add the inference extension specific fields to ConformanceOptions struct if needed,
@@ -152,16 +174,20 @@ func DefaultOptions(t *testing.T) confsuite.ConformanceOptions {
152
174
// Populate SupportedFeatures based on the GatewayLayerProfile.
153
175
// Since all features are mandatory for this profile, add all defined core features.
154
176
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 ()... )
157
180
}
158
181
}
159
182
160
183
// 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 ()... )
163
187
}
164
188
189
+ logDebugf (t , opts .Debug , "Final opts.SupportedFeatures: %v" , opts .SupportedFeatures .UnsortedList ())
190
+
165
191
return opts
166
192
}
167
193
@@ -172,7 +198,9 @@ func RunConformance(t *testing.T) {
172
198
173
199
// RunConformanceWithOptions runs the Inference Extension conformance tests with specific options.
174
200
func RunConformanceWithOptions (t * testing.T , opts confsuite.ConformanceOptions ) {
201
+ t .Helper ()
175
202
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 )
176
204
177
205
// Register the GatewayLayerProfile with the suite runner.
178
206
// In the future, other profiles (EPP, ModelServer) will also be registered here,
@@ -183,13 +211,13 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions)
183
211
cSuite , err := confsuite .NewConformanceTestSuite (opts )
184
212
require .NoError (t , err , "error initializing conformance suite" )
185
213
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.
189
214
cSuite .Setup (t , tests .ConformanceTests )
190
215
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" )
193
221
err = cSuite .Run (t , tests .ConformanceTests )
194
222
require .NoError (t , err , "error running conformance tests" )
195
223
@@ -209,6 +237,67 @@ func RunConformanceWithOptions(t *testing.T, opts confsuite.ConformanceOptions)
209
237
}
210
238
}
211
239
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
+
212
301
// writeReport writes the generated conformance report to the specified output file or logs it.
213
302
// Adapted from the core Gateway API suite.
214
303
func writeReport (logf func (string , ... any ), report confapis.ConformanceReport , output string ) error {
0 commit comments