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 }