sigs.k8s.io/gateway-api@v1.0.0/conformance/utils/suite/suite.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package suite 18 19 import ( 20 "embed" 21 "strings" 22 "testing" 23 24 "k8s.io/apimachinery/pkg/util/sets" 25 clientset "k8s.io/client-go/kubernetes" 26 "k8s.io/client-go/rest" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 29 "sigs.k8s.io/gateway-api/apis/v1beta1" 30 "sigs.k8s.io/gateway-api/conformance" 31 "sigs.k8s.io/gateway-api/conformance/utils/config" 32 "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" 33 "sigs.k8s.io/gateway-api/conformance/utils/roundtripper" 34 ) 35 36 // ConformanceTestSuite defines the test suite used to run Gateway API 37 // conformance tests. 38 type ConformanceTestSuite struct { 39 Client client.Client 40 Clientset clientset.Interface 41 RESTClient *rest.RESTClient 42 RestConfig *rest.Config 43 RoundTripper roundtripper.RoundTripper 44 GatewayClassName string 45 ControllerName string 46 Debug bool 47 Cleanup bool 48 BaseManifests string 49 MeshManifests string 50 Applier kubernetes.Applier 51 SupportedFeatures sets.Set[SupportedFeature] 52 TimeoutConfig config.TimeoutConfig 53 SkipTests sets.Set[string] 54 RunTest string 55 FS embed.FS 56 UsableNetworkAddresses []v1beta1.GatewayAddress 57 UnusableNetworkAddresses []v1beta1.GatewayAddress 58 } 59 60 // Options can be used to initialize a ConformanceTestSuite. 61 type Options struct { 62 Client client.Client 63 Clientset clientset.Interface 64 RestConfig *rest.Config 65 GatewayClassName string 66 Debug bool 67 RoundTripper roundtripper.RoundTripper 68 BaseManifests string 69 MeshManifests string 70 NamespaceLabels map[string]string 71 NamespaceAnnotations map[string]string 72 73 // CleanupBaseResources indicates whether or not the base test 74 // resources such as Gateways should be cleaned up after the run. 75 CleanupBaseResources bool 76 SupportedFeatures sets.Set[SupportedFeature] 77 ExemptFeatures sets.Set[SupportedFeature] 78 EnableAllSupportedFeatures bool 79 TimeoutConfig config.TimeoutConfig 80 // SkipTests contains all the tests not to be run and can be used to opt out 81 // of specific tests 82 SkipTests []string 83 // RunTest is a single test to run, mostly for development/debugging convenience. 84 RunTest string 85 86 FS *embed.FS 87 88 // UsableNetworkAddresses is an optional pool of usable addresses for 89 // Gateways for tests which need to test manual address assignments. 90 UsableNetworkAddresses []v1beta1.GatewayAddress 91 92 // UnusableNetworkAddresses is an optional pool of unusable addresses for 93 // Gateways for tests which need to test failures with manual Gateway 94 // address assignment. 95 UnusableNetworkAddresses []v1beta1.GatewayAddress 96 } 97 98 // New returns a new ConformanceTestSuite. 99 func New(s Options) *ConformanceTestSuite { 100 config.SetupTimeoutConfig(&s.TimeoutConfig) 101 102 roundTripper := s.RoundTripper 103 if roundTripper == nil { 104 roundTripper = &roundtripper.DefaultRoundTripper{Debug: s.Debug, TimeoutConfig: s.TimeoutConfig} 105 } 106 107 switch { 108 case s.EnableAllSupportedFeatures: 109 s.SupportedFeatures = AllFeatures 110 case s.SupportedFeatures == nil: 111 s.SupportedFeatures = GatewayCoreFeatures 112 default: 113 for feature := range GatewayCoreFeatures { 114 s.SupportedFeatures.Insert(feature) 115 } 116 } 117 118 for feature := range s.ExemptFeatures { 119 s.SupportedFeatures.Delete(feature) 120 } 121 122 if s.FS == nil { 123 s.FS = &conformance.Manifests 124 } 125 126 suite := &ConformanceTestSuite{ 127 Client: s.Client, 128 Clientset: s.Clientset, 129 RestConfig: s.RestConfig, 130 RoundTripper: roundTripper, 131 GatewayClassName: s.GatewayClassName, 132 Debug: s.Debug, 133 Cleanup: s.CleanupBaseResources, 134 BaseManifests: s.BaseManifests, 135 MeshManifests: s.MeshManifests, 136 Applier: kubernetes.Applier{ 137 NamespaceLabels: s.NamespaceLabels, 138 NamespaceAnnotations: s.NamespaceAnnotations, 139 }, 140 SupportedFeatures: s.SupportedFeatures, 141 TimeoutConfig: s.TimeoutConfig, 142 SkipTests: sets.New(s.SkipTests...), 143 RunTest: s.RunTest, 144 FS: *s.FS, 145 UsableNetworkAddresses: s.UsableNetworkAddresses, 146 UnusableNetworkAddresses: s.UnusableNetworkAddresses, 147 } 148 149 // apply defaults 150 if suite.BaseManifests == "" { 151 suite.BaseManifests = "base/manifests.yaml" 152 } 153 if suite.MeshManifests == "" { 154 suite.MeshManifests = "mesh/manifests.yaml" 155 } 156 157 return suite 158 } 159 160 // Setup ensures the base resources required for conformance tests are installed 161 // in the cluster. It also ensures that all relevant resources are ready. 162 func (suite *ConformanceTestSuite) Setup(t *testing.T) { 163 suite.Applier.FS = suite.FS 164 suite.Applier.UsableNetworkAddresses = suite.UsableNetworkAddresses 165 suite.Applier.UnusableNetworkAddresses = suite.UnusableNetworkAddresses 166 167 if suite.SupportedFeatures.Has(SupportGateway) { 168 t.Logf("Test Setup: Ensuring GatewayClass has been accepted") 169 suite.ControllerName = kubernetes.GWCMustHaveAcceptedConditionTrue(t, suite.Client, suite.TimeoutConfig, suite.GatewayClassName) 170 171 suite.Applier.GatewayClass = suite.GatewayClassName 172 suite.Applier.ControllerName = suite.ControllerName 173 174 t.Logf("Test Setup: Applying base manifests") 175 suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, suite.BaseManifests, suite.Cleanup) 176 177 t.Logf("Test Setup: Applying programmatic resources") 178 secret := kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-web-backend", "certificate", []string{"*"}) 179 suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) 180 secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-infra", "tls-validity-checks-certificate", []string{"*", "*.org"}) 181 suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) 182 secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-infra", "tls-passthrough-checks-certificate", []string{"abc.example.com"}) 183 suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) 184 secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-app-backend", "tls-passthrough-checks-certificate", []string{"abc.example.com"}) 185 suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) 186 187 t.Logf("Test Setup: Ensuring Gateways and Pods from base manifests are ready") 188 namespaces := []string{ 189 "gateway-conformance-infra", 190 "gateway-conformance-app-backend", 191 "gateway-conformance-web-backend", 192 } 193 kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, namespaces) 194 } 195 if suite.SupportedFeatures.Has(SupportMesh) { 196 t.Logf("Test Setup: Applying base manifests") 197 suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, suite.MeshManifests, suite.Cleanup) 198 t.Logf("Test Setup: Ensuring Gateways and Pods from mesh manifests are ready") 199 namespaces := []string{ 200 "gateway-conformance-mesh", 201 "gateway-conformance-mesh-consumer", 202 "gateway-conformance-app-backend", 203 "gateway-conformance-web-backend", 204 } 205 kubernetes.MeshNamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, namespaces) 206 } 207 } 208 209 // Run runs the provided set of conformance tests. 210 func (suite *ConformanceTestSuite) Run(t *testing.T, tests []ConformanceTest) { 211 for _, test := range tests { 212 t.Run(test.ShortName, func(t *testing.T) { 213 test.Run(t, suite) 214 }) 215 } 216 } 217 218 // ConformanceTest is used to define each individual conformance test. 219 type ConformanceTest struct { 220 ShortName string 221 Description string 222 Features []SupportedFeature 223 Manifests []string 224 Slow bool 225 Parallel bool 226 Test func(*testing.T, *ConformanceTestSuite) 227 } 228 229 // Run runs an individual tests, applying and cleaning up the required manifests 230 // before calling the Test function. 231 func (test *ConformanceTest) Run(t *testing.T, suite *ConformanceTestSuite) { 232 if test.Parallel { 233 t.Parallel() 234 } 235 236 // Check that all features exercised by the test have been opted into by 237 // the suite. 238 for _, feature := range test.Features { 239 if !suite.SupportedFeatures.Has(feature) { 240 t.Skipf("Skipping %s: suite does not support %s", test.ShortName, feature) 241 } 242 } 243 244 // check that the test should not be skipped 245 if suite.SkipTests.Has(test.ShortName) || suite.RunTest != "" && suite.RunTest != test.ShortName { 246 t.Skipf("Skipping %s: test explicitly skipped", test.ShortName) 247 } 248 249 for _, manifestLocation := range test.Manifests { 250 t.Logf("Applying %s", manifestLocation) 251 suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, manifestLocation, true) 252 } 253 254 test.Test(t, suite) 255 } 256 257 // ParseSupportedFeatures parses flag arguments and converts the string to 258 // sets.Set[suite.SupportedFeature] 259 func ParseSupportedFeatures(f string) sets.Set[SupportedFeature] { 260 if f == "" { 261 return nil 262 } 263 res := sets.Set[SupportedFeature]{} 264 for _, value := range strings.Split(f, ",") { 265 res.Insert(SupportedFeature(value)) 266 } 267 return res 268 } 269 270 // ParseKeyValuePairs parses flag arguments and converts the string to 271 // map[string]string containing label key/value pairs. 272 func ParseKeyValuePairs(f string) map[string]string { 273 if f == "" { 274 return nil 275 } 276 res := map[string]string{} 277 for _, kv := range strings.Split(f, ",") { 278 parts := strings.Split(kv, "=") 279 if len(parts) == 2 { 280 res[parts[0]] = parts[1] 281 } 282 } 283 return res 284 } 285 286 // ParseSkipTests parses flag arguments and converts the string to 287 // []string containing the tests to be skipped. 288 func ParseSkipTests(t string) []string { 289 if t == "" { 290 return nil 291 } 292 return strings.Split(t, ",") 293 }