blob: a195fe529b3f741eec75351363ea29f54676f7b4 [file] [log] [blame]
//go:build integ
// +build integ
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilot
import (
"path"
"strings"
"testing"
)
import (
"gopkg.in/square/go-jose.v2/json"
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
"github.com/apache/dubbo-go-pixiu/pkg/test/datasets/validation"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/namespace"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/yml"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
type testData string
func (t testData) isValid() bool {
return !strings.HasSuffix(string(t), "-invalid.yaml")
}
func (t testData) isSkipped() bool {
return strings.HasSuffix(string(t), "-skipped.yaml")
}
func (t testData) load() (string, error) {
by, err := validation.FS.ReadFile(path.Join("dataset", string(t)))
if err != nil {
return "", err
}
return string(by), nil
}
func loadTestData(t framework.TestContext) []testData {
entries, err := validation.FS.ReadDir("dataset")
if err != nil {
t.Fatalf("Error loading test data: %v", err)
}
var result []testData
for _, e := range entries {
result = append(result, testData(e.Name()))
}
return result
}
func TestValidation(t *testing.T) {
framework.NewTest(t).
// Limit to Kube environment as we're testing integration of webhook with K8s.
RunParallel(func(t framework.TestContext) {
dataset := loadTestData(t)
denied := func(err error) bool {
if err == nil {
return false
}
// We are only checking the string literals of the rejection reasons
// from the webhook and the k8s api server as the returned errors are not
// k8s typed errors.
// Note: this explicitly does NOT catch OpenAPI schema rejections - only validating webhook rejections.
return strings.Contains(err.Error(), "denied the request")
}
for _, cluster := range t.Clusters().Configs() {
for i := range dataset {
d := dataset[i]
t.NewSubTest(string(d)).RunParallel(func(t framework.TestContext) {
if d.isSkipped() {
t.SkipNow()
return
}
ym, err := d.load()
if err != nil {
t.Fatalf("Unable to load test data: %v", err)
}
ns := namespace.NewOrFail(t, t, namespace.Config{
Prefix: "validation",
})
applyFiles := t.WriteYAMLOrFail(t, "apply", ym)
dryRunErr := cluster.ApplyYAMLFilesDryRun(ns.Name(), applyFiles...)
switch {
case dryRunErr != nil && d.isValid():
if denied(dryRunErr) {
t.Fatalf("got unexpected for valid config: %v", dryRunErr)
} else {
t.Fatalf("got unexpected unknown error for valid config: %v", dryRunErr)
}
case dryRunErr == nil && !d.isValid():
t.Fatalf("got unexpected success for invalid config")
case dryRunErr != nil && !d.isValid():
if !denied(dryRunErr) {
t.Fatalf("config request denied for wrong reason: %v", dryRunErr)
}
}
wetRunErr := cluster.ApplyYAMLFiles(ns.Name(), applyFiles...)
t.ConditionalCleanup(func() {
cluster.DeleteYAMLFiles(ns.Name(), applyFiles...)
})
if dryRunErr != nil && wetRunErr == nil {
t.Fatalf("dry run returned error, but wet run returned none: %v", dryRunErr)
}
if dryRunErr == nil && wetRunErr != nil {
t.Fatalf("wet run returned errors, but dry run returned none: %v", wetRunErr)
}
})
}
}
})
}
var ignoredCRDs = []string{
// We don't validate K8s resources
"/v1/Endpoints",
"/v1/Namespace",
"/v1/Node",
"/v1/Pod",
"/v1/Secret",
"/v1/Service",
"/v1/ConfigMap",
"apiextensions.k8s.io/v1/CustomResourceDefinition",
"admissionregistration.k8s.io/v1/MutatingWebhookConfiguration",
"apps/v1/Deployment",
"extensions/v1beta1/Ingress",
}
func TestEnsureNoMissingCRDs(t *testing.T) {
// This test ensures that we have necessary tests for all known CRDs. If you're breaking this test, it is likely
// that you need to update validation tests by either adding new/missing test cases, or removing test cases for
// types that are no longer supported.
framework.NewTest(t).
Run(func(t framework.TestContext) {
ignored := sets.New(ignoredCRDs...)
recognized := sets.New()
// TODO(jasonwzm) remove this after multi-version APIs are supported.
for _, r := range collections.Pilot.All() {
s := strings.Join([]string{r.Resource().Group(), r.Resource().Version(), r.Resource().Kind()}, "/")
recognized.Insert(s)
}
for _, gvk := range []string{
"networking.istio.io/v1beta1/Gateway",
"networking.istio.io/v1beta1/DestinationRule",
"networking.istio.io/v1beta1/VirtualService",
"networking.istio.io/v1beta1/WorkloadEntry",
"networking.istio.io/v1beta1/Sidecar",
} {
recognized.Insert(gvk)
}
// These CRDs are validated outside of Istio
for _, gvk := range []string{
"gateway.networking.k8s.io/v1alpha2/Gateway",
"gateway.networking.k8s.io/v1alpha2/GatewayClass",
"gateway.networking.k8s.io/v1alpha2/HTTPRoute",
"gateway.networking.k8s.io/v1alpha2/TCPRoute",
"gateway.networking.k8s.io/v1alpha2/TLSRoute",
"gateway.networking.k8s.io/v1alpha2/ReferencePolicy",
} {
recognized.Delete(gvk)
}
testedValid := sets.New()
testedInvalid := sets.New()
for _, te := range loadTestData(t) {
yamlBatch, err := te.load()
yamlParts := yml.SplitString(yamlBatch)
for _, yamlPart := range yamlParts {
if err != nil {
t.Fatalf("error loading test data: %v", err)
}
m := make(map[string]interface{})
by, er := yaml.YAMLToJSON([]byte(yamlPart))
if er != nil {
t.Fatalf("error loading test data: %v", er)
}
if err = json.Unmarshal(by, &m); err != nil {
t.Fatalf("error parsing JSON: %v", err)
}
apiVersion := m["apiVersion"].(string)
kind := m["kind"].(string)
key := strings.Join([]string{apiVersion, kind}, "/")
if te.isValid() {
testedValid.Insert(key)
} else {
testedInvalid.Insert(key)
}
}
}
for rec := range recognized {
if ignored.Contains(rec) {
continue
}
if !testedValid.Contains(rec) {
t.Errorf("CRD does not have a positive validation test: %v", rec)
}
if !testedInvalid.Contains(rec) {
t.Errorf("CRD does not have a negative validation test: %v", rec)
}
}
for tst := range testedValid {
if _, found := recognized[tst]; !found {
t.Errorf("Unrecognized positive validation test data found: %v", tst)
}
}
for tst := range testedInvalid {
if _, found := recognized[tst]; !found {
t.Errorf("Unrecognized negative validation test data found: %v", tst)
}
}
})
}