Skip to content

Commit 09d3d5c

Browse files
authored
Merge pull request #2800 from mlavacca/gateway-api-channel
feat: CRD version and channel added to conformance reports
2 parents 9fb8dc6 + 6322aaa commit 09d3d5c

File tree

7 files changed

+336
-50
lines changed

7 files changed

+336
-50
lines changed

conformance/apis/v1alpha1/conformancereport.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ type ConformanceReport struct {
3737
// Mode is the operating mode the implementation used to run conformance tests.
3838
Mode string `json:"mode"`
3939

40+
// GatewayAPIChannel indicates which release channel of Gateway API this
41+
// test report was made for.
42+
GatewayAPIChannel string `json:"gatewayAPIChannel"`
43+
4044
// ProfileReports is a list of the individual reports for each conformance
4145
// profile that was enabled for a test run.
4246
ProfileReports []ProfileReport `json:"profiles"`

conformance/experimental_conformance_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"os"
2121
"testing"
2222

23+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2324
"k8s.io/apimachinery/pkg/util/sets"
2425
"k8s.io/client-go/kubernetes"
2526
"k8s.io/client-go/rest"
@@ -46,6 +47,7 @@ var (
4647
namespaceAnnotations map[string]string
4748
implementation *confv1a1.Implementation
4849
mode string
50+
allowCRDsMismatch bool
4951
conformanceProfiles sets.Set[suite.ConformanceProfileName]
5052
skipTests []string
5153
)
@@ -68,6 +70,7 @@ func TestExperimentalConformance(t *testing.T) {
6870
v1alpha2.AddToScheme(mgrClient.Scheme())
6971
v1beta1.AddToScheme(mgrClient.Scheme())
7072
gatewayv1.AddToScheme(mgrClient.Scheme())
73+
apiextensionsv1.AddToScheme(mgrClient.Scheme())
7174

7275
// standard conformance flags
7376
supportedFeatures = suite.ParseSupportedFeatures(*flags.SupportedFeatures)
@@ -79,6 +82,7 @@ func TestExperimentalConformance(t *testing.T) {
7982
// experimental conformance flags
8083
conformanceProfiles = suite.ParseConformanceProfiles(*flags.ConformanceProfiles)
8184
mode = *flags.Mode
85+
allowCRDsMismatch = *flags.AllowCRDsMismatch
8286

8387
if conformanceProfiles.Len() > 0 {
8488
// if some conformance profiles have been set, run the experimental conformance suite...
@@ -122,6 +126,7 @@ func testExperimentalConformance(t *testing.T) {
122126
SkipTests: skipTests,
123127
},
124128
Mode: mode,
129+
AllowCRDsMismatch: allowCRDsMismatch,
125130
Implementation: *implementation,
126131
ConformanceProfiles: conformanceProfiles,
127132
})

conformance/utils/flags/experimental_flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var (
3535
ImplementationVersion = flag.String("version", "", "Implementation's version to issue conformance to")
3636
ImplementationContact = flag.String("contact", "", "Comma-separated list of contact information for the maintainers")
3737
Mode = flag.String("mode", DefaultMode, "The operating mode of the implementation.")
38+
AllowCRDsMismatch = flag.Bool("allow-crds-mismatch", false, "Flag to allow the suite not to fail in case there is a mismatch between CRDs versions and channels.")
3839
ConformanceProfiles = flag.String("conformance-profiles", "", "Comma-separated list of the conformance profiles to run")
3940
ReportOutput = flag.String("report-output", "", "The file where to write the conformance report")
4041
)

conformance/utils/suite/experimental_suite.go

Lines changed: 108 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package suite
1818

1919
import (
20+
"context"
2021
"errors"
2122
"fmt"
2223
"sort"
@@ -25,6 +26,7 @@ import (
2526
"testing"
2627
"time"
2728

29+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2830
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2931
"k8s.io/apimachinery/pkg/util/sets"
3032

@@ -34,6 +36,7 @@ import (
3436
"sigs.k8s.io/gateway-api/conformance/utils/flags"
3537
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
3638
"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
39+
"sigs.k8s.io/gateway-api/pkg/consts"
3740
)
3841

3942
// -----------------------------------------------------------------------------
@@ -56,6 +59,16 @@ type ExperimentalConformanceTestSuite struct {
5659
// organization, project, etc.
5760
implementation confv1a1.Implementation
5861

62+
// apiVersion is the version of the Gateway API installed in the cluster
63+
// and is extracted by the annotation gateway.networking.k8s.io/bundle-version
64+
// in the Gateway API CRDs.
65+
apiVersion string
66+
67+
// apiChannel is the channel of the Gateway API installed in the cluster
68+
// and is extracted by the annotation gateway.networking.k8s.io/channel
69+
// in the Gateway API CRDs.
70+
apiChannel string
71+
5972
// conformanceProfiles is a compiled list of profiles to check
6073
// conformance against.
6174
conformanceProfiles sets.Set[ConformanceProfileName]
@@ -84,62 +97,86 @@ type ExperimentalConformanceOptions struct {
8497
Options
8598

8699
Mode string
100+
AllowCRDsMismatch bool
87101
Implementation confv1a1.Implementation
88102
ConformanceProfiles sets.Set[ConformanceProfileName]
89103
}
90104

105+
const (
106+
undefinedKeyword = "UNDEFINED"
107+
)
108+
91109
// NewExperimentalConformanceTestSuite is a helper to use for creating a new ExperimentalConformanceTestSuite.
92-
func NewExperimentalConformanceTestSuite(s ExperimentalConformanceOptions) (*ExperimentalConformanceTestSuite, error) {
93-
config.SetupTimeoutConfig(&s.TimeoutConfig)
110+
func NewExperimentalConformanceTestSuite(options ExperimentalConformanceOptions) (*ExperimentalConformanceTestSuite, error) {
111+
config.SetupTimeoutConfig(&options.TimeoutConfig)
94112

95-
roundTripper := s.RoundTripper
113+
roundTripper := options.RoundTripper
96114
if roundTripper == nil {
97-
roundTripper = &roundtripper.DefaultRoundTripper{Debug: s.Debug, TimeoutConfig: s.TimeoutConfig}
115+
roundTripper = &roundtripper.DefaultRoundTripper{Debug: options.Debug, TimeoutConfig: options.TimeoutConfig}
116+
}
117+
118+
installedCRDs := &apiextensionsv1.CustomResourceDefinitionList{}
119+
err := options.Client.List(context.TODO(), installedCRDs)
120+
if err != nil {
121+
return nil, err
122+
}
123+
apiVersion, apiChannel, err := getAPIVersionAndChannel(installedCRDs.Items)
124+
if err != nil {
125+
// in case an error is returned and the AllowCRDsMismatch flag is false, the suite fails.
126+
// This is the default behavior but can be customized in case one wants to experiment
127+
// with mixed versions/channels of the API.
128+
if !options.AllowCRDsMismatch {
129+
return nil, err
130+
}
131+
apiVersion = undefinedKeyword
132+
apiChannel = undefinedKeyword
98133
}
99134

100135
mode := flags.DefaultMode
101-
if s.Mode != "" {
102-
mode = s.Mode
136+
if options.Mode != "" {
137+
mode = options.Mode
103138
}
104139

105140
suite := &ExperimentalConformanceTestSuite{
106141
results: make(map[string]testResult),
107142
extendedUnsupportedFeatures: make(map[ConformanceProfileName]sets.Set[SupportedFeature]),
108143
extendedSupportedFeatures: make(map[ConformanceProfileName]sets.Set[SupportedFeature]),
109-
conformanceProfiles: s.ConformanceProfiles,
110-
implementation: s.Implementation,
144+
conformanceProfiles: options.ConformanceProfiles,
145+
implementation: options.Implementation,
111146
mode: mode,
147+
apiVersion: apiVersion,
148+
apiChannel: apiChannel,
112149
}
113150

114151
// test suite callers are required to provide a conformance profile OR at
115152
// minimum a list of features which they support.
116-
if s.SupportedFeatures == nil && s.ConformanceProfiles.Len() == 0 && !s.EnableAllSupportedFeatures {
153+
if options.SupportedFeatures == nil && options.ConformanceProfiles.Len() == 0 && !options.EnableAllSupportedFeatures {
117154
return nil, fmt.Errorf("no conformance profile was selected for test run, and no supported features were provided so no tests could be selected")
118155
}
119156

120157
// test suite callers can potentially just run all tests by saying they
121158
// cover all features, if they don't they'll need to have provided a
122159
// conformance profile or at least some specific features they support.
123-
if s.EnableAllSupportedFeatures {
124-
s.SupportedFeatures = AllFeatures
125-
} else if s.SupportedFeatures == nil {
126-
s.SupportedFeatures = sets.New[SupportedFeature]()
160+
if options.EnableAllSupportedFeatures {
161+
options.SupportedFeatures = AllFeatures
162+
} else if options.SupportedFeatures == nil {
163+
options.SupportedFeatures = sets.New[SupportedFeature]()
127164
}
128165

129-
for _, conformanceProfileName := range s.ConformanceProfiles.UnsortedList() {
166+
for _, conformanceProfileName := range options.ConformanceProfiles.UnsortedList() {
130167
conformanceProfile, err := getConformanceProfileForName(conformanceProfileName)
131168
if err != nil {
132169
return nil, fmt.Errorf("failed to retrieve conformance profile: %w", err)
133170
}
134171
// the use of a conformance profile implicitly enables any features of
135172
// that profile which are supported at a Core level of support.
136173
for _, f := range conformanceProfile.CoreFeatures.UnsortedList() {
137-
if !s.SupportedFeatures.Has(f) {
138-
s.SupportedFeatures.Insert(f)
174+
if !options.SupportedFeatures.Has(f) {
175+
options.SupportedFeatures.Insert(f)
139176
}
140177
}
141178
for _, f := range conformanceProfile.ExtendedFeatures.UnsortedList() {
142-
if s.SupportedFeatures.Has(f) {
179+
if options.SupportedFeatures.Has(f) {
143180
if suite.extendedSupportedFeatures[conformanceProfileName] == nil {
144181
suite.extendedSupportedFeatures[conformanceProfileName] = sets.New[SupportedFeature]()
145182
}
@@ -151,40 +188,40 @@ func NewExperimentalConformanceTestSuite(s ExperimentalConformanceOptions) (*Exp
151188
suite.extendedUnsupportedFeatures[conformanceProfileName].Insert(f)
152189
}
153190
// Add Exempt Features into unsupported features list
154-
if s.ExemptFeatures.Has(f) {
191+
if options.ExemptFeatures.Has(f) {
155192
suite.extendedUnsupportedFeatures[conformanceProfileName].Insert(f)
156193
}
157194
}
158195
}
159196

160-
for feature := range s.ExemptFeatures {
161-
s.SupportedFeatures.Delete(feature)
197+
for feature := range options.ExemptFeatures {
198+
options.SupportedFeatures.Delete(feature)
162199
}
163200

164-
if s.FS == nil {
165-
s.FS = &conformance.Manifests
201+
if options.FS == nil {
202+
options.FS = &conformance.Manifests
166203
}
167204

168205
suite.ConformanceTestSuite = ConformanceTestSuite{
169-
Client: s.Client,
170-
Clientset: s.Clientset,
171-
RestConfig: s.RestConfig,
206+
Client: options.Client,
207+
Clientset: options.Clientset,
208+
RestConfig: options.RestConfig,
172209
RoundTripper: roundTripper,
173-
GatewayClassName: s.GatewayClassName,
174-
Debug: s.Debug,
175-
Cleanup: s.CleanupBaseResources,
176-
BaseManifests: s.BaseManifests,
177-
MeshManifests: s.MeshManifests,
210+
GatewayClassName: options.GatewayClassName,
211+
Debug: options.Debug,
212+
Cleanup: options.CleanupBaseResources,
213+
BaseManifests: options.BaseManifests,
214+
MeshManifests: options.MeshManifests,
178215
Applier: kubernetes.Applier{
179-
NamespaceLabels: s.NamespaceLabels,
180-
NamespaceAnnotations: s.NamespaceAnnotations,
216+
NamespaceLabels: options.NamespaceLabels,
217+
NamespaceAnnotations: options.NamespaceAnnotations,
181218
},
182-
SupportedFeatures: s.SupportedFeatures,
183-
TimeoutConfig: s.TimeoutConfig,
184-
SkipTests: sets.New(s.SkipTests...),
185-
FS: *s.FS,
186-
UsableNetworkAddresses: s.UsableNetworkAddresses,
187-
UnusableNetworkAddresses: s.UnusableNetworkAddresses,
219+
SupportedFeatures: options.SupportedFeatures,
220+
TimeoutConfig: options.TimeoutConfig,
221+
SkipTests: sets.New(options.SkipTests...),
222+
FS: *options.FS,
223+
UsableNetworkAddresses: options.UsableNetworkAddresses,
224+
UnusableNetworkAddresses: options.UnusableNetworkAddresses,
188225
}
189226

190227
// apply defaults
@@ -295,7 +332,8 @@ func (suite *ExperimentalConformanceTestSuite) Report() (*confv1a1.ConformanceRe
295332
Date: time.Now().Format(time.RFC3339),
296333
Mode: suite.mode,
297334
Implementation: suite.implementation,
298-
GatewayAPIVersion: "TODO",
335+
GatewayAPIVersion: suite.apiVersion,
336+
GatewayAPIChannel: suite.apiChannel,
299337
ProfileReports: profileReports.list(),
300338
}, nil
301339
}
@@ -344,3 +382,34 @@ func ParseConformanceProfiles(p string) sets.Set[ConformanceProfileName] {
344382
}
345383
return res
346384
}
385+
386+
// getAPIVersionAndChannel iterates over all the crds installed in the cluster and check the version and channel annotations.
387+
// In case the annotations are not found or there are crds with different versions or channels, an error is returned.
388+
func getAPIVersionAndChannel(crds []apiextensionsv1.CustomResourceDefinition) (version string, channel string, err error) {
389+
for _, crd := range crds {
390+
v, okv := crd.Annotations[consts.BundleVersionAnnotation]
391+
c, okc := crd.Annotations[consts.ChannelAnnotation]
392+
if !okv && !okc {
393+
continue
394+
}
395+
if !okv || !okc {
396+
return "", "", errors.New("detected CRDs with partial version and channel annotations")
397+
}
398+
if version != "" && v != version {
399+
return "", "", errors.New("multiple gateway API CRDs versions detected")
400+
}
401+
if channel != "" && c != channel {
402+
return "", "", errors.New("multiple gateway API CRDs channels detected")
403+
}
404+
version = v
405+
channel = c
406+
}
407+
if version == "" || channel == "" {
408+
return "", "", errors.New("no Gateway API CRDs with the proper annotations found in the cluster")
409+
}
410+
if version != consts.BundleVersion {
411+
return "", "", errors.New("the installed CRDs version is different from the suite version")
412+
}
413+
414+
return version, channel, nil
415+
}

0 commit comments

Comments
 (0)