github.com/qsunny/k8s@v0.0.0-20220101153623-e6dca256d5bf/examples-master/staging/examples_test.go (about)

     1  /*
     2  Copyright 2014 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 examples_test
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/golang/glog"
    29  
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/apimachinery/pkg/util/validation/field"
    34  	"k8s.io/apimachinery/pkg/util/yaml"
    35  	"k8s.io/kubernetes/pkg/api/testapi"
    36  	"k8s.io/kubernetes/pkg/apis/apps"
    37  	appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
    38  	"k8s.io/kubernetes/pkg/apis/batch"
    39  	api "k8s.io/kubernetes/pkg/apis/core"
    40  	"k8s.io/kubernetes/pkg/apis/core/validation"
    41  	"k8s.io/kubernetes/pkg/apis/extensions"
    42  	expvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
    43  	"k8s.io/kubernetes/pkg/capabilities"
    44  	"k8s.io/kubernetes/pkg/registry/batch/job"
    45  	schedulerapi "k8s.io/kubernetes/pkg/scheduler/api"
    46  	schedulerapilatest "k8s.io/kubernetes/pkg/scheduler/api/latest"
    47  	schedulerapivalidation "k8s.io/kubernetes/pkg/scheduler/api/validation"
    48  )
    49  
    50  func validateObject(obj runtime.Object) (errors field.ErrorList) {
    51  	switch t := obj.(type) {
    52  	case *api.ReplicationController:
    53  		if t.Namespace == "" {
    54  			t.Namespace = metav1.NamespaceDefault
    55  		}
    56  		errors = validation.ValidateReplicationController(t)
    57  	case *api.ReplicationControllerList:
    58  		for i := range t.Items {
    59  			errors = append(errors, validateObject(&t.Items[i])...)
    60  		}
    61  	case *api.Service:
    62  		if t.Namespace == "" {
    63  			t.Namespace = metav1.NamespaceDefault
    64  		}
    65  		errors = validation.ValidateService(t)
    66  	case *api.ServiceList:
    67  		for i := range t.Items {
    68  			errors = append(errors, validateObject(&t.Items[i])...)
    69  		}
    70  	case *api.Pod:
    71  		if t.Namespace == "" {
    72  			t.Namespace = metav1.NamespaceDefault
    73  		}
    74  		errors = validation.ValidatePod(t)
    75  	case *api.PodList:
    76  		for i := range t.Items {
    77  			errors = append(errors, validateObject(&t.Items[i])...)
    78  		}
    79  	case *api.PersistentVolume:
    80  		errors = validation.ValidatePersistentVolume(t)
    81  	case *api.PersistentVolumeClaim:
    82  		if t.Namespace == "" {
    83  			t.Namespace = metav1.NamespaceDefault
    84  		}
    85  		errors = validation.ValidatePersistentVolumeClaim(t)
    86  	case *api.PodTemplate:
    87  		if t.Namespace == "" {
    88  			t.Namespace = metav1.NamespaceDefault
    89  		}
    90  		errors = validation.ValidatePodTemplate(t)
    91  	case *api.Endpoints:
    92  		if t.Namespace == "" {
    93  			t.Namespace = metav1.NamespaceDefault
    94  		}
    95  		errors = validation.ValidateEndpoints(t)
    96  	case *api.Namespace:
    97  		errors = validation.ValidateNamespace(t)
    98  	case *api.Secret:
    99  		if t.Namespace == "" {
   100  			t.Namespace = metav1.NamespaceDefault
   101  		}
   102  		errors = validation.ValidateSecret(t)
   103  	case *api.LimitRange:
   104  		if t.Namespace == "" {
   105  			t.Namespace = metav1.NamespaceDefault
   106  		}
   107  		errors = validation.ValidateLimitRange(t)
   108  	case *api.ResourceQuota:
   109  		if t.Namespace == "" {
   110  			t.Namespace = metav1.NamespaceDefault
   111  		}
   112  		errors = validation.ValidateResourceQuota(t)
   113  	case *extensions.Deployment:
   114  		if t.Namespace == "" {
   115  			t.Namespace = metav1.NamespaceDefault
   116  		}
   117  		errors = expvalidation.ValidateDeployment(t)
   118  	case *batch.Job:
   119  		if t.Namespace == "" {
   120  			t.Namespace = metav1.NamespaceDefault
   121  		}
   122  		// Job needs generateSelector called before validation, and job.Validate does this.
   123  		// See: https://github.com/kubernetes/kubernetes/issues/20951#issuecomment-187787040
   124  		t.ObjectMeta.UID = types.UID("fakeuid")
   125  		errors = job.Strategy.Validate(nil, t)
   126  	case *extensions.Ingress:
   127  		if t.Namespace == "" {
   128  			t.Namespace = metav1.NamespaceDefault
   129  		}
   130  		errors = expvalidation.ValidateIngress(t)
   131  	case *extensions.DaemonSet:
   132  		if t.Namespace == "" {
   133  			t.Namespace = metav1.NamespaceDefault
   134  		}
   135  		errors = expvalidation.ValidateDaemonSet(t)
   136  	case *apps.StatefulSet:
   137  		if t.Namespace == "" {
   138  			t.Namespace = metav1.NamespaceDefault
   139  		}
   140  		errors = appsvalidation.ValidateStatefulSet(t)
   141  	default:
   142  		errors = field.ErrorList{}
   143  		errors = append(errors, field.InternalError(field.NewPath(""), fmt.Errorf("no validation defined for %#v", obj)))
   144  	}
   145  	return errors
   146  }
   147  
   148  func validateschedulerpolicy(obj runtime.Object) error {
   149  	switch t := obj.(type) {
   150  	case *schedulerapi.Policy:
   151  		return schedulerapivalidation.ValidatePolicy(*t)
   152  	default:
   153  		return fmt.Errorf("obj type is not schedulerapi.Policy")
   154  	}
   155  }
   156  
   157  func walkJSONFiles(inDir string, fn func(name, path string, data []byte)) error {
   158  	return filepath.Walk(inDir, func(path string, info os.FileInfo, err error) error {
   159  		if err != nil {
   160  			return err
   161  		}
   162  
   163  		if info.IsDir() && path != inDir {
   164  			return filepath.SkipDir
   165  		}
   166  
   167  		file := filepath.Base(path)
   168  		if ext := filepath.Ext(file); ext == ".json" || ext == ".yaml" {
   169  			glog.Infof("Testing %s", path)
   170  			data, err := ioutil.ReadFile(path)
   171  			if err != nil {
   172  				return err
   173  			}
   174  			name := strings.TrimSuffix(file, ext)
   175  
   176  			if ext == ".yaml" {
   177  				out, err := yaml.ToJSON(data)
   178  				if err != nil {
   179  					return fmt.Errorf("%s: %v", path, err)
   180  				}
   181  				data = out
   182  			}
   183  
   184  			fn(name, path, data)
   185  		}
   186  		return nil
   187  	})
   188  }
   189  
   190  func TestExampleObjectSchemas(t *testing.T) {
   191  	cases := map[string]map[string]runtime.Object{
   192  		"../examples/guestbook": {
   193  			"frontend-deployment":     &extensions.Deployment{},
   194  			"redis-slave-deployment":  &extensions.Deployment{},
   195  			"redis-master-deployment": &extensions.Deployment{},
   196  			"frontend-service":        &api.Service{},
   197  			"redis-master-service":    &api.Service{},
   198  			"redis-slave-service":     &api.Service{},
   199  		},
   200  		"../examples/guestbook/legacy": {
   201  			"frontend-controller":     &api.ReplicationController{},
   202  			"redis-slave-controller":  &api.ReplicationController{},
   203  			"redis-master-controller": &api.ReplicationController{},
   204  		},
   205  		"../examples/guestbook-go": {
   206  			"guestbook-controller":    &api.ReplicationController{},
   207  			"redis-slave-controller":  &api.ReplicationController{},
   208  			"redis-master-controller": &api.ReplicationController{},
   209  			"guestbook-service":       &api.Service{},
   210  			"redis-master-service":    &api.Service{},
   211  			"redis-slave-service":     &api.Service{},
   212  		},
   213  		"../examples/volumes/iscsi": {
   214  			"chap-secret": &api.Secret{},
   215  			"iscsi":       &api.Pod{},
   216  			"iscsi-chap":  &api.Pod{},
   217  		},
   218  		"../examples/volumes/glusterfs": {
   219  			"glusterfs-pod":       &api.Pod{},
   220  			"glusterfs-endpoints": &api.Endpoints{},
   221  			"glusterfs-service":   &api.Service{},
   222  		},
   223  		"../examples": {
   224  			"scheduler-policy-config":               &schedulerapi.Policy{},
   225  			"scheduler-policy-config-with-extender": &schedulerapi.Policy{},
   226  		},
   227  		"../examples/volumes/rbd/secret": {
   228  			"ceph-secret": &api.Secret{},
   229  		},
   230  		"../examples/volumes/rbd": {
   231  			"rbd":             &api.Pod{},
   232  			"rbd-with-secret": &api.Pod{},
   233  		},
   234  		"../examples/storage/cassandra": {
   235  			"cassandra-daemonset":   &extensions.DaemonSet{},
   236  			"cassandra-controller":  &api.ReplicationController{},
   237  			"cassandra-service":     &api.Service{},
   238  			"cassandra-statefulset": &apps.StatefulSet{},
   239  		},
   240  		"../examples/cluster-dns": {
   241  			"dns-backend-rc":      &api.ReplicationController{},
   242  			"dns-backend-service": &api.Service{},
   243  			"dns-frontend-pod":    &api.Pod{},
   244  			"namespace-dev":       &api.Namespace{},
   245  			"namespace-prod":      &api.Namespace{},
   246  		},
   247  		"../examples/elasticsearch": {
   248  			"es-rc":           &api.ReplicationController{},
   249  			"es-svc":          &api.Service{},
   250  			"service-account": nil,
   251  		},
   252  		"../examples/explorer": {
   253  			"pod": &api.Pod{},
   254  		},
   255  		"../examples/storage/hazelcast": {
   256  			"hazelcast-deployment": &extensions.Deployment{},
   257  			"hazelcast-service":    &api.Service{},
   258  		},
   259  		"../examples/meteor": {
   260  			"meteor-controller": &api.ReplicationController{},
   261  			"meteor-service":    &api.Service{},
   262  			"mongo-pod":         &api.Pod{},
   263  			"mongo-service":     &api.Service{},
   264  		},
   265  		"../examples/mysql-wordpress-pd": {
   266  			"gce-volumes":          &api.PersistentVolume{},
   267  			"local-volumes":        &api.PersistentVolume{},
   268  			"mysql-deployment":     &api.Service{},
   269  			"wordpress-deployment": &api.Service{},
   270  		},
   271  		"../examples/volumes/nfs": {
   272  			"nfs-busybox-rc":     &api.ReplicationController{},
   273  			"nfs-server-rc":      &api.ReplicationController{},
   274  			"nfs-server-service": &api.Service{},
   275  			"nfs-pv":             &api.PersistentVolume{},
   276  			"nfs-pvc":            &api.PersistentVolumeClaim{},
   277  			"nfs-web-rc":         &api.ReplicationController{},
   278  			"nfs-web-service":    &api.Service{},
   279  		},
   280  		"../examples/openshift-origin": {
   281  			"openshift-origin-namespace": &api.Namespace{},
   282  			"openshift-controller":       &extensions.Deployment{},
   283  			"openshift-service":          &api.Service{},
   284  			"etcd-controller":            &extensions.Deployment{},
   285  			"etcd-service":               &api.Service{},
   286  			"etcd-discovery-controller":  &extensions.Deployment{},
   287  			"etcd-discovery-service":     &api.Service{},
   288  			"secret":                     nil,
   289  		},
   290  		"../examples/phabricator": {
   291  			"phabricator-controller": &api.ReplicationController{},
   292  			"phabricator-service":    &api.Service{},
   293  		},
   294  		"../examples/storage/redis": {
   295  			"redis-controller":          &api.ReplicationController{},
   296  			"redis-master":              &api.Pod{},
   297  			"redis-sentinel-controller": &api.ReplicationController{},
   298  			"redis-sentinel-service":    &api.Service{},
   299  		},
   300  		"../examples/storage/rethinkdb": {
   301  			"admin-pod":      &api.Pod{},
   302  			"admin-service":  &api.Service{},
   303  			"driver-service": &api.Service{},
   304  			"rc":             &api.ReplicationController{},
   305  		},
   306  		"../examples/spark": {
   307  			"namespace-spark-cluster":   &api.Namespace{},
   308  			"spark-master-controller":   &api.ReplicationController{},
   309  			"spark-master-service":      &api.Service{},
   310  			"spark-ui-proxy-controller": &api.ReplicationController{},
   311  			"spark-ui-proxy-service":    &api.Service{},
   312  			"spark-worker-controller":   &api.ReplicationController{},
   313  			"zeppelin-controller":       &api.ReplicationController{},
   314  			"zeppelin-service":          &api.Service{},
   315  		},
   316  		"../examples/spark/spark-gluster": {
   317  			"spark-master-service":    &api.Service{},
   318  			"spark-master-controller": &api.ReplicationController{},
   319  			"spark-worker-controller": &api.ReplicationController{},
   320  			"glusterfs-endpoints":     &api.Endpoints{},
   321  		},
   322  		"../examples/storm": {
   323  			"storm-nimbus-service":    &api.Service{},
   324  			"storm-nimbus":            &api.Pod{},
   325  			"storm-worker-controller": &api.ReplicationController{},
   326  			"zookeeper-service":       &api.Service{},
   327  			"zookeeper":               &api.Pod{},
   328  		},
   329  		"../examples/volumes/cephfs/": {
   330  			"cephfs":             &api.Pod{},
   331  			"cephfs-with-secret": &api.Pod{},
   332  		},
   333  		"../examples/volumes/fibre_channel": {
   334  			"fc": &api.Pod{},
   335  		},
   336  		"../examples/javaweb-tomcat-sidecar": {
   337  			"javaweb":   &api.Pod{},
   338  			"javaweb-2": &api.Pod{},
   339  		},
   340  		"../examples/volumes/azure_file": {
   341  			"azure": &api.Pod{},
   342  		},
   343  		"../examples/volumes/azure_disk": {
   344  			"azure": &api.Pod{},
   345  		},
   346  	}
   347  
   348  	capabilities.SetForTests(capabilities.Capabilities{
   349  		AllowPrivileged: true,
   350  	})
   351  
   352  	for path, expected := range cases {
   353  		tested := 0
   354  		err := walkJSONFiles(path, func(name, path string, data []byte) {
   355  			expectedType, found := expected[name]
   356  			if !found {
   357  				t.Errorf("%s: %s does not have a test case defined", path, name)
   358  				return
   359  			}
   360  			tested++
   361  			if expectedType == nil {
   362  				t.Logf("skipping : %s/%s\n", path, name)
   363  				return
   364  			}
   365  			if strings.Contains(name, "scheduler-policy-config") {
   366  				if err := runtime.DecodeInto(schedulerapilatest.Codec, data, expectedType); err != nil {
   367  					t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data))
   368  					return
   369  				}
   370  				if err := validateschedulerpolicy(expectedType); err != nil {
   371  					t.Errorf("%s did not validate correctly: %v\n%s", path, err, string(data))
   372  					return
   373  				}
   374  			} else {
   375  				codec, err := testapi.GetCodecForObject(expectedType)
   376  				if err != nil {
   377  					t.Errorf("Could not get codec for %s: %s", expectedType, err)
   378  				}
   379  				if err := runtime.DecodeInto(codec, data, expectedType); err != nil {
   380  					t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data))
   381  					return
   382  				}
   383  				if errors := validateObject(expectedType); len(errors) > 0 {
   384  					t.Errorf("%s did not validate correctly: %v", path, errors)
   385  				}
   386  			}
   387  		})
   388  		if err != nil {
   389  			t.Errorf("Expected no error, Got %v", err)
   390  		}
   391  		if tested != len(expected) {
   392  			t.Errorf("Directory %v: Expected %d examples, Got %d", path, len(expected), tested)
   393  		}
   394  	}
   395  }
   396  
   397  // This regex is tricky, but it works.  For future me, here is the decode:
   398  //
   399  // Flags: (?ms) = multiline match, allow . to match \n
   400  // 1) Look for a line that starts with ``` (a markdown code block)
   401  // 2) (?: ... ) = non-capturing group
   402  // 3) (P<name>) = capture group as "name"
   403  // 4) Look for #1 followed by either:
   404  // 4a)    "yaml" followed by any word-characters followed by a newline (e.g. ```yamlfoo\n)
   405  // 4b)    "any word-characters followed by a newline (e.g. ```json\n)
   406  // 5) Look for either:
   407  // 5a)    #4a followed by one or more characters (non-greedy)
   408  // 5b)    #4b followed by { followed by one or more characters (non-greedy) followed by }
   409  // 6) Look for #5 followed by a newline followed by ``` (end of the code block)
   410  //
   411  // This could probably be simplified, but is already too delicate.  Before any
   412  // real changes, we should have a testcase that just tests this regex.
   413  var sampleRegexp = regexp.MustCompile("(?ms)^```(?:(?P<type>yaml)\\w*\\n(?P<content>.+?)|\\w*\\n(?P<content>\\{.+?\\}))\\n^```")
   414  var subsetRegexp = regexp.MustCompile("(?ms)\\.{3}")
   415  
   416  func TestReadme(t *testing.T) {
   417  	paths := []struct {
   418  		file         string
   419  		expectedType []runtime.Object
   420  	}{
   421  		{"../README.md", []runtime.Object{&api.Pod{}}},
   422  		{"../examples/volumes/iscsi/README.md", []runtime.Object{&api.Secret{}}},
   423  	}
   424  
   425  	for _, path := range paths {
   426  		data, err := ioutil.ReadFile(path.file)
   427  		if err != nil {
   428  			t.Errorf("Unable to read file %s: %v", path, err)
   429  			continue
   430  		}
   431  
   432  		matches := sampleRegexp.FindAllStringSubmatch(string(data), -1)
   433  		if matches == nil {
   434  			continue
   435  		}
   436  		ix := 0
   437  		for _, match := range matches {
   438  			var content, subtype string
   439  			for i, name := range sampleRegexp.SubexpNames() {
   440  				if name == "type" {
   441  					subtype = match[i]
   442  				}
   443  				if name == "content" && match[i] != "" {
   444  					content = match[i]
   445  				}
   446  			}
   447  			if subtype == "yaml" && subsetRegexp.FindString(content) != "" {
   448  				t.Logf("skipping (%s): \n%s", subtype, content)
   449  				continue
   450  			}
   451  
   452  			var expectedType runtime.Object
   453  			if len(path.expectedType) == 1 {
   454  				expectedType = path.expectedType[0]
   455  			} else {
   456  				expectedType = path.expectedType[ix]
   457  				ix++
   458  			}
   459  			json, err := yaml.ToJSON([]byte(content))
   460  			if err != nil {
   461  				t.Errorf("%s could not be converted to JSON: %v\n%s", path, err, string(content))
   462  			}
   463  			if err := runtime.DecodeInto(testapi.Default.Codec(), json, expectedType); err != nil {
   464  				t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(content))
   465  				continue
   466  			}
   467  			if errors := validateObject(expectedType); len(errors) > 0 {
   468  				t.Errorf("%s did not validate correctly: %v", path, errors)
   469  			}
   470  			_, err = runtime.Encode(testapi.Default.Codec(), expectedType)
   471  			if err != nil {
   472  				t.Errorf("Could not encode object: %v", err)
   473  				continue
   474  			}
   475  		}
   476  	}
   477  }