Skip to content

Commit ed2bd6b

Browse files
authored
Infer SupportedFeatures in Conformance Tests (GEP-2162) [#3759] (#3848)
* SupportedFeatures * Added inferredSupportedFeatures to determine and report if feature for tests were manually supplied or inferred from GatewayClass * Corrected suite unit tests after addition of a new field. * Added logic of determining if supportedFeatures are inferred or supplied and refactored some code around it. * moved SupportedFeatures init after scheme addition * Cleaned up and organized code to properly determine features we are testing and if they were inferred or not. * Cleanup v2 * Make inferring supportedFeatures to take precedence over all other feature flags. * Fix when no profile was setting inferred to false. * remove debug log * Switched args arrangement so ctx is the first argument passed to fetchSupportedFeatures function. * Updated context as it's unclear where it should be supplied from * removed SupportedFeatures struct and method for determining supported features. * Refactored logic of determining supported features and add flag for the report in cSuite. * Reversed flag parsing for some values. * Tweaked logging options. * Cleaned up last places for SupportedFeatures struct. * Wrote unit tests for supported features determination logic in conformance suite. * Formatting fixes. * Resolved comments. * Fixed formatting. * Updated conformance docs. * Removed doc update so it can be submitted as a seperate PR.
1 parent 8185c7d commit ed2bd6b

File tree

6 files changed

+217
-37
lines changed

6 files changed

+217
-37
lines changed

conformance/apis/v1/conformancereport.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ type ConformanceReport struct {
5050
// SucceededProvisionalTests is a list of the names of the provisional tests that
5151
// have been successfully run.
5252
SucceededProvisionalTests []string `json:"succeededProvisionalTests,omitempty"`
53+
54+
// InferredSupportedFeatures indicates whether the supported features were
55+
// automatically detected by the conformance suite.
56+
InferredSupportedFeatures bool `json:"inferredSupportedFeatures"`
5357
}
5458

5559
// Implementation provides metadata information on the downstream

conformance/conformance.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ import (
2929
"sigs.k8s.io/gateway-api/conformance/tests"
3030
conformanceconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
3131
"sigs.k8s.io/gateway-api/conformance/utils/flags"
32+
3233
"sigs.k8s.io/gateway-api/conformance/utils/suite"
3334

3435
"github.com/stretchr/testify/require"
3536
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
37+
3638
clientset "k8s.io/client-go/kubernetes"
39+
3740
"sigs.k8s.io/controller-runtime/pkg/client"
3841
"sigs.k8s.io/controller-runtime/pkg/client/config"
3942
"sigs.k8s.io/yaml"
@@ -63,6 +66,7 @@ func DefaultOptions(t *testing.T) suite.ConformanceOptions {
6366

6467
supportedFeatures := suite.ParseSupportedFeatures(*flags.SupportedFeatures)
6568
exemptFeatures := suite.ParseSupportedFeatures(*flags.ExemptFeatures)
69+
6670
skipTests := suite.ParseSkipTests(*flags.SkipTests)
6771
namespaceLabels := suite.ParseKeyValuePairs(*flags.NamespaceLabels)
6872
namespaceAnnotations := suite.ParseKeyValuePairs(*flags.NamespaceAnnotations)
@@ -144,6 +148,7 @@ func logOptions(t *testing.T, opts suite.ConformanceOptions) {
144148
t.Logf(" Enable All Features: %t", opts.EnableAllSupportedFeatures)
145149
t.Logf(" Supported Features: %v", opts.SupportedFeatures.UnsortedList())
146150
t.Logf(" ExemptFeatures: %v", opts.ExemptFeatures.UnsortedList())
151+
t.Logf(" ConformanceProfiles: %v", opts.ConformanceProfiles.UnsortedList())
147152
}
148153

149154
func writeReport(logf func(string, ...any), report confv1.ConformanceReport, output string) error {

conformance/utils/suite/conformance.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import (
2121
"strings"
2222
"testing"
2323

24-
"k8s.io/apimachinery/pkg/util/sets"
25-
2624
"sigs.k8s.io/gateway-api/conformance/utils/tlog"
2725
"sigs.k8s.io/gateway-api/pkg/features"
2826
)
@@ -81,11 +79,11 @@ func (test *ConformanceTest) Run(t *testing.T, suite *ConformanceTestSuite) {
8179

8280
// ParseSupportedFeatures parses flag arguments and converts the string to
8381
// sets.Set[features.FeatureName]
84-
func ParseSupportedFeatures(f string) sets.Set[features.FeatureName] {
82+
func ParseSupportedFeatures(f string) FeaturesSet {
8583
if f == "" {
8684
return nil
8785
}
88-
res := sets.Set[features.FeatureName]{}
86+
res := FeaturesSet{}
8987
for _, value := range strings.Split(f, ",") {
9088
res.Insert(features.FeatureName(value))
9189
}

conformance/utils/suite/reports.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"k8s.io/apimachinery/pkg/util/sets"
2323

2424
confv1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
25-
"sigs.k8s.io/gateway-api/pkg/features"
2625
)
2726

2827
// -----------------------------------------------------------------------------
@@ -107,7 +106,7 @@ func (p profileReportsMap) list() (profileReports []confv1.ProfileReport) {
107106
return
108107
}
109108

110-
func (p profileReportsMap) compileResults(supportedFeaturesMap map[ConformanceProfileName]sets.Set[features.FeatureName], unsupportedFeaturesMap map[ConformanceProfileName]sets.Set[features.FeatureName]) {
109+
func (p profileReportsMap) compileResults(supportedFeaturesMap map[ConformanceProfileName]FeaturesSet, unsupportedFeaturesMap map[ConformanceProfileName]FeaturesSet) {
111110
for key, report := range p {
112111
// report the overall result for core features
113112
switch {
@@ -162,7 +161,8 @@ func (p profileReportsMap) compileResults(supportedFeaturesMap map[ConformancePr
162161
// isTestExtended determines if a provided test is considered to be supported
163162
// at an extended level of support given the provided conformance profile.
164163
//
165-
// TODO: right now the tests themselves don't indicate the conformance
164+
// TODO(#3759) Update this method to be based on Features inferred.
165+
// Right now the tests themselves don't indicate the conformance
166166
// support level associated with them. The only way we have right now
167167
// in this prototype to know whether a test belongs to any particular
168168
// conformance level is to compare the features needed for the test to

conformance/utils/suite/suite.go

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ import (
3131
"github.com/stretchr/testify/require"
3232
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3333
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+
"k8s.io/apimachinery/pkg/types"
3435
"k8s.io/apimachinery/pkg/util/sets"
3536
clientset "k8s.io/client-go/kubernetes"
3637
"k8s.io/client-go/rest"
3738
"sigs.k8s.io/controller-runtime/pkg/client"
3839

40+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
3941
"sigs.k8s.io/gateway-api/apis/v1beta1"
4042
confv1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
4143
"sigs.k8s.io/gateway-api/conformance/utils/config"
@@ -69,7 +71,7 @@ type ConformanceTestSuite struct {
6971
BaseManifests string
7072
MeshManifests string
7173
Applier kubernetes.Applier
72-
SupportedFeatures sets.Set[features.FeatureName]
74+
SupportedFeatures FeaturesSet
7375
TimeoutConfig config.TimeoutConfig
7476
SkipTests sets.Set[string]
7577
SkipProvisionalTests bool
@@ -79,6 +81,11 @@ type ConformanceTestSuite struct {
7981
UsableNetworkAddresses []v1beta1.GatewaySpecAddress
8082
UnusableNetworkAddresses []v1beta1.GatewaySpecAddress
8183

84+
// If SupportedFeatures are automatically determined from GWC Status.
85+
// This will be required to report in future iterations as the passing
86+
// will be determined based on this.
87+
isInferredSupportedFeatures bool
88+
8289
// mode is the operating mode of the implementation.
8390
// The default value for it is "default".
8491
mode string
@@ -142,8 +149,8 @@ type ConformanceOptions struct {
142149
// CleanupBaseResources indicates whether or not the base test
143150
// resources such as Gateways should be cleaned up after the run.
144151
CleanupBaseResources bool
145-
SupportedFeatures sets.Set[features.FeatureName]
146-
ExemptFeatures sets.Set[features.FeatureName]
152+
SupportedFeatures FeaturesSet
153+
ExemptFeatures FeaturesSet
147154
EnableAllSupportedFeatures bool
148155
TimeoutConfig config.TimeoutConfig
149156
// SkipTests contains all the tests not to be run and can be used to opt out
@@ -172,6 +179,8 @@ type ConformanceOptions struct {
172179
ConformanceProfiles sets.Set[ConformanceProfileName]
173180
}
174181

182+
type FeaturesSet = sets.Set[features.FeatureName]
183+
175184
const (
176185
// undefinedKeyword is set in the ConformanceReport "GatewayAPIVersion" and
177186
// "GatewayAPIChannel" fields in case it's not possible to figure out the actual
@@ -181,16 +190,23 @@ const (
181190

182191
// NewConformanceTestSuite is a helper to use for creating a new ConformanceTestSuite.
183192
func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite, error) {
184-
// test suite callers are required to provide either:
185-
// - one conformance profile via the flag '-conformance-profiles'
186-
// - a list of supported features via the flag '-supported-features'
187-
// - an explicit test to run via the flag '-run-test'
188-
// - all features are being tested via the flag '-all-features'
189-
if options.SupportedFeatures.Len() == 0 &&
190-
options.ConformanceProfiles.Len() == 0 &&
191-
!options.EnableAllSupportedFeatures &&
192-
options.RunTest == "" {
193-
return nil, fmt.Errorf("no conformance profile, supported features, explicit tests were provided so no tests could be selected")
193+
supportedFeatures := options.SupportedFeatures.Difference(options.ExemptFeatures)
194+
isInferred := false
195+
switch {
196+
case options.EnableAllSupportedFeatures:
197+
supportedFeatures = features.SetsToNamesSet(features.AllFeatures)
198+
case shouldInferSupportedFeatures(&options):
199+
var err error
200+
supportedFeatures, err = fetchSupportedFeatures(options.Client, options.GatewayClassName)
201+
if err != nil {
202+
return nil, fmt.Errorf("Cannot infer supported features: %w", err)
203+
}
204+
isInferred = true
205+
}
206+
207+
// If features were not inferred from Status, it's a GWC issue.
208+
if isInferred && supportedFeatures.Len() == 0 {
209+
return nil, fmt.Errorf("no supported features were determined for test suite")
194210
}
195211

196212
config.SetupTimeoutConfig(&options.TimeoutConfig)
@@ -224,19 +240,6 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
224240
mode = options.Mode
225241
}
226242

227-
// test suite callers can potentially just run all tests by saying they
228-
// cover all features, if they don't they'll need to have provided a
229-
// conformance profile or at least some specific features they support.
230-
if options.EnableAllSupportedFeatures {
231-
options.SupportedFeatures = features.SetsToNamesSet(features.AllFeatures)
232-
} else if options.SupportedFeatures == nil {
233-
options.SupportedFeatures = sets.New[features.FeatureName]()
234-
}
235-
236-
for feature := range options.ExemptFeatures {
237-
options.SupportedFeatures.Delete(feature)
238-
}
239-
240243
suite := &ConformanceTestSuite{
241244
Client: options.Client,
242245
ClientOptions: options.ClientOptions,
@@ -254,7 +257,7 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
254257
NamespaceAnnotations: options.NamespaceAnnotations,
255258
AddressType: options.AddressType,
256259
},
257-
SupportedFeatures: options.SupportedFeatures,
260+
SupportedFeatures: supportedFeatures,
258261
TimeoutConfig: options.TimeoutConfig,
259262
SkipTests: sets.New(options.SkipTests...),
260263
RunTest: options.RunTest,
@@ -270,6 +273,7 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
270273
mode: mode,
271274
apiVersion: apiVersion,
272275
apiChannel: apiChannel,
276+
isInferredSupportedFeatures: isInferred,
273277
Hook: options.Hook,
274278
}
275279

@@ -288,12 +292,12 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
288292
for _, f := range conformanceProfile.ExtendedFeatures.UnsortedList() {
289293
if options.SupportedFeatures.Has(f) {
290294
if suite.extendedSupportedFeatures[conformanceProfileName] == nil {
291-
suite.extendedSupportedFeatures[conformanceProfileName] = sets.New[features.FeatureName]()
295+
suite.extendedSupportedFeatures[conformanceProfileName] = FeaturesSet{}
292296
}
293297
suite.extendedSupportedFeatures[conformanceProfileName].Insert(f)
294298
} else {
295299
if suite.extendedUnsupportedFeatures[conformanceProfileName] == nil {
296-
suite.extendedUnsupportedFeatures[conformanceProfileName] = sets.New[features.FeatureName]()
300+
suite.extendedUnsupportedFeatures[conformanceProfileName] = FeaturesSet{}
297301
}
298302
suite.extendedUnsupportedFeatures[conformanceProfileName].Insert(f)
299303
}
@@ -390,6 +394,10 @@ func (suite *ConformanceTestSuite) Setup(t *testing.T, tests []ConformanceTest)
390394
}
391395
}
392396

397+
func (suite *ConformanceTestSuite) IsInferredSupportedFeatures() bool {
398+
return suite.isInferredSupportedFeatures
399+
}
400+
393401
func (suite *ConformanceTestSuite) setClientsetForTest(test ConformanceTest) error {
394402
featureNames := []string{}
395403
for _, v := range test.Features {
@@ -544,6 +552,7 @@ func (suite *ConformanceTestSuite) Report() (*confv1.ConformanceReport, error) {
544552
GatewayAPIChannel: suite.apiChannel,
545553
ProfileReports: profileReports.list(),
546554
SucceededProvisionalTests: succeededProvisionalTests,
555+
InferredSupportedFeatures: suite.IsInferredSupportedFeatures(),
547556
}, nil
548557
}
549558

@@ -573,6 +582,39 @@ func ParseConformanceProfiles(p string) sets.Set[ConformanceProfileName] {
573582
return res
574583
}
575584

585+
func fetchSupportedFeatures(client client.Client, gatewayClassName string) (FeaturesSet, error) {
586+
if gatewayClassName == "" {
587+
return nil, fmt.Errorf("GatewayClass name must be provided to fetch supported features")
588+
}
589+
gwc := &gatewayv1.GatewayClass{}
590+
err := client.Get(context.TODO(), types.NamespacedName{Name: gatewayClassName}, gwc)
591+
if err != nil {
592+
return nil, fmt.Errorf("fetchSupportedFeatures(): %w", err)
593+
}
594+
595+
fs := FeaturesSet{}
596+
for _, feature := range gwc.Status.SupportedFeatures {
597+
fs.Insert(features.FeatureName(feature.Name))
598+
}
599+
fmt.Printf("Supported features for GatewayClass %s: %v\n", gatewayClassName, fs.UnsortedList())
600+
return fs, nil
601+
}
602+
603+
// shouldInferSupportedFeatures checks if any flags were supplied for manually
604+
// picking what to test. Inferred supported features are only used when no flags
605+
// are set.
606+
func shouldInferSupportedFeatures(opts *ConformanceOptions) bool {
607+
if opts == nil {
608+
return false
609+
}
610+
return !opts.EnableAllSupportedFeatures &&
611+
opts.SupportedFeatures.Len() == 0 &&
612+
opts.ExemptFeatures.Len() == 0 &&
613+
opts.ConformanceProfiles.Len() == 0 &&
614+
len(opts.SkipTests) == 0 &&
615+
opts.RunTest == ""
616+
}
617+
576618
// getAPIVersionAndChannel iterates over all the crds installed in the cluster and check the version and channel annotations.
577619
// In case the annotations are not found or there are crds with different versions or channels, an error is returned.
578620
func getAPIVersionAndChannel(crds []apiextensionsv1.CustomResourceDefinition) (version string, channel string, err error) {

0 commit comments

Comments
 (0)