istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/validation_test.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package pilot 19 20 import ( 21 "fmt" 22 "path" 23 "strings" 24 "testing" 25 26 json "github.com/go-jose/go-jose/v3/json" 27 "sigs.k8s.io/yaml" 28 29 "istio.io/istio/pkg/config/constants" 30 "istio.io/istio/pkg/config/schema/collections" 31 "istio.io/istio/pkg/test/datasets/validation" 32 "istio.io/istio/pkg/test/framework" 33 "istio.io/istio/pkg/test/framework/components/namespace" 34 "istio.io/istio/pkg/test/util/yml" 35 "istio.io/istio/pkg/util/sets" 36 ) 37 38 type testData string 39 40 func (t testData) load() (string, error) { 41 by, err := validation.FS.ReadFile(path.Join("dataset", string(t))) 42 if err != nil { 43 return "", err 44 } 45 46 return string(by), nil 47 } 48 49 func loadTestData(t framework.TestContext) []testData { 50 entries, err := validation.FS.ReadDir("dataset") 51 if err != nil { 52 t.Fatalf("Error loading test data: %v", err) 53 } 54 55 var result []testData 56 for _, e := range entries { 57 result = append(result, testData(e.Name())) 58 } 59 60 return result 61 } 62 63 func TestValidation(t *testing.T) { 64 framework.NewTest(t). 65 // Limit to Kube environment as we're testing integration of webhook with K8s. 66 67 RunParallel(func(t framework.TestContext) { 68 dataset := loadTestData(t) 69 70 denied := func(err error) bool { 71 if err == nil { 72 return false 73 } 74 // We are only checking the string literals of the rejection reasons 75 // from the webhook and the k8s api server as the returned errors are not 76 // k8s typed errors. 77 // Note: this explicitly does NOT catch OpenAPI schema rejections - only validating webhook rejections. 78 return strings.Contains(err.Error(), "denied the request") 79 } 80 81 for _, cluster := range t.Clusters().Configs() { 82 for i := range dataset { 83 d := dataset[i] 84 for _, valid := range []bool{true, false} { 85 t.NewSubTest(string(d) + "-" + fmt.Sprint(valid)).Run(func(t framework.TestContext) { 86 ym, err := d.load() 87 if err != nil { 88 t.Fatalf("Unable to load test data: %v", err) 89 } 90 91 if !valid { 92 ym, err = yml.ApplyAnnotation(ym, constants.AlwaysReject, "true") 93 if err != nil { 94 t.Fatal(err) 95 } 96 } 97 98 ns := namespace.NewOrFail(t, t, namespace.Config{ 99 Prefix: "validation", 100 }) 101 102 applyFiles := t.WriteYAMLOrFail(t, "apply", ym) 103 dryRunErr := cluster.ApplyYAMLFilesDryRun(ns.Name(), applyFiles...) 104 105 switch { 106 case dryRunErr != nil && valid: 107 if denied(dryRunErr) { 108 t.Fatalf("got unexpected for valid config: %v", dryRunErr) 109 } else { 110 t.Fatalf("got unexpected unknown error for valid config: %v", dryRunErr) 111 } 112 case dryRunErr == nil && !valid: 113 t.Fatalf("got unexpected success for invalid config") 114 case dryRunErr != nil && !valid: 115 if !denied(dryRunErr) { 116 t.Fatalf("config request denied for wrong reason: %v", dryRunErr) 117 } 118 } 119 120 wetRunErr := cluster.ApplyYAMLFiles(ns.Name(), applyFiles...) 121 t.CleanupConditionally(func() { 122 cluster.DeleteYAMLFiles(ns.Name(), applyFiles...) 123 }) 124 125 if dryRunErr != nil && wetRunErr == nil { 126 t.Fatalf("dry run returned error, but wet run returned none: %v", dryRunErr) 127 } 128 if dryRunErr == nil && wetRunErr != nil { 129 t.Fatalf("wet run returned errors, but dry run returned none: %v", wetRunErr) 130 } 131 }) 132 } 133 } 134 } 135 }) 136 } 137 138 var ignoredCRDs = []string{ 139 // We don't validate K8s resources 140 "/v1/Endpoints", 141 "/v1/Namespace", 142 "/v1/Node", 143 "/v1/Pod", 144 "/v1/Secret", 145 "/v1/Service", 146 "/v1/ConfigMap", 147 "apiextensions.k8s.io/v1/CustomResourceDefinition", 148 "admissionregistration.k8s.io/v1/MutatingWebhookConfiguration", 149 "apps/v1/Deployment", 150 "extensions/v1beta1/Ingress", 151 } 152 153 func TestEnsureNoMissingCRDs(t *testing.T) { 154 // This test ensures that we have necessary tests for all known CRDs. If you're breaking this test, it is likely 155 // that you need to update validation tests by either adding new/missing test cases, or removing test cases for 156 // types that are no longer supported. 157 framework.NewTest(t). 158 Run(func(t framework.TestContext) { 159 ignored := sets.New(ignoredCRDs...) 160 recognized := sets.New[string]() 161 162 // TODO(jasonwzm) remove this after multi-version APIs are supported. 163 for _, r := range collections.Pilot.All() { 164 s := strings.Join([]string{r.Group(), r.Version(), r.Kind()}, "/") 165 recognized.Insert(s) 166 for _, alias := range r.GroupVersionAliasKinds() { 167 s = strings.Join([]string{alias.Group, alias.Version, alias.Kind}, "/") 168 recognized.Insert(s) 169 } 170 } 171 // These CRDs are validated outside of Istio 172 for _, gvk := range []string{ 173 "gateway.networking.k8s.io/v1/Gateway", 174 "gateway.networking.k8s.io/v1beta1/Gateway", 175 "gateway.networking.k8s.io/v1/GatewayClass", 176 "gateway.networking.k8s.io/v1beta1/GatewayClass", 177 "gateway.networking.k8s.io/v1/HTTPRoute", 178 "gateway.networking.k8s.io/v1beta1/HTTPRoute", 179 "gateway.networking.k8s.io/v1alpha2/TCPRoute", 180 "gateway.networking.k8s.io/v1alpha2/TLSRoute", 181 "gateway.networking.k8s.io/v1beta1/ReferenceGrant", 182 "gateway.networking.k8s.io/v1alpha2/ReferenceGrant", 183 } { 184 recognized.Delete(gvk) 185 } 186 187 tested := sets.New[string]() 188 for _, te := range loadTestData(t) { 189 yamlBatch, err := te.load() 190 yamlParts := yml.SplitString(yamlBatch) 191 for _, yamlPart := range yamlParts { 192 if err != nil { 193 t.Fatalf("error loading test data: %v", err) 194 } 195 196 m := make(map[string]any) 197 by, er := yaml.YAMLToJSON([]byte(yamlPart)) 198 if er != nil { 199 t.Fatalf("error loading test data: %v", er) 200 } 201 if err = json.Unmarshal(by, &m); err != nil { 202 t.Fatalf("error parsing JSON: %v", err) 203 } 204 205 apiVersion := m["apiVersion"].(string) 206 kind := m["kind"].(string) 207 208 key := strings.Join([]string{apiVersion, kind}, "/") 209 tested.Insert(key) 210 } 211 } 212 213 for rec := range recognized { 214 if ignored.Contains(rec) { 215 continue 216 } 217 if !tested.Contains(rec) { 218 t.Errorf("CRD does not have a validation test: %v", rec) 219 } 220 } 221 222 for tst := range tested { 223 if _, found := recognized[tst]; !found { 224 t.Errorf("Unrecognized validation test data found: %v", tst) 225 } 226 } 227 }) 228 }