k8s.io/apiserver@v0.31.1/pkg/endpoints/apiserver_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 endpoints 18 19 import ( 20 "bytes" 21 "compress/gzip" 22 "context" 23 "encoding/json" 24 "errors" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "math/rand" 29 "net/http" 30 "net/http/httptest" 31 "net/http/httputil" 32 "net/url" 33 "reflect" 34 "strconv" 35 "strings" 36 "sync" 37 "testing" 38 "time" 39 40 "github.com/emicklei/go-restful/v3" 41 "github.com/google/go-cmp/cmp" 42 43 "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" 44 apiequality "k8s.io/apimachinery/pkg/api/equality" 45 apierrors "k8s.io/apimachinery/pkg/api/errors" 46 "k8s.io/apimachinery/pkg/api/meta" 47 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 48 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" 49 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 50 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 51 metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" 52 "k8s.io/apimachinery/pkg/fields" 53 "k8s.io/apimachinery/pkg/labels" 54 "k8s.io/apimachinery/pkg/runtime" 55 "k8s.io/apimachinery/pkg/runtime/schema" 56 "k8s.io/apimachinery/pkg/runtime/serializer" 57 "k8s.io/apimachinery/pkg/runtime/serializer/streaming" 58 "k8s.io/apimachinery/pkg/types" 59 "k8s.io/apimachinery/pkg/util/managedfields" 60 "k8s.io/apimachinery/pkg/util/net" 61 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 62 "k8s.io/apimachinery/pkg/util/sets" 63 "k8s.io/apimachinery/pkg/watch" 64 "k8s.io/apiserver/pkg/admission" 65 auditinternal "k8s.io/apiserver/pkg/apis/audit" 66 "k8s.io/apiserver/pkg/apis/example" 67 examplefuzzer "k8s.io/apiserver/pkg/apis/example/fuzzer" 68 examplev1 "k8s.io/apiserver/pkg/apis/example/v1" 69 "k8s.io/apiserver/pkg/audit" 70 auditpolicy "k8s.io/apiserver/pkg/audit/policy" 71 genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" 72 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" 73 "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" 74 "k8s.io/apiserver/pkg/endpoints/request" 75 genericapitesting "k8s.io/apiserver/pkg/endpoints/testing" 76 "k8s.io/apiserver/pkg/registry/rest" 77 ) 78 79 type alwaysMutatingDeny struct{} 80 81 func (alwaysMutatingDeny) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { 82 return admission.NewForbidden(a, errors.New("Mutating admission control is denying all modifications")) 83 } 84 85 func (alwaysMutatingDeny) Handles(operation admission.Operation) bool { 86 return true 87 } 88 89 var _ admission.MutationInterface = &alwaysMutatingDeny{} 90 91 type alwaysValidatingDeny struct{} 92 93 func (alwaysValidatingDeny) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { 94 return admission.NewForbidden(a, errors.New("Validating admission control is denying all modifications")) 95 } 96 97 func (alwaysValidatingDeny) Handles(operation admission.Operation) bool { 98 return true 99 } 100 101 var _ admission.ValidationInterface = &alwaysValidatingDeny{} 102 103 // This creates fake API versions, similar to api/latest.go. 104 var testAPIGroup = "test.group" 105 var testAPIGroup2 = "test.group2" 106 var testInternalGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: runtime.APIVersionInternal} 107 var testGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: "version"} 108 var newGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: "version2"} 109 var testGroup2Version = schema.GroupVersion{Group: testAPIGroup2, Version: "version"} 110 var testInternalGroup2Version = schema.GroupVersion{Group: testAPIGroup2, Version: runtime.APIVersionInternal} 111 var prefix = "apis" 112 113 var grouplessGroupVersion = schema.GroupVersion{Group: "", Version: "v1"} 114 var grouplessInternalGroupVersion = schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal} 115 var grouplessPrefix = "api" 116 117 var groupVersions = []schema.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion} 118 119 var scheme = runtime.NewScheme() 120 var codecs = serializer.NewCodecFactory(scheme) 121 122 var codec = codecs.LegacyCodec(groupVersions...) 123 var testCodec = codecs.LegacyCodec(testGroupVersion) 124 var newCodec = codecs.LegacyCodec(newGroupVersion) 125 var parameterCodec = runtime.NewParameterCodec(scheme) 126 127 var accessor = meta.NewAccessor() 128 var namer runtime.Namer = accessor 129 var admissionControl admission.Interface 130 131 func init() { 132 metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) 133 134 // unnamed core group 135 scheme.AddUnversionedTypes(grouplessGroupVersion, &metav1.Status{}) 136 metav1.AddToGroupVersion(scheme, grouplessGroupVersion) 137 138 utilruntime.Must(example.AddToScheme(scheme)) 139 utilruntime.Must(examplev1.AddToScheme(scheme)) 140 } 141 142 func addGrouplessTypes() { 143 scheme.AddKnownTypes(grouplessGroupVersion, 144 &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{}, 145 &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) 146 scheme.AddKnownTypes(grouplessInternalGroupVersion, 147 &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, 148 &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) 149 150 utilruntime.Must(genericapitesting.RegisterConversions(scheme)) 151 } 152 153 func addTestTypes() { 154 scheme.AddKnownTypes(testGroupVersion, 155 &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, 156 &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, 157 &genericapitesting.SimpleXGSubresource{}) 158 scheme.AddKnownTypes(testGroupVersion, &examplev1.Pod{}) 159 scheme.AddKnownTypes(testInternalGroupVersion, 160 &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, 161 &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, 162 &genericapitesting.SimpleXGSubresource{}) 163 scheme.AddKnownTypes(testInternalGroupVersion, &example.Pod{}) 164 // Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their 165 // their corresponding internal versions, to verify that the desired group version object is 166 // served in the tests. 167 scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{}) 168 scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{}) 169 metav1.AddToGroupVersion(scheme, testGroupVersion) 170 171 utilruntime.Must(genericapitesting.RegisterConversions(scheme)) 172 } 173 174 func addNewTestTypes() { 175 scheme.AddKnownTypes(newGroupVersion, 176 &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, 177 &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, 178 &examplev1.Pod{}, 179 ) 180 metav1.AddToGroupVersion(scheme, newGroupVersion) 181 182 utilruntime.Must(genericapitesting.RegisterConversions(scheme)) 183 } 184 185 func init() { 186 // Certain API objects are returned regardless of the contents of storage: 187 // api.Status is returned in errors 188 189 addGrouplessTypes() 190 addTestTypes() 191 addNewTestTypes() 192 193 scheme.AddFieldLabelConversionFunc(grouplessGroupVersion.WithKind("Simple"), 194 func(label, value string) (string, string, error) { 195 return label, value, nil 196 }, 197 ) 198 scheme.AddFieldLabelConversionFunc(testGroupVersion.WithKind("Simple"), 199 func(label, value string) (string, string, error) { 200 return label, value, nil 201 }, 202 ) 203 scheme.AddFieldLabelConversionFunc(newGroupVersion.WithKind("Simple"), 204 func(label, value string) (string, string, error) { 205 return label, value, nil 206 }, 207 ) 208 } 209 210 // defaultAPIServer exposes nested objects for testability. 211 type defaultAPIServer struct { 212 http.Handler 213 container *restful.Container 214 } 215 216 func handleWithWarnings(storage map[string]rest.Storage) http.Handler { 217 return genericapifilters.WithWarningRecorder(handle(storage)) 218 } 219 220 // uses the default settings 221 func handle(storage map[string]rest.Storage) http.Handler { 222 return handleInternal(storage, admissionControl, nil) 223 } 224 225 func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, auditSink audit.Sink) http.Handler { 226 container := restful.NewContainer() 227 container.Router(restful.CurlyRouter{}) 228 mux := container.ServeMux 229 230 template := APIGroupVersion{ 231 Storage: storage, 232 233 Creater: scheme, 234 Convertor: scheme, 235 TypeConverter: managedfields.NewDeducedTypeConverter(), 236 UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), 237 Defaulter: scheme, 238 Typer: scheme, 239 Namer: namer, 240 RootScopedKinds: sets.NewString("SimpleRoot"), 241 242 EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), 243 244 ParameterCodec: parameterCodec, 245 246 Admit: admissionControl, 247 } 248 249 // groupless v1 version 250 { 251 group := template 252 group.Root = "/" + grouplessPrefix 253 group.GroupVersion = grouplessGroupVersion 254 group.OptionsExternalVersion = &grouplessGroupVersion 255 group.Serializer = codecs 256 if _, _, err := (&group).InstallREST(container); err != nil { 257 panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) 258 } 259 } 260 261 // group version 1 262 { 263 group := template 264 group.Root = "/" + prefix 265 group.GroupVersion = testGroupVersion 266 group.OptionsExternalVersion = &testGroupVersion 267 group.Serializer = codecs 268 if _, _, err := (&group).InstallREST(container); err != nil { 269 panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) 270 } 271 } 272 273 // group version 2 274 { 275 group := template 276 group.Root = "/" + prefix 277 group.GroupVersion = newGroupVersion 278 group.OptionsExternalVersion = &newGroupVersion 279 group.Serializer = codecs 280 if _, _, err := (&group).InstallREST(container); err != nil { 281 panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) 282 } 283 } 284 longRunningCheck := func(r *http.Request, requestInfo *request.RequestInfo) bool { 285 // simplified long-running check 286 return requestInfo.Verb == "watch" || requestInfo.Verb == "proxy" 287 } 288 fakeRuleEvaluator := auditpolicy.NewFakePolicyRuleEvaluator(auditinternal.LevelRequestResponse, nil) 289 handler := genericapifilters.WithAudit(mux, auditSink, fakeRuleEvaluator, longRunningCheck) 290 handler = genericapifilters.WithRequestDeadline(handler, auditSink, fakeRuleEvaluator, longRunningCheck, codecs, 60*time.Second) 291 handler = genericapifilters.WithRequestInfo(handler, testRequestInfoResolver()) 292 handler = genericapifilters.WithAuditInit(handler) 293 294 return &defaultAPIServer{handler, container} 295 } 296 297 func testRequestInfoResolver() *request.RequestInfoFactory { 298 return &request.RequestInfoFactory{ 299 APIPrefixes: sets.NewString("api", "apis"), 300 GrouplessAPIPrefixes: sets.NewString("api"), 301 } 302 } 303 304 func TestSimpleSetupRight(t *testing.T) { 305 s := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "aName"}} 306 wire, err := runtime.Encode(codec, s) 307 if err != nil { 308 t.Fatal(err) 309 } 310 s2, err := runtime.Decode(codec, wire) 311 if err != nil { 312 t.Fatal(err) 313 } 314 if !reflect.DeepEqual(s, s2) { 315 t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2) 316 } 317 } 318 319 func TestSimpleOptionsSetupRight(t *testing.T) { 320 s := &genericapitesting.SimpleGetOptions{} 321 wire, err := runtime.Encode(codec, s) 322 if err != nil { 323 t.Fatal(err) 324 } 325 s2, err := runtime.Decode(codec, wire) 326 if err != nil { 327 t.Fatal(err) 328 } 329 if !reflect.DeepEqual(s, s2) { 330 t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2) 331 } 332 } 333 334 type SimpleRESTStorage struct { 335 lock sync.Mutex 336 337 errors map[string]error 338 list []genericapitesting.Simple 339 item genericapitesting.Simple 340 341 updated *genericapitesting.Simple 342 created *genericapitesting.Simple 343 344 stream *SimpleStream 345 346 deleted string 347 deleteOptions *metav1.DeleteOptions 348 349 actualNamespace string 350 namespacePresent bool 351 352 // These are set when Watch is called 353 fakeWatch *watch.FakeWatcher 354 requestedLabelSelector labels.Selector 355 requestedFieldSelector fields.Selector 356 requestedResourceVersion string 357 requestedResourceNamespace string 358 359 expectedResourceNamespace string 360 361 // If non-nil, called inside the WorkFunc when answering update, delete, create. 362 // obj receives the original input to the update, delete, or create call. 363 injectedFunction func(obj runtime.Object) (returnObj runtime.Object, err error) 364 } 365 366 func (storage *SimpleRESTStorage) NamespaceScoped() bool { 367 return true 368 } 369 370 func (storage *SimpleRESTStorage) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { 371 return rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "simple"}).ConvertToTable(ctx, obj, tableOptions) 372 } 373 374 func (storate *SimpleRESTStorage) GetSingularName() string { 375 return "simple" 376 } 377 378 func (storage *SimpleRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { 379 storage.checkContext(ctx) 380 result := &genericapitesting.SimpleList{ 381 ListMeta: metav1.ListMeta{ 382 ResourceVersion: "10", 383 }, 384 Items: storage.list, 385 } 386 storage.requestedLabelSelector = labels.Everything() 387 if options != nil && options.LabelSelector != nil { 388 storage.requestedLabelSelector = options.LabelSelector 389 } 390 storage.requestedFieldSelector = fields.Everything() 391 if options != nil && options.FieldSelector != nil { 392 storage.requestedFieldSelector = options.FieldSelector 393 } 394 return result, storage.errors["list"] 395 } 396 397 type SimpleStream struct { 398 version string 399 accept string 400 contentType string 401 err error 402 403 io.Reader 404 closed bool 405 } 406 407 func (s *SimpleStream) Close() error { 408 s.closed = true 409 return nil 410 } 411 412 func (s *SimpleStream) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } 413 func (s *SimpleStream) DeepCopyObject() runtime.Object { 414 panic("SimpleStream does not support DeepCopy") 415 } 416 417 func (s *SimpleStream) InputStream(_ context.Context, version, accept string) (io.ReadCloser, bool, string, error) { 418 s.version = version 419 s.accept = accept 420 return s, false, s.contentType, s.err 421 } 422 423 type OutputConnect struct { 424 response string 425 } 426 427 func (h *OutputConnect) ServeHTTP(w http.ResponseWriter, req *http.Request) { 428 w.Write([]byte(h.response)) 429 } 430 431 func (storage *SimpleRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { 432 storage.checkContext(ctx) 433 if id == "binary" { 434 return storage.stream, storage.errors["get"] 435 } 436 return storage.item.DeepCopy(), storage.errors["get"] 437 } 438 439 func (storage *SimpleRESTStorage) checkContext(ctx context.Context) { 440 storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx) 441 } 442 443 func (storage *SimpleRESTStorage) Delete(ctx context.Context, id string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { 444 storage.checkContext(ctx) 445 storage.deleted = id 446 storage.deleteOptions = options 447 if err := storage.errors["delete"]; err != nil { 448 return nil, false, err 449 } 450 if err := deleteValidation(ctx, &storage.item); err != nil { 451 return nil, false, err 452 } 453 var obj runtime.Object = &metav1.Status{Status: metav1.StatusSuccess} 454 var err error 455 if storage.injectedFunction != nil { 456 obj, err = storage.injectedFunction(&genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: id}}) 457 } 458 return obj, true, err 459 } 460 461 func (storage *SimpleRESTStorage) New() runtime.Object { 462 return &genericapitesting.Simple{} 463 } 464 465 func (storage *SimpleRESTStorage) NewList() runtime.Object { 466 return &genericapitesting.SimpleList{} 467 } 468 469 func (storage *SimpleRESTStorage) Destroy() { 470 } 471 472 func (storage *SimpleRESTStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { 473 storage.checkContext(ctx) 474 storage.created = obj.(*genericapitesting.Simple) 475 if err := storage.errors["create"]; err != nil { 476 return nil, err 477 } 478 var err error 479 if storage.injectedFunction != nil { 480 obj, err = storage.injectedFunction(obj) 481 } 482 if err := createValidation(ctx, obj); err != nil { 483 return nil, err 484 } 485 return obj, err 486 } 487 488 func (storage *SimpleRESTStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { 489 storage.checkContext(ctx) 490 obj, err := objInfo.UpdatedObject(ctx, &storage.item) 491 if err != nil { 492 return nil, false, err 493 } 494 storage.updated = obj.(*genericapitesting.Simple) 495 if err := storage.errors["update"]; err != nil { 496 return nil, false, err 497 } 498 if storage.injectedFunction != nil { 499 obj, err = storage.injectedFunction(obj) 500 } 501 if err := updateValidation(ctx, &storage.item, obj); err != nil { 502 return nil, false, err 503 } 504 return obj, false, err 505 } 506 507 // Implement ResourceWatcher. 508 func (storage *SimpleRESTStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { 509 storage.lock.Lock() 510 defer storage.lock.Unlock() 511 storage.checkContext(ctx) 512 storage.requestedLabelSelector = labels.Everything() 513 if options != nil && options.LabelSelector != nil { 514 storage.requestedLabelSelector = options.LabelSelector 515 } 516 storage.requestedFieldSelector = fields.Everything() 517 if options != nil && options.FieldSelector != nil { 518 storage.requestedFieldSelector = options.FieldSelector 519 } 520 storage.requestedResourceVersion = "" 521 if options != nil { 522 storage.requestedResourceVersion = options.ResourceVersion 523 } 524 storage.requestedResourceNamespace = request.NamespaceValue(ctx) 525 if err := storage.errors["watch"]; err != nil { 526 return nil, err 527 } 528 storage.fakeWatch = watch.NewFake() 529 return storage.fakeWatch, nil 530 } 531 532 func (storage *SimpleRESTStorage) Watcher() *watch.FakeWatcher { 533 storage.lock.Lock() 534 defer storage.lock.Unlock() 535 return storage.fakeWatch 536 } 537 538 // Implement Connecter 539 type ConnecterRESTStorage struct { 540 connectHandler http.Handler 541 handlerFunc func() http.Handler 542 543 emptyConnectOptions runtime.Object 544 receivedConnectOptions runtime.Object 545 receivedID string 546 receivedResponder rest.Responder 547 takesPath string 548 } 549 550 // Implement Connecter 551 var _ = rest.Connecter(&ConnecterRESTStorage{}) 552 553 func (s *ConnecterRESTStorage) New() runtime.Object { 554 return &genericapitesting.Simple{} 555 } 556 557 func (s *ConnecterRESTStorage) Destroy() { 558 } 559 560 func (s *ConnecterRESTStorage) Connect(ctx context.Context, id string, options runtime.Object, responder rest.Responder) (http.Handler, error) { 561 s.receivedConnectOptions = options 562 s.receivedID = id 563 s.receivedResponder = responder 564 if s.handlerFunc != nil { 565 return s.handlerFunc(), nil 566 } 567 return s.connectHandler, nil 568 } 569 570 func (s *ConnecterRESTStorage) ConnectMethods() []string { 571 return []string{"GET", "POST", "PUT", "DELETE"} 572 } 573 574 func (s *ConnecterRESTStorage) NewConnectOptions() (runtime.Object, bool, string) { 575 if len(s.takesPath) > 0 { 576 return s.emptyConnectOptions, true, s.takesPath 577 } 578 return s.emptyConnectOptions, false, "" 579 } 580 581 func (s *ConnecterRESTStorage) GetSingularName() string { 582 return "simple" 583 } 584 585 type MetadataRESTStorage struct { 586 *SimpleRESTStorage 587 types []string 588 } 589 590 func (m *MetadataRESTStorage) ProducesMIMETypes(method string) []string { 591 return m.types 592 } 593 594 func (m *MetadataRESTStorage) ProducesObject(verb string) interface{} { 595 return nil 596 } 597 598 var _ rest.StorageMetadata = &MetadataRESTStorage{} 599 600 type GetWithOptionsRESTStorage struct { 601 *SimpleRESTStorage 602 optionsReceived runtime.Object 603 takesPath string 604 } 605 606 func (r *GetWithOptionsRESTStorage) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) { 607 if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok { 608 return nil, fmt.Errorf("Unexpected options object: %#v", options) 609 } 610 r.optionsReceived = options 611 return r.SimpleRESTStorage.Get(ctx, name, &metav1.GetOptions{}) 612 } 613 614 func (r *GetWithOptionsRESTStorage) NewGetOptions() (runtime.Object, bool, string) { 615 if len(r.takesPath) > 0 { 616 return &genericapitesting.SimpleGetOptions{}, true, r.takesPath 617 } 618 return &genericapitesting.SimpleGetOptions{}, false, "" 619 } 620 621 var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{} 622 623 type GetWithOptionsRootRESTStorage struct { 624 *SimpleTypedStorage 625 optionsReceived runtime.Object 626 takesPath string 627 } 628 629 func (r *GetWithOptionsRootRESTStorage) GetSingularName() string { 630 return "simple" 631 } 632 633 func (r *GetWithOptionsRootRESTStorage) NamespaceScoped() bool { 634 return false 635 } 636 637 func (r *GetWithOptionsRootRESTStorage) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) { 638 if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok { 639 return nil, fmt.Errorf("Unexpected options object: %#v", options) 640 } 641 r.optionsReceived = options 642 return r.SimpleTypedStorage.Get(ctx, name, &metav1.GetOptions{}) 643 } 644 645 func (r *GetWithOptionsRootRESTStorage) NewGetOptions() (runtime.Object, bool, string) { 646 if len(r.takesPath) > 0 { 647 return &genericapitesting.SimpleGetOptions{}, true, r.takesPath 648 } 649 return &genericapitesting.SimpleGetOptions{}, false, "" 650 } 651 652 var _ rest.GetterWithOptions = &GetWithOptionsRootRESTStorage{} 653 654 type NamedCreaterRESTStorage struct { 655 *SimpleRESTStorage 656 createdName string 657 } 658 659 func (storage *NamedCreaterRESTStorage) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { 660 storage.checkContext(ctx) 661 storage.created = obj.(*genericapitesting.Simple) 662 storage.createdName = name 663 if err := storage.errors["create"]; err != nil { 664 return nil, err 665 } 666 var err error 667 if storage.injectedFunction != nil { 668 obj, err = storage.injectedFunction(obj) 669 } 670 if err := createValidation(ctx, obj); err != nil { 671 return nil, err 672 } 673 return obj, err 674 } 675 676 type SimpleTypedStorage struct { 677 errors map[string]error 678 item runtime.Object 679 baseType runtime.Object 680 681 actualNamespace string 682 namespacePresent bool 683 } 684 685 func (storage *SimpleTypedStorage) New() runtime.Object { 686 return storage.baseType 687 } 688 689 func (storage *SimpleTypedStorage) Destroy() { 690 } 691 692 func (storage *SimpleTypedStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { 693 storage.checkContext(ctx) 694 return storage.item.DeepCopyObject(), storage.errors["get"] 695 } 696 697 func (storage *SimpleTypedStorage) checkContext(ctx context.Context) { 698 storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx) 699 } 700 701 func (storage *SimpleTypedStorage) GetSingularName() string { 702 return "simple" 703 } 704 705 func bodyOrDie(response *http.Response) string { 706 defer response.Body.Close() 707 body, err := ioutil.ReadAll(response.Body) 708 if err != nil { 709 panic(err) 710 } 711 return string(body) 712 } 713 714 func extractBody(response *http.Response, object runtime.Object) (string, error) { 715 return extractBodyDecoder(response, object, codec) 716 } 717 718 func extractBodyDecoder(response *http.Response, object runtime.Object, decoder runtime.Decoder) (string, error) { 719 defer response.Body.Close() 720 body, err := ioutil.ReadAll(response.Body) 721 if err != nil { 722 return string(body), err 723 } 724 return string(body), runtime.DecodeInto(decoder, body, object) 725 } 726 727 func extractBodyObject(response *http.Response, decoder runtime.Decoder) (runtime.Object, string, error) { 728 defer response.Body.Close() 729 body, err := ioutil.ReadAll(response.Body) 730 if err != nil { 731 return nil, string(body), err 732 } 733 obj, err := runtime.Decode(decoder, body) 734 return obj, string(body), err 735 } 736 737 func TestNotFound(t *testing.T) { 738 type T struct { 739 Method string 740 Path string 741 Status int 742 } 743 cases := map[string]T{ 744 // Positive checks to make sure everything is wired correctly 745 "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK}, 746 "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, 747 748 "groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, 749 750 "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, 751 "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound}, 752 "groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, 753 "groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, 754 "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, 755 "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, 756 "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, 757 "groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError}, 758 759 "groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, 760 "groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, 761 "groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound}, 762 "groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, 763 "groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, 764 "groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, 765 "groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, 766 "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, 767 "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, 768 "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError}, 769 "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, 770 "groupless namespaced watch param with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed}, 771 772 // Positive checks to make sure everything is wired correctly 773 "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, 774 // TODO: JTL: "GET root item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusOK}, 775 "GET namespaced": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, 776 // TODO: JTL: "GET namespaced item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusOK}, 777 778 "GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound}, 779 780 "root PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, 781 "root GET missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound}, 782 "root GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, 783 // TODO: JTL: "root POST with extra segment": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusMethodNotAllowed}, 784 "root DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, 785 "root DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, 786 "root PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, 787 "root PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, 788 "root watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError}, 789 // TODO: JTL: "root watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simpleroot/bar", http.StatusMethodNotAllowed}, 790 791 "namespaced PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, 792 "namespaced GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound}, 793 "namespaced GET missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound}, 794 "namespaced GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, 795 "namespaced POST with extra segment": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, 796 "namespaced DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, 797 "namespaced DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, 798 "namespaced PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, 799 "namespaced PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, 800 "namespaced watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError}, 801 "namespaced watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, 802 "namespaced watch param with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed}, 803 } 804 handler := handle(map[string]rest.Storage{ 805 "simples": &SimpleRESTStorage{}, 806 "simpleroots": &SimpleRESTStorage{}, 807 }) 808 server := httptest.NewServer(handler) 809 defer server.Close() 810 client := http.Client{} 811 for k, v := range cases { 812 request, err := http.NewRequest(v.Method, server.URL+v.Path, nil) 813 if err != nil { 814 t.Fatalf("unexpected error: %v", err) 815 } 816 817 response, err := client.Do(request) 818 if err != nil { 819 t.Errorf("unexpected error: %v", err) 820 } 821 822 if response.StatusCode != v.Status { 823 t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response) 824 } 825 } 826 } 827 828 type UnimplementedRESTStorage struct{} 829 830 func (UnimplementedRESTStorage) NamespaceScoped() bool { 831 return true 832 } 833 834 func (UnimplementedRESTStorage) New() runtime.Object { 835 return &genericapitesting.Simple{} 836 } 837 838 func (UnimplementedRESTStorage) Destroy() { 839 } 840 841 func (UnimplementedRESTStorage) GetSingularName() string { 842 return "" 843 } 844 845 // TestUnimplementedRESTStorage ensures that if a rest.Storage does not implement a given 846 // method, that it is literally not registered with the server. In the past, 847 // we registered everything, and returned method not supported if it didn't support 848 // a verb. Now we literally do not register a storage if it does not implement anything. 849 // TODO: in future, we should update proxy/redirect 850 func TestUnimplementedRESTStorage(t *testing.T) { 851 type T struct { 852 Method string 853 Path string 854 ErrCode int 855 } 856 cases := map[string]T{ 857 "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, 858 "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, 859 "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, 860 "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, 861 "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, 862 "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo", http.StatusNotFound}, 863 "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, 864 "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound}, 865 866 "GET object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, 867 "GET list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, 868 "POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, 869 "PUT object": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, 870 "DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, 871 "watch list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo", http.StatusNotFound}, 872 "watch object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, 873 "proxy object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound}, 874 } 875 handler := handle(map[string]rest.Storage{ 876 "foo": UnimplementedRESTStorage{}, 877 }) 878 server := httptest.NewServer(handler) 879 defer server.Close() 880 client := http.Client{} 881 for k, v := range cases { 882 request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`))) 883 if err != nil { 884 t.Fatalf("unexpected error: %v", err) 885 } 886 887 response, err := client.Do(request) 888 if err != nil { 889 t.Fatalf("unexpected error: %v", err) 890 } 891 defer response.Body.Close() 892 data, err := ioutil.ReadAll(response.Body) 893 if err != nil { 894 t.Fatalf("unexpected error: %v", err) 895 } 896 if response.StatusCode != v.ErrCode { 897 t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data)) 898 continue 899 } 900 } 901 } 902 903 type OnlyGetRESTStorage struct { 904 UnimplementedRESTStorage 905 } 906 907 func (OnlyGetRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { 908 return nil, nil 909 } 910 911 func (OnlyGetRESTStorage) NewList() runtime.Object { 912 return &genericapitesting.SimpleList{} 913 } 914 915 func (OnlyGetRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { 916 return nil, nil 917 } 918 919 func (OnlyGetRESTStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { 920 return nil, nil 921 } 922 923 // TestSomeUnimplementedRESTStorage ensures that if a rest.Storage does 924 // not implement a given method, that it is literally not registered 925 // with the server. We need to have at least one verb supported inorder 926 // to get a MethodNotAllowed rather than NotFound error. 927 func TestSomeUnimplementedRESTStorage(t *testing.T) { 928 type T struct { 929 Method string 930 Path string 931 ErrCode int 932 } 933 934 cases := map[string]T{ 935 "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, 936 "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, 937 "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, 938 "groupless DELETE collection": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, 939 "POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, 940 "PUT object": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, 941 "DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, 942 "DELETE collection": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, 943 } 944 handler := handle(map[string]rest.Storage{ 945 "foo": OnlyGetRESTStorage{}, 946 }) 947 server := httptest.NewServer(handler) 948 defer server.Close() 949 client := http.Client{} 950 for k, v := range cases { 951 request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`))) 952 if err != nil { 953 t.Fatalf("unexpected error: %v", err) 954 } 955 956 response, err := client.Do(request) 957 if err != nil { 958 t.Fatalf("unexpected error: %v", err) 959 } 960 defer response.Body.Close() 961 data, err := ioutil.ReadAll(response.Body) 962 if err != nil { 963 t.Fatalf("unexpected error: %v", err) 964 } 965 if response.StatusCode != v.ErrCode { 966 t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data)) 967 continue 968 } 969 } 970 } 971 972 func TestList(t *testing.T) { 973 testCases := []struct { 974 url string 975 namespace string 976 legacy bool 977 label string 978 field string 979 }{ 980 // Groupless API 981 982 // legacy namespace param is ignored 983 { 984 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=", 985 namespace: "", 986 legacy: true, 987 }, 988 { 989 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other", 990 namespace: "", 991 legacy: true, 992 }, 993 { 994 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", 995 namespace: "", 996 legacy: true, 997 label: "a=b", 998 field: "c=d", 999 }, 1000 // legacy api version is honored 1001 { 1002 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", 1003 namespace: "", 1004 legacy: true, 1005 }, 1006 { 1007 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", 1008 namespace: "other", 1009 legacy: true, 1010 }, 1011 { 1012 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", 1013 namespace: "other", 1014 legacy: true, 1015 label: "a=b", 1016 field: "c=d", 1017 }, 1018 // list items across all namespaces 1019 { 1020 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", 1021 namespace: "", 1022 legacy: true, 1023 }, 1024 // list items in a namespace in the path 1025 { 1026 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", 1027 namespace: "default", 1028 }, 1029 { 1030 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", 1031 namespace: "other", 1032 }, 1033 { 1034 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", 1035 namespace: "other", 1036 label: "a=b", 1037 field: "c=d", 1038 }, 1039 // list items across all namespaces 1040 { 1041 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", 1042 namespace: "", 1043 }, 1044 1045 // Group API 1046 1047 // legacy namespace param is ignored 1048 { 1049 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=", 1050 namespace: "", 1051 legacy: true, 1052 }, 1053 { 1054 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other", 1055 namespace: "", 1056 legacy: true, 1057 }, 1058 { 1059 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", 1060 namespace: "", 1061 legacy: true, 1062 label: "a=b", 1063 field: "c=d", 1064 }, 1065 // legacy api version is honored 1066 { 1067 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", 1068 namespace: "", 1069 legacy: true, 1070 }, 1071 { 1072 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", 1073 namespace: "other", 1074 legacy: true, 1075 }, 1076 { 1077 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", 1078 namespace: "other", 1079 legacy: true, 1080 label: "a=b", 1081 field: "c=d", 1082 }, 1083 // list items across all namespaces 1084 { 1085 url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", 1086 namespace: "", 1087 legacy: true, 1088 }, 1089 // list items in a namespace in the path 1090 { 1091 url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple", 1092 namespace: "default", 1093 }, 1094 { 1095 url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", 1096 namespace: "other", 1097 }, 1098 { 1099 url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", 1100 namespace: "other", 1101 label: "a=b", 1102 field: "c=d", 1103 }, 1104 // list items across all namespaces 1105 { 1106 url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple", 1107 namespace: "", 1108 }, 1109 } 1110 for i, testCase := range testCases { 1111 storage := map[string]rest.Storage{} 1112 simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace} 1113 storage["simple"] = &simpleStorage 1114 var handler = handleInternal(storage, admissionControl, nil) 1115 server := httptest.NewServer(handler) 1116 defer server.Close() 1117 1118 resp, err := http.Get(server.URL + testCase.url) 1119 if err != nil { 1120 t.Errorf("%d: unexpected error: %v", i, err) 1121 continue 1122 } 1123 defer resp.Body.Close() 1124 if resp.StatusCode != http.StatusOK { 1125 t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp) 1126 body, err := ioutil.ReadAll(resp.Body) 1127 if err != nil { 1128 t.Errorf("%d: unexpected error: %v", i, err) 1129 continue 1130 } 1131 t.Logf("%d: body: %s", i, string(body)) 1132 continue 1133 } 1134 if !simpleStorage.namespacePresent { 1135 t.Errorf("%d: namespace not set", i) 1136 } else if simpleStorage.actualNamespace != testCase.namespace { 1137 t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace) 1138 } 1139 if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label { 1140 t.Errorf("%d: unexpected label selector: expected=%v got=%v", i, testCase.label, simpleStorage.requestedLabelSelector) 1141 } 1142 if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field { 1143 t.Errorf("%d: unexpected field selector: expected=%v got=%v", i, testCase.field, simpleStorage.requestedFieldSelector) 1144 } 1145 } 1146 } 1147 1148 func TestRequestsWithInvalidQuery(t *testing.T) { 1149 storage := map[string]rest.Storage{} 1150 1151 storage["simple"] = &SimpleRESTStorage{expectedResourceNamespace: "default"} 1152 storage["withoptions"] = GetWithOptionsRESTStorage{} 1153 1154 var handler = handleInternal(storage, admissionControl, nil) 1155 server := httptest.NewServer(handler) 1156 defer server.Close() 1157 1158 for i, test := range []struct { 1159 postfix string 1160 method string 1161 }{ 1162 {"/simple?labelSelector=<invalid>", http.MethodGet}, 1163 {"/simple/foo?gracePeriodSeconds=<invalid>", http.MethodDelete}, 1164 // {"/simple?labelSelector=<value>", http.MethodDelete}, TODO: implement DeleteCollection in SimpleRESTStorage 1165 // {"/simple/foo?export=<invalid>", http.MethodGet}, TODO: there is no invalid bool in conversion. Should we be more strict? 1166 // {"/simple/foo?resourceVersion=<invalid>", http.MethodGet}, TODO: there is no invalid resourceVersion. Should we be more strict? 1167 // {"/withoptions?labelSelector=<invalid>", http.MethodGet}, TODO: SimpleGetOptions is always valid. Add more validation that can fail. 1168 } { 1169 baseURL := server.URL + "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default" 1170 url := baseURL + test.postfix 1171 r, err := http.NewRequest(test.method, url, nil) 1172 if err != nil { 1173 t.Errorf("%d: unexpected error: %v", i, err) 1174 continue 1175 } 1176 resp, err := http.DefaultClient.Do(r) 1177 if err != nil { 1178 t.Errorf("%d: unexpected error: %v", i, err) 1179 continue 1180 } 1181 defer resp.Body.Close() 1182 if resp.StatusCode != http.StatusBadRequest { 1183 t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, url, http.StatusBadRequest, resp) 1184 body, err := ioutil.ReadAll(resp.Body) 1185 if err != nil { 1186 t.Errorf("%d: unexpected error: %v", i, err) 1187 continue 1188 } 1189 t.Logf("%d: body: %s", i, string(body)) 1190 } 1191 } 1192 } 1193 1194 func TestListCompression(t *testing.T) { 1195 testCases := []struct { 1196 url string 1197 namespace string 1198 legacy bool 1199 label string 1200 field string 1201 acceptEncoding string 1202 }{ 1203 // list items in a namespace in the path 1204 { 1205 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", 1206 namespace: "default", 1207 acceptEncoding: "", 1208 }, 1209 { 1210 url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", 1211 namespace: "default", 1212 acceptEncoding: "gzip", 1213 }, 1214 } 1215 for i, testCase := range testCases { 1216 storage := map[string]rest.Storage{} 1217 simpleStorage := SimpleRESTStorage{ 1218 expectedResourceNamespace: testCase.namespace, 1219 list: []genericapitesting.Simple{ 1220 {Other: strings.Repeat("0123456789abcdef", (128*1024/16)+1)}, 1221 }, 1222 } 1223 storage["simple"] = &simpleStorage 1224 var handler = handleInternal(storage, admissionControl, nil) 1225 1226 handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver()) 1227 1228 server := httptest.NewServer(handler) 1229 1230 defer server.Close() 1231 1232 req, err := http.NewRequest("GET", server.URL+testCase.url, nil) 1233 if err != nil { 1234 t.Errorf("%d: unexpected error: %v", i, err) 1235 continue 1236 } 1237 // It's necessary to manually set Accept-Encoding here 1238 // to prevent http.DefaultClient from automatically 1239 // decoding responses 1240 req.Header.Set("Accept-Encoding", testCase.acceptEncoding) 1241 resp, err := http.DefaultClient.Do(req) 1242 if err != nil { 1243 t.Errorf("%d: unexpected error: %v", i, err) 1244 continue 1245 } 1246 defer resp.Body.Close() 1247 if resp.StatusCode != http.StatusOK { 1248 t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp) 1249 body, err := ioutil.ReadAll(resp.Body) 1250 if err != nil { 1251 t.Errorf("%d: unexpected error: %v", i, err) 1252 continue 1253 } 1254 t.Logf("%d: body: %s", i, string(body)) 1255 continue 1256 } 1257 if !simpleStorage.namespacePresent { 1258 t.Errorf("%d: namespace not set", i) 1259 } else if simpleStorage.actualNamespace != testCase.namespace { 1260 t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace) 1261 } 1262 if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label { 1263 t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector) 1264 } 1265 if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field { 1266 t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector) 1267 } 1268 1269 var decoder *json.Decoder 1270 if testCase.acceptEncoding == "gzip" { 1271 gzipReader, err := gzip.NewReader(resp.Body) 1272 if err != nil { 1273 t.Fatalf("unexpected error creating gzip reader: %v", err) 1274 } 1275 decoder = json.NewDecoder(gzipReader) 1276 } else { 1277 decoder = json.NewDecoder(resp.Body) 1278 } 1279 var itemOut genericapitesting.SimpleList 1280 err = decoder.Decode(&itemOut) 1281 if err != nil { 1282 t.Errorf("failed to read response body as SimpleList: %v", err) 1283 } 1284 } 1285 } 1286 1287 func TestLogs(t *testing.T) { 1288 handler := handle(map[string]rest.Storage{}) 1289 server := httptest.NewServer(handler) 1290 defer server.Close() 1291 client := http.Client{} 1292 1293 request, err := http.NewRequest("GET", server.URL+"/logs", nil) 1294 if err != nil { 1295 t.Errorf("unexpected error: %v", err) 1296 } 1297 1298 response, err := client.Do(request) 1299 if err != nil { 1300 t.Errorf("unexpected error: %v", err) 1301 } 1302 1303 body, err := ioutil.ReadAll(response.Body) 1304 if err != nil { 1305 t.Fatalf("unexpected error: %v", err) 1306 } 1307 t.Logf("Data: %s", string(body)) 1308 } 1309 1310 func TestErrorList(t *testing.T) { 1311 storage := map[string]rest.Storage{} 1312 simpleStorage := SimpleRESTStorage{ 1313 errors: map[string]error{"list": fmt.Errorf("test Error")}, 1314 } 1315 storage["simple"] = &simpleStorage 1316 handler := handle(storage) 1317 server := httptest.NewServer(handler) 1318 defer server.Close() 1319 1320 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") 1321 if err != nil { 1322 t.Fatalf("unexpected error: %v", err) 1323 } 1324 1325 if resp.StatusCode != http.StatusInternalServerError { 1326 t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusInternalServerError, resp) 1327 } 1328 } 1329 1330 func TestNonEmptyList(t *testing.T) { 1331 storage := map[string]rest.Storage{} 1332 simpleStorage := SimpleRESTStorage{ 1333 list: []genericapitesting.Simple{ 1334 { 1335 ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "other"}, 1336 Other: "foo", 1337 }, 1338 }, 1339 } 1340 storage["simple"] = &simpleStorage 1341 handler := handle(storage) 1342 server := httptest.NewServer(handler) 1343 defer server.Close() 1344 1345 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") 1346 if err != nil { 1347 t.Fatalf("unexpected error: %v", err) 1348 } 1349 1350 if resp.StatusCode != http.StatusOK { 1351 t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) 1352 body, err := ioutil.ReadAll(resp.Body) 1353 if err != nil { 1354 t.Fatalf("unexpected error: %v", err) 1355 } 1356 t.Logf("Data: %s", string(body)) 1357 } 1358 1359 var listOut genericapitesting.SimpleList 1360 body, err := extractBody(resp, &listOut) 1361 if err != nil { 1362 t.Fatalf("unexpected error: %v", err) 1363 } 1364 t.Log(body) 1365 1366 if len(listOut.Items) != 1 { 1367 t.Errorf("Unexpected response: %#v", listOut) 1368 return 1369 } 1370 if listOut.Items[0].Other != simpleStorage.list[0].Other { 1371 t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) 1372 } 1373 } 1374 1375 func TestMetadata(t *testing.T) { 1376 simpleStorage := &MetadataRESTStorage{&SimpleRESTStorage{}, []string{"text/plain"}} 1377 h := handle(map[string]rest.Storage{"simple": simpleStorage}) 1378 ws := h.(*defaultAPIServer).container.RegisteredWebServices() 1379 if len(ws) == 0 { 1380 t.Fatal("no web services registered") 1381 } 1382 matches := map[string]int{} 1383 for _, w := range ws { 1384 for _, r := range w.Routes() { 1385 t.Logf("%v %v %#v", r.Method, r.Path, r.Produces) 1386 s := strings.Join(r.Produces, ",") 1387 i := matches[s] 1388 matches[s] = i + 1 1389 } 1390 } 1391 cs := []func() bool{ 1392 func() bool { 1393 return matches["text/plain,application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 1394 }, 1395 func() bool { 1396 return matches["application/json,application/yaml,application/vnd.kubernetes.protobuf,application/json;stream=watch,application/vnd.kubernetes.protobuf;stream=watch"] == 0 1397 }, 1398 func() bool { 1399 return matches["application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 1400 }, 1401 func() bool { 1402 return len(matches) != 4 1403 }, 1404 } 1405 for i, c := range cs { 1406 if c() { 1407 t.Errorf("[%d]unexpected mime types: %#v", i, matches) 1408 } 1409 } 1410 } 1411 1412 func TestGet(t *testing.T) { 1413 storage := map[string]rest.Storage{} 1414 simpleStorage := SimpleRESTStorage{ 1415 item: genericapitesting.Simple{ 1416 Other: "foo", 1417 }, 1418 } 1419 storage["simple"] = &simpleStorage 1420 handler := handle(storage) 1421 server := httptest.NewServer(handler) 1422 defer server.Close() 1423 1424 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") 1425 if err != nil { 1426 t.Fatalf("unexpected error: %v", err) 1427 } 1428 if resp.StatusCode != http.StatusOK { 1429 t.Fatalf("unexpected response: %#v", resp) 1430 } 1431 var itemOut genericapitesting.Simple 1432 body, err := extractBody(resp, &itemOut) 1433 if err != nil { 1434 t.Errorf("unexpected error: %v", err) 1435 } 1436 1437 if itemOut.Name != simpleStorage.item.Name { 1438 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) 1439 } 1440 } 1441 1442 func BenchmarkGet(b *testing.B) { 1443 storage := map[string]rest.Storage{} 1444 simpleStorage := SimpleRESTStorage{ 1445 item: genericapitesting.Simple{ 1446 Other: "foo", 1447 }, 1448 } 1449 storage["simple"] = &simpleStorage 1450 handler := handle(storage) 1451 server := httptest.NewServer(handler) 1452 defer server.Close() 1453 1454 u := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id" 1455 1456 b.ResetTimer() 1457 for i := 0; i < b.N; i++ { 1458 resp, err := http.Get(u) 1459 if err != nil { 1460 b.Fatalf("unexpected error: %v", err) 1461 } 1462 if resp.StatusCode != http.StatusOK { 1463 b.Fatalf("unexpected response: %#v", resp) 1464 } 1465 if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil { 1466 b.Fatalf("unable to read body") 1467 } 1468 } 1469 b.StopTimer() 1470 } 1471 1472 func BenchmarkGetNoCompression(b *testing.B) { 1473 storage := map[string]rest.Storage{} 1474 simpleStorage := SimpleRESTStorage{ 1475 item: genericapitesting.Simple{ 1476 Other: "foo", 1477 }, 1478 } 1479 storage["simple"] = &simpleStorage 1480 handler := handle(storage) 1481 server := httptest.NewServer(handler) 1482 defer server.Close() 1483 1484 client := &http.Client{ 1485 Transport: &http.Transport{ 1486 DisableCompression: true, 1487 }, 1488 } 1489 1490 u := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id" 1491 1492 b.ResetTimer() 1493 for i := 0; i < b.N; i++ { 1494 resp, err := client.Get(u) 1495 if err != nil { 1496 b.Fatalf("unexpected error: %v", err) 1497 } 1498 if resp.StatusCode != http.StatusOK { 1499 b.Fatalf("unexpected response: %#v", resp) 1500 } 1501 if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil { 1502 b.Fatalf("unable to read body") 1503 } 1504 } 1505 b.StopTimer() 1506 } 1507 1508 func TestGetCompression(t *testing.T) { 1509 storage := map[string]rest.Storage{} 1510 simpleStorage := SimpleRESTStorage{ 1511 item: genericapitesting.Simple{ 1512 Other: strings.Repeat("0123456789abcdef", (128*1024/16)+1), 1513 }, 1514 } 1515 1516 storage["simple"] = &simpleStorage 1517 handler := handle(storage) 1518 handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver()) 1519 server := httptest.NewServer(handler) 1520 defer server.Close() 1521 1522 tests := []struct { 1523 acceptEncoding string 1524 }{ 1525 {acceptEncoding: ""}, 1526 {acceptEncoding: "gzip"}, 1527 } 1528 1529 for _, test := range tests { 1530 req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", nil) 1531 if err != nil { 1532 t.Fatalf("unexpected error creating request: %v", err) 1533 } 1534 // It's necessary to manually set Accept-Encoding here 1535 // to prevent http.DefaultClient from automatically 1536 // decoding responses 1537 req.Header.Set("Accept-Encoding", test.acceptEncoding) 1538 resp, err := http.DefaultClient.Do(req) 1539 if err != nil { 1540 t.Fatalf("unexpected error: %v", err) 1541 } 1542 if resp.StatusCode != http.StatusOK { 1543 t.Fatalf("unexpected response: %#v", resp) 1544 } 1545 var decoder *json.Decoder 1546 if test.acceptEncoding == "gzip" { 1547 gzipReader, err := gzip.NewReader(resp.Body) 1548 if err != nil { 1549 t.Fatalf("unexpected error creating gzip reader: %v", err) 1550 } 1551 decoder = json.NewDecoder(gzipReader) 1552 } else { 1553 decoder = json.NewDecoder(resp.Body) 1554 } 1555 var itemOut genericapitesting.Simple 1556 err = decoder.Decode(&itemOut) 1557 if err != nil { 1558 t.Errorf("unexpected error: %v", err) 1559 } 1560 body, err := ioutil.ReadAll(resp.Body) 1561 if err != nil { 1562 t.Errorf("unexpected error reading body: %v", err) 1563 } 1564 1565 if itemOut.Name != simpleStorage.item.Name { 1566 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) 1567 } 1568 } 1569 } 1570 1571 func TestGetPretty(t *testing.T) { 1572 storage := map[string]rest.Storage{} 1573 simpleStorage := SimpleRESTStorage{ 1574 item: genericapitesting.Simple{ 1575 Other: "foo", 1576 }, 1577 } 1578 storage["simple"] = &simpleStorage 1579 handler := handle(storage) 1580 server := httptest.NewServer(handler) 1581 defer server.Close() 1582 1583 tests := []struct { 1584 accept string 1585 userAgent string 1586 params url.Values 1587 pretty bool 1588 }{ 1589 {accept: runtime.ContentTypeJSON}, 1590 {accept: "application/json;pretty=0"}, 1591 {accept: runtime.ContentTypeJSON, userAgent: "kubectl"}, 1592 {accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"0"}}}, 1593 1594 {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "curl"}, 1595 {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Mozilla/5.0"}, 1596 {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Wget"}, 1597 {pretty: true, accept: "application/json;pretty=1"}, 1598 {pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"1"}}}, 1599 {pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"true"}}}, 1600 } 1601 for i, test := range tests { 1602 u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") 1603 if err != nil { 1604 t.Fatal(err) 1605 } 1606 u.RawQuery = test.params.Encode() 1607 req := &http.Request{Method: "GET", URL: u} 1608 req.Header = http.Header{} 1609 req.Header.Set("Accept", test.accept) 1610 req.Header.Set("User-Agent", test.userAgent) 1611 resp, err := http.DefaultClient.Do(req) 1612 if err != nil { 1613 t.Fatal(err) 1614 } 1615 if resp.StatusCode != http.StatusOK { 1616 t.Fatal(err) 1617 } 1618 var itemOut genericapitesting.Simple 1619 body, err := extractBody(resp, &itemOut) 1620 if err != nil { 1621 t.Fatal(err) 1622 } 1623 // to get stable ordering we need to use a go type 1624 unstructured := genericapitesting.Simple{} 1625 if err := json.Unmarshal([]byte(body), &unstructured); err != nil { 1626 t.Fatal(err) 1627 } 1628 var expect string 1629 if test.pretty { 1630 out, err := json.MarshalIndent(unstructured, "", " ") 1631 if err != nil { 1632 t.Fatal(err) 1633 } 1634 expect = string(out) 1635 } else { 1636 out, err := json.Marshal(unstructured) 1637 if err != nil { 1638 t.Fatal(err) 1639 } 1640 expect = string(out) + "\n" 1641 } 1642 if expect != body { 1643 t.Errorf("%d: body did not match expected:\n%s\n%s", i, body, expect) 1644 } 1645 } 1646 } 1647 1648 func TestGetTable(t *testing.T) { 1649 now := metav1.Now() 1650 obj := genericapitesting.Simple{ 1651 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", CreationTimestamp: now, UID: types.UID("abcdef0123")}, 1652 Other: "foo", 1653 } 1654 1655 m, err := meta.Accessor(&obj) 1656 if err != nil { 1657 t.Fatal(err) 1658 } 1659 var encodedV1Beta1Body []byte 1660 { 1661 partial := meta.AsPartialObjectMetadata(m) 1662 partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) 1663 encodedBody, err := runtime.Encode(metainternalversionscheme.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial) 1664 if err != nil { 1665 t.Fatal(err) 1666 } 1667 // the codec includes a trailing newline that is not present during decode 1668 encodedV1Beta1Body = bytes.TrimSpace(encodedBody) 1669 } 1670 var encodedV1Body []byte 1671 { 1672 partial := meta.AsPartialObjectMetadata(m) 1673 partial.GetObjectKind().SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) 1674 encodedBody, err := runtime.Encode(metainternalversionscheme.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial) 1675 if err != nil { 1676 t.Fatal(err) 1677 } 1678 // the codec includes a trailing newline that is not present during decode 1679 encodedV1Body = bytes.TrimSpace(encodedBody) 1680 } 1681 1682 metaDoc := metav1.ObjectMeta{}.SwaggerDoc() 1683 1684 tests := []struct { 1685 accept string 1686 params url.Values 1687 pretty bool 1688 expected *metav1.Table 1689 statusCode int 1690 item bool 1691 }{ 1692 { 1693 accept: "application/json;as=Table;v=v1alpha1;g=meta.k8s.io", 1694 statusCode: http.StatusNotAcceptable, 1695 }, 1696 { 1697 accept: runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io", 1698 statusCode: http.StatusNotAcceptable, 1699 }, 1700 { 1701 accept: runtime.ContentTypeProtobuf + ";as=Table;v=v1;g=meta.k8s.io", 1702 statusCode: http.StatusNotAcceptable, 1703 }, 1704 1705 { 1706 item: true, 1707 accept: "application/json;as=Table;v=v1;g=meta.k8s.io", 1708 expected: &metav1.Table{ 1709 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"}, 1710 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1711 ColumnDefinitions: []metav1.TableColumnDefinition{ 1712 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1713 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1714 }, 1715 Rows: []metav1.TableRow{ 1716 {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Body}}, 1717 }, 1718 }, 1719 }, 1720 { 1721 item: true, 1722 accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", 1723 expected: &metav1.Table{ 1724 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1725 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1726 ColumnDefinitions: []metav1.TableColumnDefinition{ 1727 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1728 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1729 }, 1730 Rows: []metav1.TableRow{ 1731 {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, 1732 }, 1733 }, 1734 }, 1735 { 1736 item: true, 1737 accept: strings.Join([]string{ 1738 runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io", 1739 "application/json;as=Table;v=v1beta1;g=meta.k8s.io", 1740 }, ","), 1741 expected: &metav1.Table{ 1742 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1743 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1744 ColumnDefinitions: []metav1.TableColumnDefinition{ 1745 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1746 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1747 }, 1748 Rows: []metav1.TableRow{ 1749 {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, 1750 }, 1751 }, 1752 }, 1753 { 1754 item: true, 1755 accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", 1756 params: url.Values{"includeObject": []string{"Metadata"}}, 1757 expected: &metav1.Table{ 1758 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1759 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1760 ColumnDefinitions: []metav1.TableColumnDefinition{ 1761 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1762 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1763 }, 1764 Rows: []metav1.TableRow{ 1765 {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, 1766 }, 1767 }, 1768 }, 1769 { 1770 accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", 1771 params: url.Values{"includeObject": []string{"Metadata"}}, 1772 expected: &metav1.Table{ 1773 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1774 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1775 ColumnDefinitions: []metav1.TableColumnDefinition{ 1776 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1777 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1778 }, 1779 Rows: []metav1.TableRow{ 1780 {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, 1781 }, 1782 }, 1783 }, 1784 } 1785 for i, test := range tests { 1786 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 1787 storage := map[string]rest.Storage{} 1788 simpleStorage := SimpleRESTStorage{ 1789 item: obj, 1790 list: []genericapitesting.Simple{obj}, 1791 } 1792 storage["simple"] = &simpleStorage 1793 handler := handle(storage) 1794 server := httptest.NewServer(handler) 1795 defer server.Close() 1796 1797 var id string 1798 if test.item { 1799 id = "/id" 1800 } 1801 u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple" + id) 1802 if err != nil { 1803 t.Fatal(err) 1804 } 1805 u.RawQuery = test.params.Encode() 1806 req := &http.Request{Method: "GET", URL: u} 1807 req.Header = http.Header{} 1808 req.Header.Set("Accept", test.accept) 1809 resp, err := http.DefaultClient.Do(req) 1810 if err != nil { 1811 t.Fatal(err) 1812 } 1813 if test.statusCode != 0 { 1814 if resp.StatusCode != test.statusCode { 1815 t.Errorf("%d: unexpected response: %#v", i, resp) 1816 } 1817 obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) 1818 if err != nil { 1819 t.Fatalf("%d: unexpected body read error: %v", i, err) 1820 } 1821 gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} 1822 if obj.GetObjectKind().GroupVersionKind() != gvk { 1823 t.Fatalf("%d: unexpected error body: %#v", i, obj) 1824 } 1825 return 1826 } 1827 if resp.StatusCode != http.StatusOK { 1828 t.Errorf("%d: unexpected response: %#v", i, resp) 1829 } 1830 var itemOut metav1.Table 1831 body, err := extractBody(resp, &itemOut) 1832 if err != nil { 1833 t.Fatal(err) 1834 } 1835 if !reflect.DeepEqual(test.expected, &itemOut) { 1836 t.Log(body) 1837 t.Errorf("%d: did not match: %s", i, cmp.Diff(test.expected, &itemOut)) 1838 } 1839 }) 1840 } 1841 } 1842 1843 func TestWatchTable(t *testing.T) { 1844 obj := genericapitesting.Simple{ 1845 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", CreationTimestamp: metav1.NewTime(time.Unix(1, 0)), UID: types.UID("abcdef0123")}, 1846 Other: "foo", 1847 } 1848 1849 m, err := meta.Accessor(&obj) 1850 if err != nil { 1851 t.Fatal(err) 1852 } 1853 partial := meta.AsPartialObjectMetadata(m) 1854 partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) 1855 encodedBody, err := runtime.Encode(metainternalversionscheme.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial) 1856 if err != nil { 1857 t.Fatal(err) 1858 } 1859 // the codec includes a trailing newline that is not present during decode 1860 encodedBody = bytes.TrimSpace(encodedBody) 1861 1862 encodedBodyV1, err := runtime.Encode(metainternalversionscheme.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial) 1863 if err != nil { 1864 t.Fatal(err) 1865 } 1866 // the codec includes a trailing newline that is not present during decode 1867 encodedBodyV1 = bytes.TrimSpace(encodedBodyV1) 1868 1869 metaDoc := metav1.ObjectMeta{}.SwaggerDoc() 1870 1871 s := metainternalversionscheme.Codecs.SupportedMediaTypes()[0].Serializer 1872 1873 tests := []struct { 1874 accept string 1875 params url.Values 1876 send func(w *watch.FakeWatcher) 1877 1878 expected []*metav1.WatchEvent 1879 contentType string 1880 statusCode int 1881 item bool 1882 }{ 1883 { 1884 accept: "application/json;as=Table;v=v1alpha1;g=meta.k8s.io", 1885 statusCode: http.StatusNotAcceptable, 1886 }, 1887 { 1888 accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", 1889 send: func(w *watch.FakeWatcher) { 1890 w.Add(&obj) 1891 }, 1892 expected: []*metav1.WatchEvent{ 1893 { 1894 Type: "ADDED", 1895 Object: runtime.RawExtension{ 1896 Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ 1897 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1898 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1899 ColumnDefinitions: []metav1.TableColumnDefinition{ 1900 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1901 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1902 }, 1903 Rows: []metav1.TableRow{ 1904 {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, 1905 }, 1906 }))), 1907 }, 1908 }, 1909 }, 1910 }, 1911 { 1912 accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", 1913 send: func(w *watch.FakeWatcher) { 1914 w.Add(&obj) 1915 w.Modify(&obj) 1916 }, 1917 expected: []*metav1.WatchEvent{ 1918 { 1919 Type: "ADDED", 1920 Object: runtime.RawExtension{ 1921 Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ 1922 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1923 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1924 ColumnDefinitions: []metav1.TableColumnDefinition{ 1925 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1926 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1927 }, 1928 Rows: []metav1.TableRow{ 1929 {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, 1930 }, 1931 }))), 1932 }, 1933 }, 1934 { 1935 Type: "MODIFIED", 1936 Object: runtime.RawExtension{ 1937 Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ 1938 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, 1939 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1940 Rows: []metav1.TableRow{ 1941 {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, 1942 }, 1943 }))), 1944 }, 1945 }, 1946 }, 1947 }, 1948 { 1949 accept: "application/json;as=Table;v=v1;g=meta.k8s.io", 1950 send: func(w *watch.FakeWatcher) { 1951 w.Add(&obj) 1952 w.Modify(&obj) 1953 }, 1954 expected: []*metav1.WatchEvent{ 1955 { 1956 Type: "ADDED", 1957 Object: runtime.RawExtension{ 1958 Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ 1959 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"}, 1960 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1961 ColumnDefinitions: []metav1.TableColumnDefinition{ 1962 {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, 1963 {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, 1964 }, 1965 Rows: []metav1.TableRow{ 1966 {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}}, 1967 }, 1968 }))), 1969 }, 1970 }, 1971 { 1972 Type: "MODIFIED", 1973 Object: runtime.RawExtension{ 1974 Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ 1975 TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"}, 1976 ListMeta: metav1.ListMeta{ResourceVersion: "10"}, 1977 Rows: []metav1.TableRow{ 1978 {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}}, 1979 }, 1980 }))), 1981 }, 1982 }, 1983 }, 1984 }, 1985 } 1986 for i, test := range tests { 1987 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 1988 storage := map[string]rest.Storage{} 1989 simpleStorage := SimpleRESTStorage{ 1990 item: obj, 1991 list: []genericapitesting.Simple{obj}, 1992 } 1993 1994 storage["simple"] = &simpleStorage 1995 handler := handle(storage) 1996 server := httptest.NewServer(handler) 1997 defer server.Close() 1998 1999 var id string 2000 if test.item { 2001 id = "/id" 2002 } 2003 u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple") 2004 if err != nil { 2005 t.Fatal(err) 2006 } 2007 if test.params == nil { 2008 test.params = url.Values{} 2009 } 2010 if test.item { 2011 test.params["fieldSelector"] = []string{fmt.Sprintf("metadata.name=%s", id)} 2012 } 2013 test.params["watch"] = []string{"1"} 2014 2015 u.RawQuery = test.params.Encode() 2016 req := &http.Request{Method: "GET", URL: u} 2017 req.Header = http.Header{} 2018 req.Header.Set("Accept", test.accept) 2019 resp, err := http.DefaultClient.Do(req) 2020 if err != nil { 2021 t.Fatal(err) 2022 } 2023 defer resp.Body.Close() 2024 if test.statusCode != 0 { 2025 if resp.StatusCode != test.statusCode { 2026 t.Fatalf("%d: unexpected response: %#v", i, resp) 2027 } 2028 obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) 2029 if err != nil { 2030 t.Fatalf("%d: unexpected body read error: %v", i, err) 2031 } 2032 gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} 2033 if obj.GetObjectKind().GroupVersionKind() != gvk { 2034 t.Fatalf("%d: unexpected error body: %#v", i, obj) 2035 } 2036 return 2037 } 2038 if resp.StatusCode != http.StatusOK { 2039 t.Fatalf("%d: unexpected response: %#v", i, resp) 2040 } 2041 2042 go func() { 2043 defer simpleStorage.fakeWatch.Stop() 2044 test.send(simpleStorage.fakeWatch) 2045 }() 2046 2047 body, err := ioutil.ReadAll(resp.Body) 2048 if err != nil { 2049 t.Fatal(err) 2050 } 2051 t.Logf("Body:\n%s", string(body)) 2052 d := watcher(resp.Header.Get("Content-Type"), ioutil.NopCloser(bytes.NewReader(body))) 2053 var actual []*metav1.WatchEvent 2054 for { 2055 var event metav1.WatchEvent 2056 _, _, err := d.Decode(nil, &event) 2057 if err == io.EOF { 2058 break 2059 } 2060 if err != nil { 2061 t.Fatal(err) 2062 } 2063 actual = append(actual, &event) 2064 } 2065 if !reflect.DeepEqual(test.expected, actual) { 2066 t.Fatalf("unexpected: %s", cmp.Diff(test.expected, actual)) 2067 } 2068 }) 2069 } 2070 } 2071 2072 func watcher(mediaType string, r io.ReadCloser) streaming.Decoder { 2073 info, ok := runtime.SerializerInfoForMediaType(metainternalversionscheme.Codecs.SupportedMediaTypes(), mediaType) 2074 if !ok || info.StreamSerializer == nil { 2075 panic(info) 2076 } 2077 streamSerializer := info.StreamSerializer 2078 fr := streamSerializer.Framer.NewFrameReader(r) 2079 d := streaming.NewDecoder(fr, streamSerializer.Serializer) 2080 return d 2081 } 2082 2083 func TestGetPartialObjectMetadata(t *testing.T) { 2084 now := metav1.Time{Time: metav1.Now().Rfc3339Copy().Local()} 2085 storage := map[string]rest.Storage{} 2086 simpleStorage := SimpleRESTStorage{ 2087 item: genericapitesting.Simple{ 2088 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, 2089 Other: "foo", 2090 }, 2091 list: []genericapitesting.Simple{ 2092 { 2093 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("newer")}, 2094 Other: "foo", 2095 }, 2096 { 2097 ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", CreationTimestamp: now, UID: types.UID("older")}, 2098 Other: "bar", 2099 }, 2100 }, 2101 } 2102 storage["simple"] = &simpleStorage 2103 handler := handle(storage) 2104 server := httptest.NewServer(handler) 2105 defer server.Close() 2106 2107 tests := []struct { 2108 accept string 2109 params url.Values 2110 pretty bool 2111 list bool 2112 expected runtime.Object 2113 expectKind schema.GroupVersionKind 2114 statusCode int 2115 }{ 2116 { 2117 accept: "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io", 2118 statusCode: http.StatusNotAcceptable, 2119 }, 2120 { 2121 accept: "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json", 2122 expectKind: schema.GroupVersionKind{Kind: "Simple", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, 2123 }, 2124 { 2125 accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json", 2126 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, 2127 }, 2128 { 2129 list: true, 2130 accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", 2131 statusCode: http.StatusNotAcceptable, 2132 }, 2133 2134 // verify preferred version overrides supported version 2135 { 2136 accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", 2137 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, 2138 }, 2139 { 2140 accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json", 2141 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"}, 2142 }, 2143 { 2144 accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io", 2145 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, 2146 }, 2147 { 2148 accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", 2149 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"}, 2150 }, 2151 2152 { 2153 list: true, 2154 accept: "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json", 2155 expectKind: schema.GroupVersionKind{Kind: "SimpleList", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, 2156 }, 2157 { 2158 list: true, 2159 accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io, application/json", 2160 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"}, 2161 }, 2162 { 2163 accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", 2164 statusCode: http.StatusNotAcceptable, 2165 }, 2166 { 2167 accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", 2168 expected: &metav1beta1.PartialObjectMetadata{ 2169 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, 2170 }, 2171 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, 2172 }, 2173 { 2174 accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io", 2175 expected: &metav1.PartialObjectMetadata{ 2176 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, 2177 }, 2178 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"}, 2179 }, 2180 { 2181 list: true, 2182 accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", 2183 expected: &metav1beta1.PartialObjectMetadataList{ 2184 ListMeta: metav1.ListMeta{ 2185 ResourceVersion: "10", 2186 }, 2187 Items: []metav1beta1.PartialObjectMetadata{ 2188 { 2189 TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"}, 2190 ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("newer")}, 2191 }, 2192 { 2193 TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"}, 2194 ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", CreationTimestamp: now, UID: types.UID("older")}, 2195 }, 2196 }, 2197 }, 2198 expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"}, 2199 }, 2200 } 2201 for i, test := range tests { 2202 suffix := "/namespaces/default/simple/id" 2203 if test.list { 2204 suffix = "/namespaces/default/simple" 2205 } 2206 u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + suffix) 2207 if err != nil { 2208 t.Fatal(err) 2209 } 2210 u.RawQuery = test.params.Encode() 2211 req := &http.Request{Method: "GET", URL: u} 2212 req.Header = http.Header{} 2213 req.Header.Set("Accept", test.accept) 2214 resp, err := http.DefaultClient.Do(req) 2215 if err != nil { 2216 t.Fatal(err) 2217 } 2218 if test.statusCode != 0 { 2219 if resp.StatusCode != test.statusCode { 2220 t.Errorf("%d: unexpected response: %#v", i, resp) 2221 } 2222 obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) 2223 if err != nil { 2224 t.Errorf("%d: unexpected body read error: %v", i, err) 2225 continue 2226 } 2227 gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} 2228 if obj.GetObjectKind().GroupVersionKind() != gvk { 2229 t.Errorf("%d: unexpected error body: %#v", i, obj) 2230 } 2231 continue 2232 } 2233 if resp.StatusCode != http.StatusOK { 2234 t.Errorf("%d: invalid status: %#v\n%s", i, resp, bodyOrDie(resp)) 2235 continue 2236 } 2237 body := "" 2238 if test.expected != nil { 2239 itemOut, d, err := extractBodyObject(resp, metainternalversionscheme.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion)) 2240 if err != nil { 2241 t.Fatal(err) 2242 } 2243 if !reflect.DeepEqual(test.expected, itemOut) { 2244 t.Errorf("%d: did not match: %s", i, cmp.Diff(test.expected, itemOut)) 2245 } 2246 body = d 2247 } else { 2248 d, err := ioutil.ReadAll(resp.Body) 2249 if err != nil { 2250 t.Fatal(err) 2251 } 2252 body = string(d) 2253 } 2254 obj := &unstructured.Unstructured{} 2255 if err := json.Unmarshal([]byte(body), obj); err != nil { 2256 t.Fatal(err) 2257 } 2258 if obj.GetObjectKind().GroupVersionKind() != test.expectKind { 2259 t.Errorf("%d: unexpected kind: %#v", i, obj.GetObjectKind().GroupVersionKind()) 2260 } 2261 } 2262 } 2263 2264 func TestGetBinary(t *testing.T) { 2265 simpleStorage := SimpleRESTStorage{ 2266 stream: &SimpleStream{ 2267 contentType: "text/plain", 2268 Reader: bytes.NewBufferString("response data"), 2269 }, 2270 } 2271 stream := simpleStorage.stream 2272 server := httptest.NewServer(handle(map[string]rest.Storage{"simple": &simpleStorage})) 2273 defer server.Close() 2274 2275 req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/binary", nil) 2276 if err != nil { 2277 t.Fatalf("unexpected error: %v", err) 2278 } 2279 req.Header.Add("Accept", "text/other, */*") 2280 resp, err := http.DefaultClient.Do(req) 2281 if err != nil { 2282 t.Fatalf("unexpected error: %v", err) 2283 } 2284 if resp.StatusCode != http.StatusOK { 2285 t.Fatalf("unexpected response: %#v", resp) 2286 } 2287 body, err := ioutil.ReadAll(resp.Body) 2288 if err != nil { 2289 t.Errorf("unexpected error: %v", err) 2290 } 2291 if !stream.closed || stream.version != testGroupVersion.String() || stream.accept != "text/other, */*" || 2292 resp.Header.Get("Content-Type") != stream.contentType || string(body) != "response data" { 2293 t.Errorf("unexpected stream: %#v", stream) 2294 } 2295 } 2296 2297 func validateSimpleGetOptionsParams(t *testing.T, route *restful.Route) { 2298 // Validate name and description 2299 expectedParams := map[string]string{ 2300 "param1": "description for param1", 2301 "param2": "description for param2", 2302 "atAPath": "", 2303 } 2304 for _, p := range route.ParameterDocs { 2305 data := p.Data() 2306 if desc, exists := expectedParams[data.Name]; exists { 2307 if desc != data.Description { 2308 t.Errorf("unexpected description for parameter %s: %s\n", data.Name, data.Description) 2309 } 2310 delete(expectedParams, data.Name) 2311 } 2312 } 2313 if len(expectedParams) > 0 { 2314 t.Errorf("did not find all expected parameters: %#v", expectedParams) 2315 } 2316 } 2317 2318 func TestGetWithOptionsRouteParams(t *testing.T) { 2319 storage := map[string]rest.Storage{} 2320 simpleStorage := GetWithOptionsRESTStorage{ 2321 SimpleRESTStorage: &SimpleRESTStorage{}, 2322 } 2323 storage["simple"] = &simpleStorage 2324 handler := handle(storage) 2325 ws := handler.(*defaultAPIServer).container.RegisteredWebServices() 2326 if len(ws) == 0 { 2327 t.Fatal("no web services registered") 2328 } 2329 routes := ws[0].Routes() 2330 for i := range routes { 2331 if routes[i].Method == "GET" && routes[i].Operation == "readNamespacedSimple" { 2332 validateSimpleGetOptionsParams(t, &routes[i]) 2333 break 2334 } 2335 } 2336 } 2337 2338 func TestGetWithOptions(t *testing.T) { 2339 2340 tests := []struct { 2341 name string 2342 rootScoped bool 2343 requestURL string 2344 expectedPath string 2345 }{ 2346 { 2347 name: "basic", 2348 requestURL: "/namespaces/default/simple/id?param1=test1¶m2=test2", 2349 expectedPath: "", 2350 }, 2351 { 2352 name: "with root slash", 2353 requestURL: "/namespaces/default/simple/id/?param1=test1¶m2=test2", 2354 expectedPath: "/", 2355 }, 2356 { 2357 name: "with path", 2358 requestURL: "/namespaces/default/simple/id/a/different/path?param1=test1¶m2=test2", 2359 expectedPath: "/a/different/path", 2360 }, 2361 { 2362 name: "with path with trailing slash", 2363 requestURL: "/namespaces/default/simple/id/a/different/path/?param1=test1¶m2=test2", 2364 expectedPath: "/a/different/path/", 2365 }, 2366 { 2367 name: "as subresource", 2368 requestURL: "/namespaces/default/simple/id/subresource/another/different/path?param1=test1¶m2=test2", 2369 expectedPath: "/another/different/path", 2370 }, 2371 { 2372 name: "cluster-scoped basic", 2373 rootScoped: true, 2374 requestURL: "/simple/id?param1=test1¶m2=test2", 2375 expectedPath: "", 2376 }, 2377 { 2378 name: "cluster-scoped basic with path", 2379 rootScoped: true, 2380 requestURL: "/simple/id/a/cluster/path?param1=test1¶m2=test2", 2381 expectedPath: "/a/cluster/path", 2382 }, 2383 { 2384 name: "cluster-scoped basic as subresource", 2385 rootScoped: true, 2386 requestURL: "/simple/id/subresource/another/cluster/path?param1=test1¶m2=test2", 2387 expectedPath: "/another/cluster/path", 2388 }, 2389 } 2390 2391 for _, test := range tests { 2392 simpleStorage := GetWithOptionsRESTStorage{ 2393 SimpleRESTStorage: &SimpleRESTStorage{ 2394 item: genericapitesting.Simple{ 2395 Other: "foo", 2396 }, 2397 }, 2398 takesPath: "atAPath", 2399 } 2400 simpleRootStorage := GetWithOptionsRootRESTStorage{ 2401 SimpleTypedStorage: &SimpleTypedStorage{ 2402 baseType: &genericapitesting.SimpleRoot{}, // a root scoped type 2403 item: &genericapitesting.SimpleRoot{ 2404 Other: "foo", 2405 }, 2406 }, 2407 takesPath: "atAPath", 2408 } 2409 2410 storage := map[string]rest.Storage{} 2411 if test.rootScoped { 2412 storage["simple"] = &simpleRootStorage 2413 storage["simple/subresource"] = &simpleRootStorage 2414 } else { 2415 storage["simple"] = &simpleStorage 2416 storage["simple/subresource"] = &simpleStorage 2417 } 2418 handler := handle(storage) 2419 server := httptest.NewServer(handler) 2420 defer server.Close() 2421 2422 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + test.requestURL) 2423 if err != nil { 2424 t.Errorf("%s: %v", test.name, err) 2425 continue 2426 } 2427 if resp.StatusCode != http.StatusOK { 2428 t.Errorf("%s: unexpected response: %#v", test.name, resp) 2429 continue 2430 } 2431 2432 var itemOut runtime.Object 2433 if test.rootScoped { 2434 itemOut = &genericapitesting.SimpleRoot{} 2435 } else { 2436 itemOut = &genericapitesting.Simple{} 2437 } 2438 body, err := extractBody(resp, itemOut) 2439 if err != nil { 2440 t.Errorf("%s: %v", test.name, err) 2441 continue 2442 } 2443 if metadata, err := meta.Accessor(itemOut); err == nil { 2444 if metadata.GetName() != simpleStorage.item.Name { 2445 t.Errorf("%s: Unexpected data: %#v, expected %#v (%s)", test.name, itemOut, simpleStorage.item, string(body)) 2446 continue 2447 } 2448 } else { 2449 t.Errorf("%s: Couldn't get name from %#v: %v", test.name, itemOut, err) 2450 } 2451 2452 var opts *genericapitesting.SimpleGetOptions 2453 var ok bool 2454 if test.rootScoped { 2455 opts, ok = simpleRootStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) 2456 } else { 2457 opts, ok = simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) 2458 2459 } 2460 if !ok { 2461 t.Errorf("%s: Unexpected options object received: %#v", test.name, simpleStorage.optionsReceived) 2462 continue 2463 } 2464 if opts.Param1 != "test1" || opts.Param2 != "test2" { 2465 t.Errorf("%s: Did not receive expected options: %#v", test.name, opts) 2466 continue 2467 } 2468 if opts.Path != test.expectedPath { 2469 t.Errorf("%s: Unexpected path value. Expected: %s. Actual: %s.", test.name, test.expectedPath, opts.Path) 2470 continue 2471 } 2472 } 2473 } 2474 2475 func TestGetMissing(t *testing.T) { 2476 storage := map[string]rest.Storage{} 2477 simpleStorage := SimpleRESTStorage{ 2478 errors: map[string]error{"get": apierrors.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")}, 2479 } 2480 storage["simple"] = &simpleStorage 2481 handler := handle(storage) 2482 server := httptest.NewServer(handler) 2483 defer server.Close() 2484 2485 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") 2486 if err != nil { 2487 t.Errorf("unexpected error: %v", err) 2488 } 2489 2490 if resp.StatusCode != http.StatusNotFound { 2491 t.Errorf("Unexpected response %#v", resp) 2492 } 2493 } 2494 2495 func TestGetRetryAfter(t *testing.T) { 2496 storage := map[string]rest.Storage{} 2497 simpleStorage := SimpleRESTStorage{ 2498 errors: map[string]error{"get": apierrors.NewServerTimeout(schema.GroupResource{Resource: "simples"}, "id", 2)}, 2499 } 2500 storage["simple"] = &simpleStorage 2501 handler := handle(storage) 2502 server := httptest.NewServer(handler) 2503 defer server.Close() 2504 2505 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") 2506 if err != nil { 2507 t.Errorf("unexpected error: %v", err) 2508 } 2509 if resp.StatusCode != http.StatusInternalServerError { 2510 t.Errorf("Unexpected response %#v", resp) 2511 } 2512 if resp.Header.Get("Retry-After") != "2" { 2513 t.Errorf("Unexpected Retry-After header: %v", resp.Header) 2514 } 2515 } 2516 2517 func TestConnect(t *testing.T) { 2518 responseText := "Hello World" 2519 itemID := "theID" 2520 connectStorage := &ConnecterRESTStorage{ 2521 connectHandler: &OutputConnect{ 2522 response: responseText, 2523 }, 2524 } 2525 storage := map[string]rest.Storage{ 2526 "simple": &SimpleRESTStorage{}, 2527 "simple/connect": connectStorage, 2528 } 2529 handler := handle(storage) 2530 server := httptest.NewServer(handler) 2531 defer server.Close() 2532 2533 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") 2534 2535 if err != nil { 2536 t.Errorf("unexpected error: %v", err) 2537 } 2538 if resp.StatusCode != http.StatusOK { 2539 t.Errorf("unexpected response: %#v", resp) 2540 } 2541 defer resp.Body.Close() 2542 body, err := ioutil.ReadAll(resp.Body) 2543 if err != nil { 2544 t.Fatalf("Unexpected error: %v", err) 2545 } 2546 if connectStorage.receivedID != itemID { 2547 t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) 2548 } 2549 if string(body) != responseText { 2550 t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) 2551 } 2552 } 2553 2554 func TestConnectResponderObject(t *testing.T) { 2555 itemID := "theID" 2556 simple := &genericapitesting.Simple{Other: "foo"} 2557 connectStorage := &ConnecterRESTStorage{} 2558 connectStorage.handlerFunc = func() http.Handler { 2559 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 2560 connectStorage.receivedResponder.Object(http.StatusCreated, simple) 2561 }) 2562 } 2563 storage := map[string]rest.Storage{ 2564 "simple": &SimpleRESTStorage{}, 2565 "simple/connect": connectStorage, 2566 } 2567 handler := handle(storage) 2568 server := httptest.NewServer(handler) 2569 defer server.Close() 2570 2571 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") 2572 2573 if err != nil { 2574 t.Errorf("unexpected error: %v", err) 2575 } 2576 if resp.StatusCode != http.StatusCreated { 2577 t.Errorf("unexpected response: %#v", resp) 2578 } 2579 defer resp.Body.Close() 2580 body, err := ioutil.ReadAll(resp.Body) 2581 if err != nil { 2582 t.Fatalf("Unexpected error: %v", err) 2583 } 2584 if connectStorage.receivedID != itemID { 2585 t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) 2586 } 2587 obj, err := runtime.Decode(codec, body) 2588 if err != nil { 2589 t.Fatal(err) 2590 } 2591 if !apiequality.Semantic.DeepEqual(obj, simple) { 2592 t.Errorf("Unexpected response: %#v", obj) 2593 } 2594 } 2595 2596 func TestConnectResponderError(t *testing.T) { 2597 itemID := "theID" 2598 connectStorage := &ConnecterRESTStorage{} 2599 connectStorage.handlerFunc = func() http.Handler { 2600 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 2601 connectStorage.receivedResponder.Error(apierrors.NewForbidden(schema.GroupResource{Resource: "simples"}, itemID, errors.New("you are terminated"))) 2602 }) 2603 } 2604 storage := map[string]rest.Storage{ 2605 "simple": &SimpleRESTStorage{}, 2606 "simple/connect": connectStorage, 2607 } 2608 handler := handle(storage) 2609 server := httptest.NewServer(handler) 2610 defer server.Close() 2611 2612 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") 2613 2614 if err != nil { 2615 t.Errorf("unexpected error: %v", err) 2616 } 2617 if resp.StatusCode != http.StatusForbidden { 2618 t.Errorf("unexpected response: %#v", resp) 2619 } 2620 defer resp.Body.Close() 2621 body, err := ioutil.ReadAll(resp.Body) 2622 if err != nil { 2623 t.Fatalf("Unexpected error: %v", err) 2624 } 2625 if connectStorage.receivedID != itemID { 2626 t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) 2627 } 2628 obj, err := runtime.Decode(codec, body) 2629 if err != nil { 2630 t.Fatal(err) 2631 } 2632 if obj.(*metav1.Status).Code != http.StatusForbidden { 2633 t.Errorf("Unexpected response: %#v", obj) 2634 } 2635 } 2636 2637 func TestConnectWithOptionsRouteParams(t *testing.T) { 2638 connectStorage := &ConnecterRESTStorage{ 2639 connectHandler: &OutputConnect{}, 2640 emptyConnectOptions: &genericapitesting.SimpleGetOptions{}, 2641 } 2642 storage := map[string]rest.Storage{ 2643 "simple": &SimpleRESTStorage{}, 2644 "simple/connect": connectStorage, 2645 } 2646 handler := handle(storage) 2647 ws := handler.(*defaultAPIServer).container.RegisteredWebServices() 2648 if len(ws) == 0 { 2649 t.Fatal("no web services registered") 2650 } 2651 routes := ws[0].Routes() 2652 for i := range routes { 2653 switch routes[i].Operation { 2654 case "connectGetNamespacedSimpleConnect": 2655 case "connectPostNamespacedSimpleConnect": 2656 case "connectPutNamespacedSimpleConnect": 2657 case "connectDeleteNamespacedSimpleConnect": 2658 validateSimpleGetOptionsParams(t, &routes[i]) 2659 2660 } 2661 } 2662 } 2663 2664 func TestConnectWithOptions(t *testing.T) { 2665 responseText := "Hello World" 2666 itemID := "theID" 2667 connectStorage := &ConnecterRESTStorage{ 2668 connectHandler: &OutputConnect{ 2669 response: responseText, 2670 }, 2671 emptyConnectOptions: &genericapitesting.SimpleGetOptions{}, 2672 } 2673 storage := map[string]rest.Storage{ 2674 "simple": &SimpleRESTStorage{}, 2675 "simple/connect": connectStorage, 2676 } 2677 handler := handle(storage) 2678 server := httptest.NewServer(handler) 2679 defer server.Close() 2680 2681 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect?param1=value1¶m2=value2") 2682 2683 if err != nil { 2684 t.Errorf("unexpected error: %v", err) 2685 } 2686 if resp.StatusCode != http.StatusOK { 2687 t.Errorf("unexpected response: %#v", resp) 2688 } 2689 defer resp.Body.Close() 2690 body, err := ioutil.ReadAll(resp.Body) 2691 if err != nil { 2692 t.Fatalf("Unexpected error: %v", err) 2693 } 2694 if connectStorage.receivedID != itemID { 2695 t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) 2696 } 2697 if string(body) != responseText { 2698 t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) 2699 } 2700 if connectStorage.receivedResponder == nil { 2701 t.Errorf("Unexpected responder") 2702 } 2703 opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions) 2704 if !ok { 2705 t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions) 2706 } 2707 if opts.Param1 != "value1" && opts.Param2 != "value2" { 2708 t.Errorf("Unexpected options value: %#v", opts) 2709 } 2710 } 2711 2712 func TestConnectWithOptionsAndPath(t *testing.T) { 2713 responseText := "Hello World" 2714 itemID := "theID" 2715 testPath := "/a/b/c/def" 2716 connectStorage := &ConnecterRESTStorage{ 2717 connectHandler: &OutputConnect{ 2718 response: responseText, 2719 }, 2720 emptyConnectOptions: &genericapitesting.SimpleGetOptions{}, 2721 takesPath: "atAPath", 2722 } 2723 storage := map[string]rest.Storage{ 2724 "simple": &SimpleRESTStorage{}, 2725 "simple/connect": connectStorage, 2726 } 2727 handler := handle(storage) 2728 server := httptest.NewServer(handler) 2729 defer server.Close() 2730 2731 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect" + testPath + "?param1=value1¶m2=value2") 2732 2733 if err != nil { 2734 t.Errorf("unexpected error: %v", err) 2735 } 2736 if resp.StatusCode != http.StatusOK { 2737 t.Errorf("unexpected response: %#v", resp) 2738 } 2739 defer resp.Body.Close() 2740 body, err := ioutil.ReadAll(resp.Body) 2741 if err != nil { 2742 t.Fatalf("Unexpected error: %v", err) 2743 } 2744 if connectStorage.receivedID != itemID { 2745 t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) 2746 } 2747 if string(body) != responseText { 2748 t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) 2749 } 2750 opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions) 2751 if !ok { 2752 t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions) 2753 } 2754 if opts.Param1 != "value1" && opts.Param2 != "value2" { 2755 t.Errorf("Unexpected options value: %#v", opts) 2756 } 2757 if opts.Path != testPath { 2758 t.Errorf("Unexpected path value. Expected: %s. Actual: %s.", testPath, opts.Path) 2759 } 2760 } 2761 2762 func TestDelete(t *testing.T) { 2763 storage := map[string]rest.Storage{} 2764 simpleStorage := SimpleRESTStorage{} 2765 ID := "id" 2766 storage["simple"] = &simpleStorage 2767 handler := handle(storage) 2768 server := httptest.NewServer(handler) 2769 defer server.Close() 2770 2771 client := http.Client{} 2772 request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) 2773 if err != nil { 2774 t.Errorf("unexpected error: %v", err) 2775 } 2776 res, err := client.Do(request) 2777 if err != nil { 2778 t.Fatalf("unexpected error: %v", err) 2779 } 2780 if res.StatusCode != http.StatusOK { 2781 t.Errorf("unexpected response: %#v", res) 2782 } 2783 if simpleStorage.deleted != ID { 2784 t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) 2785 } 2786 } 2787 2788 func TestDeleteWithOptions(t *testing.T) { 2789 storage := map[string]rest.Storage{} 2790 simpleStorage := SimpleRESTStorage{} 2791 ID := "id" 2792 storage["simple"] = &simpleStorage 2793 handler := handle(storage) 2794 server := httptest.NewServer(handler) 2795 defer server.Close() 2796 2797 grace := int64(300) 2798 item := &metav1.DeleteOptions{ 2799 GracePeriodSeconds: &grace, 2800 } 2801 body, err := runtime.Encode(codec, item) 2802 if err != nil { 2803 t.Fatalf("unexpected error: %v", err) 2804 } 2805 2806 client := http.Client{} 2807 request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 2808 if err != nil { 2809 t.Errorf("unexpected error: %v", err) 2810 } 2811 res, err := client.Do(request) 2812 if err != nil { 2813 t.Fatalf("unexpected error: %v", err) 2814 } 2815 if res.StatusCode != http.StatusOK { 2816 t.Errorf("unexpected response: %s %#v", request.URL, res) 2817 s, err := ioutil.ReadAll(res.Body) 2818 if err != nil { 2819 t.Fatalf("unexpected error: %v", err) 2820 } 2821 t.Logf(string(s)) 2822 } 2823 if simpleStorage.deleted != ID { 2824 t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) 2825 } 2826 simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 2827 if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { 2828 t.Errorf("unexpected delete options: %s", cmp.Diff(simpleStorage.deleteOptions, item)) 2829 } 2830 } 2831 2832 func TestDeleteWithOptionsQuery(t *testing.T) { 2833 storage := map[string]rest.Storage{} 2834 simpleStorage := SimpleRESTStorage{} 2835 ID := "id" 2836 storage["simple"] = &simpleStorage 2837 handler := handle(storage) 2838 server := httptest.NewServer(handler) 2839 defer server.Close() 2840 2841 grace := int64(300) 2842 item := &metav1.DeleteOptions{ 2843 GracePeriodSeconds: &grace, 2844 } 2845 2846 client := http.Client{} 2847 request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?gracePeriodSeconds="+strconv.FormatInt(grace, 10), nil) 2848 if err != nil { 2849 t.Errorf("unexpected error: %v", err) 2850 } 2851 res, err := client.Do(request) 2852 if err != nil { 2853 t.Fatalf("unexpected error: %v", err) 2854 } 2855 if res.StatusCode != http.StatusOK { 2856 t.Errorf("unexpected response: %s %#v", request.URL, res) 2857 s, err := ioutil.ReadAll(res.Body) 2858 if err != nil { 2859 t.Fatalf("unexpected error: %v", err) 2860 } 2861 t.Logf(string(s)) 2862 } 2863 if simpleStorage.deleted != ID { 2864 t.Fatalf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) 2865 } 2866 simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 2867 if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { 2868 t.Errorf("unexpected delete options: %s", cmp.Diff(simpleStorage.deleteOptions, item)) 2869 } 2870 } 2871 2872 func TestDeleteWithOptionsQueryAndBody(t *testing.T) { 2873 storage := map[string]rest.Storage{} 2874 simpleStorage := SimpleRESTStorage{} 2875 ID := "id" 2876 storage["simple"] = &simpleStorage 2877 handler := handle(storage) 2878 server := httptest.NewServer(handler) 2879 defer server.Close() 2880 2881 grace := int64(300) 2882 item := &metav1.DeleteOptions{ 2883 GracePeriodSeconds: &grace, 2884 } 2885 body, err := runtime.Encode(codec, item) 2886 if err != nil { 2887 t.Fatalf("unexpected error: %v", err) 2888 } 2889 client := http.Client{} 2890 request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?gracePeriodSeconds="+strconv.FormatInt(grace+10, 10), bytes.NewReader(body)) 2891 if err != nil { 2892 t.Errorf("unexpected error: %v", err) 2893 } 2894 res, err := client.Do(request) 2895 if err != nil { 2896 t.Fatalf("unexpected error: %v", err) 2897 } 2898 if res.StatusCode != http.StatusOK { 2899 t.Errorf("unexpected response: %s %#v", request.URL, res) 2900 s, err := ioutil.ReadAll(res.Body) 2901 if err != nil { 2902 t.Fatalf("unexpected error: %v", err) 2903 } 2904 t.Logf(string(s)) 2905 } 2906 if simpleStorage.deleted != ID { 2907 t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) 2908 } 2909 simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 2910 if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { 2911 t.Errorf("unexpected delete options: %s", cmp.Diff(simpleStorage.deleteOptions, item)) 2912 } 2913 } 2914 2915 func TestDeleteInvokesAdmissionControl(t *testing.T) { 2916 // TODO: remove mutating deny when we removed it from the endpoint implementation and ported all plugins 2917 for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { 2918 t.Logf("Testing %T", admit) 2919 2920 storage := map[string]rest.Storage{} 2921 simpleStorage := SimpleRESTStorage{} 2922 ID := "id" 2923 storage["simple"] = &simpleStorage 2924 handler := handleInternal(storage, admit, nil) 2925 server := httptest.NewServer(handler) 2926 defer server.Close() 2927 2928 client := http.Client{} 2929 request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) 2930 if err != nil { 2931 t.Errorf("unexpected error: %v", err) 2932 } 2933 response, err := client.Do(request) 2934 if err != nil { 2935 t.Errorf("unexpected error: %v", err) 2936 } 2937 if response.StatusCode != http.StatusForbidden { 2938 t.Errorf("Unexpected response %#v", response) 2939 } 2940 } 2941 } 2942 2943 func TestDeleteMissing(t *testing.T) { 2944 storage := map[string]rest.Storage{} 2945 ID := "id" 2946 simpleStorage := SimpleRESTStorage{ 2947 errors: map[string]error{"delete": apierrors.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)}, 2948 } 2949 storage["simple"] = &simpleStorage 2950 handler := handle(storage) 2951 server := httptest.NewServer(handler) 2952 defer server.Close() 2953 2954 client := http.Client{} 2955 request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) 2956 if err != nil { 2957 t.Errorf("unexpected error: %v", err) 2958 } 2959 response, err := client.Do(request) 2960 if err != nil { 2961 t.Errorf("unexpected error: %v", err) 2962 } 2963 2964 if response.StatusCode != http.StatusNotFound { 2965 t.Errorf("Unexpected response %#v", response) 2966 } 2967 } 2968 2969 func TestUpdate(t *testing.T) { 2970 storage := map[string]rest.Storage{} 2971 simpleStorage := SimpleRESTStorage{} 2972 ID := "id" 2973 storage["simple"] = &simpleStorage 2974 handler := handle(storage) 2975 server := httptest.NewServer(handler) 2976 defer server.Close() 2977 2978 item := &genericapitesting.Simple{ 2979 ObjectMeta: metav1.ObjectMeta{ 2980 Name: ID, 2981 Namespace: "", // update should allow the client to send an empty namespace 2982 }, 2983 Other: "bar", 2984 } 2985 body, err := runtime.Encode(testCodec, item) 2986 if err != nil { 2987 // The following cases will fail, so die now 2988 t.Fatalf("unexpected error: %v", err) 2989 } 2990 2991 client := http.Client{} 2992 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 2993 if err != nil { 2994 t.Errorf("unexpected error: %v", err) 2995 } 2996 response, err := client.Do(request) 2997 if err != nil { 2998 t.Errorf("unexpected error: %v", err) 2999 } 3000 dump, _ := httputil.DumpResponse(response, true) 3001 t.Log(string(dump)) 3002 3003 if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name { 3004 t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item) 3005 } 3006 } 3007 3008 func TestUpdateInvokesAdmissionControl(t *testing.T) { 3009 for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { 3010 t.Logf("Testing %T", admit) 3011 3012 storage := map[string]rest.Storage{} 3013 simpleStorage := SimpleRESTStorage{} 3014 ID := "id" 3015 storage["simple"] = &simpleStorage 3016 handler := handleInternal(storage, admit, nil) 3017 server := httptest.NewServer(handler) 3018 defer server.Close() 3019 3020 item := &genericapitesting.Simple{ 3021 ObjectMeta: metav1.ObjectMeta{ 3022 Name: ID, 3023 Namespace: metav1.NamespaceDefault, 3024 }, 3025 Other: "bar", 3026 } 3027 body, err := runtime.Encode(testCodec, item) 3028 if err != nil { 3029 // The following cases will fail, so die now 3030 t.Fatalf("unexpected error: %v", err) 3031 } 3032 3033 client := http.Client{} 3034 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 3035 if err != nil { 3036 t.Errorf("unexpected error: %v", err) 3037 } 3038 response, err := client.Do(request) 3039 if err != nil { 3040 t.Errorf("unexpected error: %v", err) 3041 } 3042 dump, _ := httputil.DumpResponse(response, true) 3043 t.Log(string(dump)) 3044 3045 if response.StatusCode != http.StatusForbidden { 3046 t.Errorf("Unexpected response %#v", response) 3047 } 3048 } 3049 } 3050 3051 func TestUpdateRequiresMatchingName(t *testing.T) { 3052 storage := map[string]rest.Storage{} 3053 simpleStorage := SimpleRESTStorage{} 3054 ID := "id" 3055 storage["simple"] = &simpleStorage 3056 handler := handle(storage) 3057 server := httptest.NewServer(handler) 3058 defer server.Close() 3059 3060 item := &genericapitesting.Simple{ 3061 Other: "bar", 3062 } 3063 body, err := runtime.Encode(testCodec, item) 3064 if err != nil { 3065 // The following cases will fail, so die now 3066 t.Fatalf("unexpected error: %v", err) 3067 } 3068 3069 client := http.Client{} 3070 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 3071 if err != nil { 3072 t.Errorf("unexpected error: %v", err) 3073 } 3074 response, err := client.Do(request) 3075 if err != nil { 3076 t.Errorf("unexpected error: %v", err) 3077 } 3078 if response.StatusCode != http.StatusBadRequest { 3079 dump, _ := httputil.DumpResponse(response, true) 3080 t.Log(string(dump)) 3081 t.Errorf("Unexpected response %#v", response) 3082 } 3083 } 3084 3085 func TestUpdateAllowsMissingNamespace(t *testing.T) { 3086 storage := map[string]rest.Storage{} 3087 simpleStorage := SimpleRESTStorage{} 3088 ID := "id" 3089 storage["simple"] = &simpleStorage 3090 handler := handle(storage) 3091 server := httptest.NewServer(handler) 3092 defer server.Close() 3093 3094 item := &genericapitesting.Simple{ 3095 ObjectMeta: metav1.ObjectMeta{ 3096 Name: ID, 3097 }, 3098 Other: "bar", 3099 } 3100 body, err := runtime.Encode(testCodec, item) 3101 if err != nil { 3102 // The following cases will fail, so die now 3103 t.Fatalf("unexpected error: %v", err) 3104 } 3105 3106 client := http.Client{} 3107 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 3108 if err != nil { 3109 t.Errorf("unexpected error: %v", err) 3110 } 3111 response, err := client.Do(request) 3112 if err != nil { 3113 t.Errorf("unexpected error: %v", err) 3114 } 3115 dump, _ := httputil.DumpResponse(response, true) 3116 t.Log(string(dump)) 3117 3118 if response.StatusCode != http.StatusOK { 3119 t.Errorf("Unexpected response %#v", response) 3120 } 3121 } 3122 3123 // when the object name and namespace can't be retrieved, don't update. It isn't safe. 3124 func TestUpdateDisallowsMismatchedNamespaceOnError(t *testing.T) { 3125 storage := map[string]rest.Storage{} 3126 simpleStorage := SimpleRESTStorage{} 3127 ID := "id" 3128 storage["simple"] = &simpleStorage 3129 handler := handle(storage) 3130 server := httptest.NewServer(handler) 3131 defer server.Close() 3132 3133 item := &genericapitesting.Simple{ 3134 ObjectMeta: metav1.ObjectMeta{ 3135 Name: ID, 3136 Namespace: "other", // does not match request 3137 }, 3138 Other: "bar", 3139 } 3140 body, err := runtime.Encode(testCodec, item) 3141 if err != nil { 3142 // The following cases will fail, so die now 3143 t.Fatalf("unexpected error: %v", err) 3144 } 3145 3146 client := http.Client{} 3147 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 3148 if err != nil { 3149 t.Errorf("unexpected error: %v", err) 3150 } 3151 response, err := client.Do(request) 3152 if err != nil { 3153 t.Errorf("unexpected error: %v", err) 3154 } 3155 dump, _ := httputil.DumpResponse(response, true) 3156 t.Log(string(dump)) 3157 3158 if simpleStorage.updated != nil { 3159 t.Errorf("Unexpected update value %#v.", simpleStorage.updated) 3160 } 3161 } 3162 3163 func TestUpdatePreventsMismatchedNamespace(t *testing.T) { 3164 storage := map[string]rest.Storage{} 3165 simpleStorage := SimpleRESTStorage{} 3166 ID := "id" 3167 storage["simple"] = &simpleStorage 3168 handler := handle(storage) 3169 server := httptest.NewServer(handler) 3170 defer server.Close() 3171 3172 item := &genericapitesting.Simple{ 3173 ObjectMeta: metav1.ObjectMeta{ 3174 Name: ID, 3175 Namespace: "other", 3176 }, 3177 Other: "bar", 3178 } 3179 body, err := runtime.Encode(testCodec, item) 3180 if err != nil { 3181 // The following cases will fail, so die now 3182 t.Fatalf("unexpected error: %v", err) 3183 } 3184 3185 client := http.Client{} 3186 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 3187 if err != nil { 3188 t.Errorf("unexpected error: %v", err) 3189 } 3190 response, err := client.Do(request) 3191 if err != nil { 3192 t.Errorf("unexpected error: %v", err) 3193 } 3194 if response.StatusCode != http.StatusBadRequest { 3195 t.Errorf("Unexpected response %#v", response) 3196 } 3197 } 3198 3199 func TestUpdateMissing(t *testing.T) { 3200 storage := map[string]rest.Storage{} 3201 ID := "id" 3202 simpleStorage := SimpleRESTStorage{ 3203 errors: map[string]error{"update": apierrors.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)}, 3204 } 3205 storage["simple"] = &simpleStorage 3206 handler := handle(storage) 3207 server := httptest.NewServer(handler) 3208 defer server.Close() 3209 3210 item := &genericapitesting.Simple{ 3211 ObjectMeta: metav1.ObjectMeta{ 3212 Name: ID, 3213 Namespace: metav1.NamespaceDefault, 3214 }, 3215 Other: "bar", 3216 } 3217 body, err := runtime.Encode(testCodec, item) 3218 if err != nil { 3219 t.Errorf("unexpected error: %v", err) 3220 } 3221 3222 client := http.Client{} 3223 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) 3224 if err != nil { 3225 t.Errorf("unexpected error: %v", err) 3226 } 3227 response, err := client.Do(request) 3228 if err != nil { 3229 t.Errorf("unexpected error: %v", err) 3230 } 3231 if response.StatusCode != http.StatusNotFound { 3232 t.Errorf("Unexpected response %#v", response) 3233 } 3234 } 3235 3236 func TestCreateNotFound(t *testing.T) { 3237 handler := handle(map[string]rest.Storage{ 3238 "simple": &SimpleRESTStorage{ 3239 // storage.Create can fail with not found error in theory. 3240 // See https://pr.k8s.io/486#discussion_r15037092. 3241 errors: map[string]error{"create": apierrors.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")}, 3242 }, 3243 }) 3244 server := httptest.NewServer(handler) 3245 defer server.Close() 3246 client := http.Client{} 3247 3248 simple := &genericapitesting.Simple{Other: "foo"} 3249 data, err := runtime.Encode(testCodec, simple) 3250 if err != nil { 3251 t.Errorf("unexpected error: %v", err) 3252 } 3253 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) 3254 if err != nil { 3255 t.Errorf("unexpected error: %v", err) 3256 } 3257 3258 response, err := client.Do(request) 3259 if err != nil { 3260 t.Errorf("unexpected error: %v", err) 3261 } 3262 3263 if response.StatusCode != http.StatusNotFound { 3264 t.Errorf("Unexpected response %#v", response) 3265 } 3266 } 3267 3268 func TestCreateChecksDecode(t *testing.T) { 3269 handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) 3270 server := httptest.NewServer(handler) 3271 defer server.Close() 3272 client := http.Client{} 3273 3274 simple := &example.Pod{} 3275 data, err := runtime.Encode(testCodec, simple) 3276 if err != nil { 3277 t.Errorf("unexpected error: %v", err) 3278 } 3279 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) 3280 if err != nil { 3281 t.Errorf("unexpected error: %v", err) 3282 } 3283 response, err := client.Do(request) 3284 if err != nil { 3285 t.Errorf("unexpected error: %v", err) 3286 } 3287 if response.StatusCode != http.StatusBadRequest { 3288 t.Errorf("Unexpected response %#v", response) 3289 } 3290 b, err := ioutil.ReadAll(response.Body) 3291 if err != nil { 3292 t.Errorf("unexpected error: %v", err) 3293 } else if !strings.Contains(string(b), "cannot be handled as a Simple") { 3294 t.Errorf("unexpected response: %s", string(b)) 3295 } 3296 } 3297 3298 func TestParentResourceIsRequired(t *testing.T) { 3299 storage := &SimpleTypedStorage{ 3300 baseType: &genericapitesting.SimpleRoot{}, // a root scoped type 3301 item: &genericapitesting.SimpleRoot{}, 3302 } 3303 group := &APIGroupVersion{ 3304 Storage: map[string]rest.Storage{ 3305 "simple/sub": storage, 3306 }, 3307 Root: "/" + prefix, 3308 Creater: scheme, 3309 Convertor: scheme, 3310 UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), 3311 Defaulter: scheme, 3312 Typer: scheme, 3313 Namer: namer, 3314 RootScopedKinds: sets.NewString("SimpleRoot"), 3315 3316 EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), 3317 3318 Admit: admissionControl, 3319 3320 GroupVersion: newGroupVersion, 3321 OptionsExternalVersion: &newGroupVersion, 3322 3323 Serializer: codecs, 3324 ParameterCodec: parameterCodec, 3325 } 3326 container := restful.NewContainer() 3327 if _, _, err := group.InstallREST(container); err == nil { 3328 t.Fatal("expected error") 3329 } 3330 3331 storage = &SimpleTypedStorage{ 3332 baseType: &genericapitesting.SimpleRoot{}, // a root scoped type 3333 item: &genericapitesting.SimpleRoot{}, 3334 } 3335 group = &APIGroupVersion{ 3336 Storage: map[string]rest.Storage{ 3337 "simple": &SimpleRESTStorage{}, 3338 "simple/sub": storage, 3339 }, 3340 Root: "/" + prefix, 3341 Creater: scheme, 3342 Convertor: scheme, 3343 UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), 3344 TypeConverter: managedfields.NewDeducedTypeConverter(), 3345 Defaulter: scheme, 3346 Typer: scheme, 3347 Namer: namer, 3348 3349 EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), 3350 3351 Admit: admissionControl, 3352 3353 GroupVersion: newGroupVersion, 3354 OptionsExternalVersion: &newGroupVersion, 3355 3356 Serializer: codecs, 3357 ParameterCodec: parameterCodec, 3358 } 3359 container = restful.NewContainer() 3360 if _, _, err := group.InstallREST(container); err != nil { 3361 t.Fatal(err) 3362 } 3363 3364 handler := genericapifilters.WithRequestInfo(container, newTestRequestInfoResolver()) 3365 3366 // resource is NOT registered in the root scope 3367 w := httptest.NewRecorder() 3368 handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}}) 3369 if w.Code != http.StatusNotFound { 3370 t.Errorf("expected not found: %#v", w) 3371 } 3372 3373 // resource is registered in the namespace scope 3374 w = httptest.NewRecorder() 3375 handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/simple/test/sub"}}) 3376 if w.Code != http.StatusOK { 3377 t.Fatalf("expected OK: %#v", w) 3378 } 3379 if storage.actualNamespace != "test" { 3380 t.Errorf("namespace should be set %#v", storage) 3381 } 3382 } 3383 3384 func TestNamedCreaterWithName(t *testing.T) { 3385 pathName := "helloworld" 3386 storage := &NamedCreaterRESTStorage{SimpleRESTStorage: &SimpleRESTStorage{}} 3387 handler := handle(map[string]rest.Storage{ 3388 "simple": &SimpleRESTStorage{}, 3389 "simple/sub": storage, 3390 }) 3391 server := httptest.NewServer(handler) 3392 defer server.Close() 3393 client := http.Client{} 3394 3395 simple := &genericapitesting.Simple{Other: "foo"} 3396 data, err := runtime.Encode(testCodec, simple) 3397 if err != nil { 3398 t.Errorf("unexpected error: %v", err) 3399 } 3400 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+pathName+"/sub", bytes.NewBuffer(data)) 3401 if err != nil { 3402 t.Errorf("unexpected error: %v", err) 3403 } 3404 response, err := client.Do(request) 3405 if err != nil { 3406 t.Errorf("unexpected error: %v", err) 3407 } 3408 if response.StatusCode != http.StatusCreated { 3409 t.Errorf("Unexpected response %#v", response) 3410 } 3411 if storage.createdName != pathName { 3412 t.Errorf("Did not get expected name in create context. Got: %s, Expected: %s", storage.createdName, pathName) 3413 } 3414 } 3415 3416 func TestNamedCreaterWithoutName(t *testing.T) { 3417 storage := &NamedCreaterRESTStorage{ 3418 SimpleRESTStorage: &SimpleRESTStorage{ 3419 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3420 time.Sleep(5 * time.Millisecond) 3421 return obj, nil 3422 }, 3423 }, 3424 } 3425 3426 handler := handle(map[string]rest.Storage{"foo": storage}) 3427 server := httptest.NewServer(handler) 3428 defer server.Close() 3429 client := http.Client{} 3430 3431 simple := &genericapitesting.Simple{ 3432 Other: "bar", 3433 } 3434 data, err := runtime.Encode(testCodec, simple) 3435 if err != nil { 3436 t.Errorf("unexpected error: %v", err) 3437 } 3438 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) 3439 if err != nil { 3440 t.Errorf("unexpected error: %v", err) 3441 } 3442 3443 wg := sync.WaitGroup{} 3444 wg.Add(1) 3445 var response *http.Response 3446 go func() { 3447 response, err = client.Do(request) 3448 wg.Done() 3449 }() 3450 wg.Wait() 3451 if err != nil { 3452 t.Errorf("unexpected error: %v", err) 3453 } 3454 // empty name is not allowed for NamedCreater 3455 if response.StatusCode != http.StatusBadRequest { 3456 t.Errorf("Unexpected response %#v", response) 3457 } 3458 } 3459 3460 type namePopulatorAdmissionControl struct { 3461 t *testing.T 3462 populateName string 3463 } 3464 3465 func (npac *namePopulatorAdmissionControl) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { 3466 if a.GetName() != npac.populateName { 3467 npac.t.Errorf("Unexpected name: got %q, expected %q", a.GetName(), npac.populateName) 3468 } 3469 return nil 3470 } 3471 3472 func (npac *namePopulatorAdmissionControl) Handles(operation admission.Operation) bool { 3473 return true 3474 } 3475 3476 var _ admission.ValidationInterface = &namePopulatorAdmissionControl{} 3477 3478 func TestNamedCreaterWithGenerateName(t *testing.T) { 3479 populateName := "bar" 3480 storage := &SimpleRESTStorage{ 3481 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3482 time.Sleep(5 * time.Millisecond) 3483 if metadata, err := meta.Accessor(obj); err == nil { 3484 if len(metadata.GetName()) != 0 { 3485 t.Errorf("Unexpected name %q", metadata.GetName()) 3486 } 3487 metadata.SetName(populateName) 3488 } else { 3489 return nil, err 3490 } 3491 return obj, nil 3492 }, 3493 } 3494 3495 ac := &namePopulatorAdmissionControl{ 3496 t: t, 3497 populateName: populateName, 3498 } 3499 3500 handler := handleInternal(map[string]rest.Storage{"foo": storage}, ac, nil) 3501 server := httptest.NewServer(handler) 3502 defer server.Close() 3503 client := http.Client{} 3504 3505 simple := &genericapitesting.Simple{ 3506 Other: "bar", 3507 } 3508 data, err := runtime.Encode(testCodec, simple) 3509 if err != nil { 3510 t.Errorf("unexpected error: %v", err) 3511 } 3512 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) 3513 if err != nil { 3514 t.Errorf("unexpected error: %v", err) 3515 } 3516 3517 wg := sync.WaitGroup{} 3518 wg.Add(1) 3519 var response *http.Response 3520 go func() { 3521 response, err = client.Do(request) 3522 wg.Done() 3523 }() 3524 wg.Wait() 3525 if err != nil { 3526 t.Errorf("unexpected error: %v", err) 3527 } 3528 if response.StatusCode != http.StatusCreated { 3529 t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) 3530 } 3531 3532 var itemOut genericapitesting.Simple 3533 body, err := extractBody(response, &itemOut) 3534 if err != nil { 3535 t.Errorf("unexpected error: %v %#v", err, response) 3536 } 3537 3538 // Avoid comparing managed fields in expected result 3539 itemOut.ManagedFields = nil 3540 itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 3541 simple.Name = populateName 3542 simple.Namespace = "default" // populated by create handler to match request URL 3543 if !reflect.DeepEqual(&itemOut, simple) { 3544 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) 3545 } 3546 } 3547 3548 func TestUpdateChecksDecode(t *testing.T) { 3549 handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) 3550 server := httptest.NewServer(handler) 3551 defer server.Close() 3552 client := http.Client{} 3553 3554 simple := &example.Pod{} 3555 data, err := runtime.Encode(testCodec, simple) 3556 if err != nil { 3557 t.Errorf("unexpected error: %v", err) 3558 } 3559 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) 3560 if err != nil { 3561 t.Errorf("unexpected error: %v", err) 3562 } 3563 response, err := client.Do(request) 3564 if err != nil { 3565 t.Errorf("unexpected error: %v", err) 3566 } 3567 if response.StatusCode != http.StatusBadRequest { 3568 t.Errorf("Unexpected response %#v\n%s", response, readBodyOrDie(response.Body)) 3569 } 3570 b, err := ioutil.ReadAll(response.Body) 3571 if err != nil { 3572 t.Errorf("unexpected error: %v", err) 3573 } else if !strings.Contains(string(b), "cannot be handled as a Simple") { 3574 t.Errorf("unexpected response: %s", string(b)) 3575 } 3576 } 3577 3578 func TestCreate(t *testing.T) { 3579 storage := SimpleRESTStorage{ 3580 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3581 time.Sleep(5 * time.Millisecond) 3582 return obj, nil 3583 }, 3584 } 3585 handler := handle(map[string]rest.Storage{"foo": &storage}) 3586 server := httptest.NewServer(handler) 3587 defer server.Close() 3588 client := http.Client{} 3589 3590 simple := &genericapitesting.Simple{ 3591 Other: "bar", 3592 } 3593 data, err := runtime.Encode(testCodec, simple) 3594 if err != nil { 3595 t.Errorf("unexpected error: %v", err) 3596 } 3597 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) 3598 if err != nil { 3599 t.Errorf("unexpected error: %v", err) 3600 } 3601 3602 wg := sync.WaitGroup{} 3603 wg.Add(1) 3604 var response *http.Response 3605 go func() { 3606 response, err = client.Do(request) 3607 wg.Done() 3608 }() 3609 wg.Wait() 3610 if err != nil { 3611 t.Errorf("unexpected error: %v", err) 3612 } 3613 3614 var itemOut genericapitesting.Simple 3615 body, err := extractBody(response, &itemOut) 3616 if err != nil { 3617 t.Errorf("unexpected error: %v %#v", err, response) 3618 } 3619 3620 // Avoid comparing managed fields in expected result 3621 itemOut.ManagedFields = nil 3622 itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 3623 simple.Namespace = "default" // populated by create handler to match request URL 3624 if !reflect.DeepEqual(&itemOut, simple) { 3625 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) 3626 } 3627 if response.StatusCode != http.StatusCreated { 3628 t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) 3629 } 3630 } 3631 3632 func TestCreateYAML(t *testing.T) { 3633 storage := SimpleRESTStorage{ 3634 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3635 time.Sleep(5 * time.Millisecond) 3636 return obj, nil 3637 }, 3638 } 3639 handler := handle(map[string]rest.Storage{"foo": &storage}) 3640 server := httptest.NewServer(handler) 3641 defer server.Close() 3642 client := http.Client{} 3643 3644 // yaml encoder 3645 simple := &genericapitesting.Simple{ 3646 Other: "bar", 3647 } 3648 info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/yaml") 3649 if !ok { 3650 t.Fatal("No yaml serializer") 3651 } 3652 encoder := codecs.EncoderForVersion(info.Serializer, testGroupVersion) 3653 decoder := codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion) 3654 3655 data, err := runtime.Encode(encoder, simple) 3656 if err != nil { 3657 t.Fatalf("unexpected error: %v", err) 3658 } 3659 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) 3660 if err != nil { 3661 t.Fatalf("unexpected error: %v", err) 3662 } 3663 request.Header.Set("Accept", "application/yaml, application/json") 3664 request.Header.Set("Content-Type", "application/yaml") 3665 3666 wg := sync.WaitGroup{} 3667 wg.Add(1) 3668 var response *http.Response 3669 go func() { 3670 response, err = client.Do(request) 3671 wg.Done() 3672 }() 3673 wg.Wait() 3674 if err != nil { 3675 t.Fatalf("unexpected error: %v", err) 3676 } 3677 3678 var itemOut genericapitesting.Simple 3679 body, err := extractBodyDecoder(response, &itemOut, decoder) 3680 if err != nil { 3681 t.Fatalf("unexpected error: %v %#v", err, response) 3682 } 3683 3684 // Avoid comparing managed fields in expected result 3685 itemOut.ManagedFields = nil 3686 itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 3687 simple.Namespace = "default" // populated by create handler to match request URL 3688 if !reflect.DeepEqual(&itemOut, simple) { 3689 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) 3690 } 3691 if response.StatusCode != http.StatusCreated { 3692 t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) 3693 } 3694 } 3695 3696 func TestCreateInNamespace(t *testing.T) { 3697 storage := SimpleRESTStorage{ 3698 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3699 time.Sleep(5 * time.Millisecond) 3700 return obj, nil 3701 }, 3702 } 3703 handler := handle(map[string]rest.Storage{"foo": &storage}) 3704 server := httptest.NewServer(handler) 3705 defer server.Close() 3706 client := http.Client{} 3707 3708 simple := &genericapitesting.Simple{ 3709 Other: "bar", 3710 } 3711 data, err := runtime.Encode(testCodec, simple) 3712 if err != nil { 3713 t.Fatalf("unexpected error: %v", err) 3714 } 3715 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) 3716 if err != nil { 3717 t.Fatalf("unexpected error: %v", err) 3718 } 3719 3720 wg := sync.WaitGroup{} 3721 wg.Add(1) 3722 var response *http.Response 3723 go func() { 3724 response, err = client.Do(request) 3725 wg.Done() 3726 }() 3727 wg.Wait() 3728 if err != nil { 3729 t.Fatalf("unexpected error: %v", err) 3730 } 3731 3732 var itemOut genericapitesting.Simple 3733 body, err := extractBody(response, &itemOut) 3734 if err != nil { 3735 t.Fatalf("unexpected error: %v\n%s", err, data) 3736 } 3737 3738 // Avoid comparing managed fields in expected result 3739 itemOut.ManagedFields = nil 3740 itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) 3741 simple.Namespace = "other" // populated by create handler to match request URL 3742 if !reflect.DeepEqual(&itemOut, simple) { 3743 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) 3744 } 3745 if response.StatusCode != http.StatusCreated { 3746 t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) 3747 } 3748 } 3749 3750 func TestCreateInvokeAdmissionControl(t *testing.T) { 3751 for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { 3752 t.Logf("Testing %T", admit) 3753 3754 storage := SimpleRESTStorage{ 3755 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3756 time.Sleep(5 * time.Millisecond) 3757 return obj, nil 3758 }, 3759 } 3760 handler := handleInternal(map[string]rest.Storage{"foo": &storage}, admit, nil) 3761 server := httptest.NewServer(handler) 3762 defer server.Close() 3763 client := http.Client{} 3764 3765 simple := &genericapitesting.Simple{ 3766 Other: "bar", 3767 } 3768 data, err := runtime.Encode(testCodec, simple) 3769 if err != nil { 3770 t.Errorf("unexpected error: %v", err) 3771 } 3772 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) 3773 if err != nil { 3774 t.Errorf("unexpected error: %v", err) 3775 } 3776 3777 var response *http.Response 3778 response, err = client.Do(request) 3779 if err != nil { 3780 t.Errorf("unexpected error: %v", err) 3781 } 3782 if response.StatusCode != http.StatusForbidden { 3783 t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response) 3784 } 3785 } 3786 } 3787 3788 func expectAPIStatus(t *testing.T, method, url string, data []byte, code int) *metav1.Status { 3789 t.Helper() 3790 client := http.Client{} 3791 request, err := http.NewRequest(method, url, bytes.NewBuffer(data)) 3792 if err != nil { 3793 t.Fatalf("unexpected error %#v", err) 3794 return nil 3795 } 3796 response, err := client.Do(request) 3797 if err != nil { 3798 t.Fatalf("unexpected error on %s %s: %v", method, url, err) 3799 return nil 3800 } 3801 var status metav1.Status 3802 body, err := extractBody(response, &status) 3803 if err != nil { 3804 t.Fatalf("unexpected error on %s %s: %v\nbody:\n%s", method, url, err, body) 3805 return nil 3806 } 3807 if code != response.StatusCode { 3808 t.Fatalf("Expected %s %s to return %d, Got %d: %v", method, url, code, response.StatusCode, body) 3809 } 3810 return &status 3811 } 3812 3813 func TestDelayReturnsError(t *testing.T) { 3814 storage := SimpleRESTStorage{ 3815 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3816 return nil, apierrors.NewAlreadyExists(schema.GroupResource{Resource: "foos"}, "bar") 3817 }, 3818 } 3819 handler := handle(map[string]rest.Storage{"foo": &storage}) 3820 server := httptest.NewServer(handler) 3821 defer server.Close() 3822 3823 status := expectAPIStatus(t, "DELETE", fmt.Sprintf("%s/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo/bar", server.URL), nil, http.StatusConflict) 3824 if status.Status != metav1.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != metav1.StatusReasonAlreadyExists { 3825 t.Errorf("Unexpected status %#v", status) 3826 } 3827 } 3828 3829 type UnregisteredAPIObject struct { 3830 Value string 3831 } 3832 3833 func (obj *UnregisteredAPIObject) GetObjectKind() schema.ObjectKind { 3834 return schema.EmptyObjectKind 3835 } 3836 func (obj *UnregisteredAPIObject) DeepCopyObject() runtime.Object { 3837 if obj == nil { 3838 return nil 3839 } 3840 clone := *obj 3841 return &clone 3842 } 3843 3844 func TestWriteJSONDecodeError(t *testing.T) { 3845 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 3846 responsewriters.WriteObjectNegotiated(codecs, negotiation.DefaultEndpointRestrictions, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"}, false) 3847 })) 3848 defer server.Close() 3849 // Decode error response behavior is dictated by 3850 // apiserver/pkg/endpoints/handlers/responsewriters/status.go::ErrorToAPIStatus(). 3851 // Unless specific metav1.Status() parameters are implemented for the particular error in question, such that 3852 // the status code is defined, metav1 errors where error.status == metav1.StatusFailure 3853 // will throw a '500 Internal Server Error'. Non-metav1 type errors will always throw a '500 Internal Server Error'. 3854 status := expectAPIStatus(t, "GET", server.URL, nil, http.StatusInternalServerError) 3855 if status.Reason != metav1.StatusReasonUnknown { 3856 t.Errorf("unexpected reason %#v", status) 3857 } 3858 if !strings.Contains(status.Message, "no kind is registered for the type endpoints.UnregisteredAPIObject") { 3859 t.Errorf("unexpected message %#v", status) 3860 } 3861 } 3862 3863 type marshalError struct { 3864 err error 3865 } 3866 3867 func (m *marshalError) MarshalJSON() ([]byte, error) { 3868 return []byte{}, m.err 3869 } 3870 3871 func TestWriteRAWJSONMarshalError(t *testing.T) { 3872 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 3873 responsewriters.WriteRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w) 3874 })) 3875 defer server.Close() 3876 client := http.Client{} 3877 resp, err := client.Get(server.URL) 3878 if err != nil { 3879 t.Errorf("unexpected error: %v", err) 3880 } 3881 3882 if resp.StatusCode != http.StatusInternalServerError { 3883 t.Errorf("unexpected status code %d", resp.StatusCode) 3884 } 3885 } 3886 3887 func TestCreateTimeout(t *testing.T) { 3888 testOver := make(chan struct{}) 3889 defer close(testOver) 3890 storage := SimpleRESTStorage{ 3891 injectedFunction: func(obj runtime.Object) (runtime.Object, error) { 3892 // Eliminate flakes by ensuring the create operation takes longer than this test. 3893 <-testOver 3894 return obj, nil 3895 }, 3896 } 3897 handler := handle(map[string]rest.Storage{ 3898 "foo": &storage, 3899 }) 3900 server := httptest.NewServer(handler) 3901 defer server.Close() 3902 3903 simple := &genericapitesting.Simple{Other: "foo"} 3904 data, err := runtime.Encode(testCodec, simple) 3905 if err != nil { 3906 t.Errorf("unexpected error: %v", err) 3907 } 3908 itemOut := expectAPIStatus(t, "POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo?timeout=4ms", data, http.StatusGatewayTimeout) 3909 if itemOut.Status != metav1.StatusFailure || itemOut.Reason != metav1.StatusReasonTimeout { 3910 t.Errorf("Unexpected status %#v", itemOut) 3911 } 3912 } 3913 3914 func TestCreateChecksAPIVersion(t *testing.T) { 3915 handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) 3916 server := httptest.NewServer(handler) 3917 defer server.Close() 3918 client := http.Client{} 3919 3920 simple := &genericapitesting.Simple{} 3921 //using newCodec and send the request to testVersion URL shall cause a discrepancy in apiVersion 3922 data, err := runtime.Encode(newCodec, simple) 3923 if err != nil { 3924 t.Errorf("unexpected error: %v", err) 3925 } 3926 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) 3927 if err != nil { 3928 t.Errorf("unexpected error: %v", err) 3929 } 3930 response, err := client.Do(request) 3931 if err != nil { 3932 t.Errorf("unexpected error: %v", err) 3933 } 3934 if response.StatusCode != http.StatusBadRequest { 3935 t.Errorf("Unexpected response %#v", response) 3936 } 3937 b, err := ioutil.ReadAll(response.Body) 3938 if err != nil { 3939 t.Errorf("unexpected error: %v", err) 3940 } else if !strings.Contains(string(b), "does not match the expected API version") { 3941 t.Errorf("unexpected response: %s", string(b)) 3942 } 3943 } 3944 3945 func TestCreateDefaultsAPIVersion(t *testing.T) { 3946 handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) 3947 server := httptest.NewServer(handler) 3948 defer server.Close() 3949 client := http.Client{} 3950 3951 simple := &genericapitesting.Simple{} 3952 data, err := runtime.Encode(codec, simple) 3953 if err != nil { 3954 t.Errorf("unexpected error: %v", err) 3955 } 3956 3957 m := make(map[string]interface{}) 3958 if err := json.Unmarshal(data, &m); err != nil { 3959 t.Errorf("unexpected error: %v", err) 3960 } 3961 delete(m, "apiVersion") 3962 data, err = json.Marshal(m) 3963 if err != nil { 3964 t.Errorf("unexpected error: %v", err) 3965 } 3966 3967 request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) 3968 if err != nil { 3969 t.Errorf("unexpected error: %v", err) 3970 } 3971 response, err := client.Do(request) 3972 if err != nil { 3973 t.Errorf("unexpected error: %v", err) 3974 } 3975 if response.StatusCode != http.StatusCreated { 3976 t.Errorf("unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusCreated, response) 3977 } 3978 } 3979 3980 func TestUpdateChecksAPIVersion(t *testing.T) { 3981 handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) 3982 server := httptest.NewServer(handler) 3983 defer server.Close() 3984 client := http.Client{} 3985 3986 simple := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} 3987 data, err := runtime.Encode(newCodec, simple) 3988 if err != nil { 3989 t.Fatalf("unexpected error: %v", err) 3990 } 3991 request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) 3992 if err != nil { 3993 t.Fatalf("unexpected error: %v", err) 3994 } 3995 response, err := client.Do(request) 3996 if err != nil { 3997 t.Fatalf("unexpected error: %v", err) 3998 } 3999 if response.StatusCode != http.StatusBadRequest { 4000 t.Errorf("Unexpected response %#v", response) 4001 } 4002 b, err := ioutil.ReadAll(response.Body) 4003 if err != nil { 4004 t.Errorf("unexpected error: %v", err) 4005 } else if !strings.Contains(string(b), "does not match the expected API version") { 4006 t.Errorf("unexpected response: %s", string(b)) 4007 } 4008 } 4009 4010 // runRequest is used by TestDryRun since it runs the test twice in a 4011 // row with a slightly different URL (one has ?dryRun, one doesn't). 4012 func runRequest(t testing.TB, path, verb string, data []byte, contentType string) *http.Response { 4013 request, err := http.NewRequest(verb, path, bytes.NewBuffer(data)) 4014 if err != nil { 4015 t.Fatalf("unexpected error: %v", err) 4016 } 4017 if contentType != "" { 4018 request.Header.Set("Content-Type", contentType) 4019 } 4020 response, err := http.DefaultClient.Do(request) 4021 if err != nil { 4022 t.Fatalf("unexpected error: %v", err) 4023 } 4024 return response 4025 } 4026 4027 type SimpleRESTStorageWithDeleteCollection struct { 4028 SimpleRESTStorage 4029 } 4030 4031 // Delete collection doesn't do much, but let us test this path. 4032 func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { 4033 storage.checkContext(ctx) 4034 return nil, nil 4035 } 4036 4037 // shared vars used by both TestFieldValidation and BenchmarkFieldValidation 4038 var ( 4039 strictFieldValidation = "?fieldValidation=Strict" 4040 warnFieldValidation = "?fieldValidation=Warn" 4041 ignoreFieldValidation = "?fieldValidation=Ignore" 4042 ) 4043 4044 // TestFieldValidation tests the create, update, and patch handlers for correctness when faced with field validation errors. 4045 func TestFieldValidation(t *testing.T) { 4046 var ( 4047 strictDecodingErr = `strict decoding error: duplicate field \"other\", unknown field \"unknown\"` 4048 strictDecodingWarns = []string{`duplicate field "other"`, `unknown field "unknown"`} 4049 strictDecodingErrYAML = `strict decoding error: yaml: unmarshal errors:\n line 6: key \"other\" already set in map, unknown field \"unknown\"` 4050 strictDecodingWarnsYAML = []string{`line 6: key "other" already set in map`, `unknown field "unknown"`} 4051 strictDecodingErrYAMLPut = `strict decoding error: yaml: unmarshal errors:\n line 7: key \"other\" already set in map, unknown field \"unknown\"` 4052 strictDecodingWarnsYAMLPut = []string{`line 7: key "other" already set in map`, `unknown field "unknown"`} 4053 4054 invalidJSONDataPost = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"creationTimestamp":null}, "other":"foo","other":"bar","unknown":"baz"}`) 4055 invalidYAMLDataPost = []byte(`apiVersion: test.group/version 4056 kind: Simple 4057 metadata: 4058 creationTimestamp: null 4059 other: foo 4060 other: bar 4061 unknown: baz`) 4062 4063 invalidJSONDataPut = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"name":"id", "creationTimestamp":null}, "other":"foo","other":"bar","unknown":"baz"}`) 4064 invalidYAMLDataPut = []byte(`apiVersion: test.group/version 4065 kind: Simple 4066 metadata: 4067 name: id 4068 creationTimestamp: null 4069 other: foo 4070 other: bar 4071 unknown: baz`) 4072 4073 invalidMergePatch = []byte(`{"labels":{"foo":"bar"}, "unknown": "foo", "other": "foo", "other": "bar"}`) 4074 invalidJSONPatch = []byte(` 4075 [ 4076 {"op": "add", "path": "/unknown", "value": "foo"}, 4077 {"op": "add", "path": "/other", "value": "foo"}, 4078 {"op": "add", "path": "/other", "value": "bar"} 4079 ] 4080 `) 4081 // note: duplicate fields in the patch itself 4082 // are dropped by the 4083 // evanphx/json-patch library and is expected. 4084 jsonPatchStrictDecodingErr = `strict decoding error: unknown field \"unknown\"` 4085 jsonPatchStrictDecodingWarns = []string{`unknown field "unknown"`} 4086 4087 invalidSMP = []byte(`{"unknown": "foo", "other":"foo", "other": "bar"}`) 4088 4089 fieldValidationTests = []struct { 4090 name string 4091 path string 4092 verb string 4093 data []byte 4094 queryParams string 4095 contentType string 4096 expectedErr string 4097 expectedWarns []string 4098 expectedStatusCode int 4099 }{ 4100 // Create 4101 {name: "post-strict-validation", path: "/namespaces/default/simples", verb: "POST", data: invalidJSONDataPost, queryParams: strictFieldValidation, expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErr}, 4102 {name: "post-warn-validation", path: "/namespaces/default/simples", verb: "POST", data: invalidJSONDataPost, queryParams: warnFieldValidation, expectedStatusCode: http.StatusCreated, expectedWarns: strictDecodingWarns}, 4103 {name: "post-ignore-validation", path: "/namespaces/default/simples", verb: "POST", data: invalidJSONDataPost, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusCreated}, 4104 4105 {name: "post-strict-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: invalidYAMLDataPost, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErrYAML}, 4106 {name: "post-warn-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: invalidYAMLDataPost, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated, expectedWarns: strictDecodingWarnsYAML}, 4107 {name: "post-ignore-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: invalidYAMLDataPost, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated}, 4108 4109 // Update 4110 {name: "put-strict-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidJSONDataPut, queryParams: strictFieldValidation, expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErr}, 4111 {name: "put-warn-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidJSONDataPut, queryParams: warnFieldValidation, expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarns}, 4112 {name: "put-ignore-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidJSONDataPut, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusOK}, 4113 4114 {name: "put-strict-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidYAMLDataPut, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErrYAMLPut}, 4115 {name: "put-warn-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidYAMLDataPut, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarnsYAMLPut}, 4116 {name: "put-ignore-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidYAMLDataPut, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK}, 4117 4118 // MergePatch 4119 {name: "merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidMergePatch, queryParams: strictFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusUnprocessableEntity, expectedErr: strictDecodingErr}, 4120 {name: "merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidMergePatch, queryParams: warnFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarns}, 4121 {name: "merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidMergePatch, queryParams: ignoreFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4122 4123 // JSON Patch 4124 {name: "json-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidJSONPatch, queryParams: strictFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusUnprocessableEntity, expectedErr: jsonPatchStrictDecodingErr}, 4125 {name: "json-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidJSONPatch, queryParams: warnFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK, expectedWarns: jsonPatchStrictDecodingWarns}, 4126 {name: "json-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidJSONPatch, queryParams: ignoreFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4127 4128 // SMP 4129 {name: "strategic-merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidSMP, queryParams: strictFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusUnprocessableEntity, expectedErr: strictDecodingErr}, 4130 {name: "strategic-merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidSMP, queryParams: warnFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarns}, 4131 {name: "strategic-merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidSMP, queryParams: ignoreFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4132 } 4133 ) 4134 4135 server := httptest.NewServer(handleWithWarnings(map[string]rest.Storage{ 4136 "simples": &SimpleRESTStorageWithDeleteCollection{ 4137 SimpleRESTStorage{ 4138 item: genericapitesting.Simple{ 4139 ObjectMeta: metav1.ObjectMeta{ 4140 Name: "id", 4141 Namespace: "", 4142 UID: "uid", 4143 }, 4144 Other: "baz", 4145 }, 4146 }, 4147 }, 4148 "simples/subsimple": &SimpleXGSubresourceRESTStorage{ 4149 item: genericapitesting.SimpleXGSubresource{ 4150 SubresourceInfo: "foo", 4151 }, 4152 itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"), 4153 }, 4154 })) 4155 defer server.Close() 4156 for _, test := range fieldValidationTests { 4157 t.Run(test.name, func(t *testing.T) { 4158 baseURL := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version 4159 response := runRequest(t, baseURL+test.path+test.queryParams, test.verb, test.data, test.contentType) 4160 buf := new(bytes.Buffer) 4161 buf.ReadFrom(response.Body) 4162 4163 if response.StatusCode != test.expectedStatusCode || !strings.Contains(buf.String(), test.expectedErr) { 4164 t.Fatalf("unexpected response: %#v, expected err: %#v", response, test.expectedErr) 4165 } 4166 4167 warnings, _ := net.ParseWarningHeaders(response.Header["Warning"]) 4168 if len(warnings) != len(test.expectedWarns) { 4169 t.Fatalf("unexpected number of warnings. Got count %d, expected %d. Got warnings %#v, expected %#v", len(warnings), len(test.expectedWarns), warnings, test.expectedWarns) 4170 4171 } 4172 for i, warn := range warnings { 4173 if warn.Text != test.expectedWarns[i] { 4174 t.Fatalf("unexpected warning: %#v, expected warning: %#v", warn.Text, test.expectedWarns[i]) 4175 } 4176 } 4177 }) 4178 } 4179 } 4180 4181 // BenchmarkFieldValidation benchmarks the create, update, and patch handlers for performance distinctions between 4182 // strict, warn, and ignore field validation handling. 4183 func BenchmarkFieldValidation(b *testing.B) { 4184 var ( 4185 validJSONDataPost = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"creationTimestamp":null}, "other":"foo"}`) 4186 validYAMLDataPost = []byte(`apiVersion: test.group/version 4187 kind: Simple 4188 metadata: 4189 creationTimestamp: null 4190 other: foo`) 4191 4192 validJSONDataPut = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"name":"id", "creationTimestamp":null}, "other":"bar"}`) 4193 validYAMLDataPut = []byte(`apiVersion: test.group/version 4194 kind: Simple 4195 metadata: 4196 name: id 4197 creationTimestamp: null 4198 other: bar`) 4199 4200 validMergePatch = []byte(`{"labels":{"foo":"bar"}, "other": "bar"}`) 4201 validJSONPatch = []byte(` 4202 [ 4203 {"op": "add", "path": "/other", "value": "bar"} 4204 ] 4205 `) 4206 validSMP = []byte(`{"other": "bar"}`) 4207 4208 fieldValidationBenchmarks = []struct { 4209 name string 4210 path string 4211 verb string 4212 data []byte 4213 queryParams string 4214 contentType string 4215 expectedStatusCode int 4216 }{ 4217 // Create 4218 {name: "post-strict-validation", path: "/namespaces/default/simples", verb: "POST", data: validJSONDataPost, queryParams: strictFieldValidation, expectedStatusCode: http.StatusCreated}, 4219 {name: "post-warn-validation", path: "/namespaces/default/simples", verb: "POST", data: validJSONDataPost, queryParams: warnFieldValidation, expectedStatusCode: http.StatusCreated}, 4220 {name: "post-ignore-validation", path: "/namespaces/default/simples", verb: "POST", data: validJSONDataPost, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusCreated}, 4221 4222 {name: "post-strict-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: validYAMLDataPost, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated}, 4223 {name: "post-warn-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: validYAMLDataPost, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated}, 4224 {name: "post-ignore-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: validYAMLDataPost, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated}, 4225 4226 // Update 4227 {name: "put-strict-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: validJSONDataPut, queryParams: strictFieldValidation, expectedStatusCode: http.StatusOK}, 4228 {name: "put-warn-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: validJSONDataPut, queryParams: warnFieldValidation, expectedStatusCode: http.StatusOK}, 4229 {name: "put-ignore-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: validJSONDataPut, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusOK}, 4230 4231 {name: "put-strict-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: validYAMLDataPut, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK}, 4232 {name: "put-warn-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: validYAMLDataPut, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK}, 4233 {name: "put-ignore-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: validYAMLDataPut, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK}, 4234 4235 // MergePatch 4236 {name: "merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validMergePatch, queryParams: strictFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4237 {name: "merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validMergePatch, queryParams: warnFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4238 {name: "merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validMergePatch, queryParams: ignoreFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4239 4240 // JSON Patch 4241 {name: "json-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validJSONPatch, queryParams: strictFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4242 {name: "json-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validJSONPatch, queryParams: warnFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4243 {name: "json-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validJSONPatch, queryParams: ignoreFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4244 4245 // SMP 4246 {name: "strategic-merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validSMP, queryParams: strictFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4247 {name: "strategic-merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validSMP, queryParams: warnFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4248 {name: "strategic-merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validSMP, queryParams: ignoreFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK}, 4249 } 4250 ) 4251 4252 server := httptest.NewServer(handleWithWarnings(map[string]rest.Storage{ 4253 "simples": &SimpleRESTStorageWithDeleteCollection{ 4254 SimpleRESTStorage{ 4255 item: genericapitesting.Simple{ 4256 ObjectMeta: metav1.ObjectMeta{ 4257 Name: "id", 4258 Namespace: "", 4259 UID: "uid", 4260 }, 4261 Other: "bar", 4262 }, 4263 }, 4264 }, 4265 "simples/subsimple": &SimpleXGSubresourceRESTStorage{ 4266 item: genericapitesting.SimpleXGSubresource{ 4267 SubresourceInfo: "foo", 4268 }, 4269 itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"), 4270 }, 4271 })) 4272 defer server.Close() 4273 for _, test := range fieldValidationBenchmarks { 4274 b.Run(test.name, func(b *testing.B) { 4275 for n := 0; n < b.N; n++ { 4276 baseURL := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version 4277 response := runRequest(b, baseURL+test.path+test.queryParams, test.verb, test.data, test.contentType) 4278 if response.StatusCode != test.expectedStatusCode { 4279 b.Fatalf("unexpected status code: %d, expected: %d", response.StatusCode, test.expectedStatusCode) 4280 } 4281 } 4282 }) 4283 } 4284 } 4285 4286 type SimpleXGSubresourceRESTStorage struct { 4287 item genericapitesting.SimpleXGSubresource 4288 itemGVK schema.GroupVersionKind 4289 } 4290 4291 var _ = rest.GroupVersionKindProvider(&SimpleXGSubresourceRESTStorage{}) 4292 4293 func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object { 4294 return &genericapitesting.SimpleXGSubresource{} 4295 } 4296 4297 func (storage *SimpleXGSubresourceRESTStorage) Destroy() { 4298 } 4299 4300 func (storage *SimpleXGSubresourceRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { 4301 return storage.item.DeepCopyObject(), nil 4302 } 4303 4304 func (storage *SimpleXGSubresourceRESTStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { 4305 return nil, true, nil 4306 } 4307 4308 func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { 4309 return storage.itemGVK 4310 } 4311 4312 func (storage *SimpleXGSubresourceRESTStorage) GetSingularName() string { 4313 return "simple" 4314 } 4315 4316 func TestXGSubresource(t *testing.T) { 4317 container := restful.NewContainer() 4318 container.Router(restful.CurlyRouter{}) 4319 mux := container.ServeMux 4320 4321 itemID := "theID" 4322 subresourceStorage := &SimpleXGSubresourceRESTStorage{ 4323 item: genericapitesting.SimpleXGSubresource{ 4324 SubresourceInfo: "foo", 4325 }, 4326 itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"), 4327 } 4328 storage := map[string]rest.Storage{ 4329 "simple": &SimpleRESTStorage{}, 4330 "simple/subsimple": subresourceStorage, 4331 } 4332 4333 group := APIGroupVersion{ 4334 Storage: storage, 4335 4336 Creater: scheme, 4337 Convertor: scheme, 4338 TypeConverter: managedfields.NewDeducedTypeConverter(), 4339 UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), 4340 Defaulter: scheme, 4341 Typer: scheme, 4342 Namer: namer, 4343 4344 EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), 4345 4346 ParameterCodec: parameterCodec, 4347 4348 Admit: admissionControl, 4349 4350 Root: "/" + prefix, 4351 GroupVersion: testGroupVersion, 4352 OptionsExternalVersion: &testGroupVersion, 4353 Serializer: codecs, 4354 } 4355 4356 if _, _, err := (&group).InstallREST(container); err != nil { 4357 panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) 4358 } 4359 4360 server := newTestServer(defaultAPIServer{mux, container}) 4361 defer server.Close() 4362 4363 resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple") 4364 if err != nil { 4365 t.Fatalf("unexpected error: %v", err) 4366 } 4367 if resp.StatusCode != http.StatusOK { 4368 t.Fatalf("unexpected response: %#v", resp) 4369 } 4370 var itemOut genericapitesting.SimpleXGSubresource 4371 body, err := extractBody(resp, &itemOut) 4372 if err != nil { 4373 t.Errorf("unexpected error: %v", err) 4374 } 4375 4376 // Test if the returned object has the expected group, version and kind 4377 // We are directly unmarshaling JSON here because TypeMeta cannot be decoded through the 4378 // installed decoders. TypeMeta cannot be decoded because it is added to the ignored 4379 // conversion type list in API scheme and hence cannot be converted from input type object 4380 // to output type object. So it's values don't appear in the decoded output object. 4381 decoder := json.NewDecoder(strings.NewReader(body)) 4382 var itemFromBody genericapitesting.SimpleXGSubresource 4383 err = decoder.Decode(&itemFromBody) 4384 if err != nil { 4385 t.Errorf("unexpected JSON decoding error: %v", err) 4386 } 4387 if want := fmt.Sprintf("%s/%s", testGroup2Version.Group, testGroup2Version.Version); itemFromBody.APIVersion != want { 4388 t.Errorf("unexpected APIVersion got: %+v want: %+v", itemFromBody.APIVersion, want) 4389 } 4390 if itemFromBody.Kind != "SimpleXGSubresource" { 4391 t.Errorf("unexpected Kind got: %+v want: SimpleXGSubresource", itemFromBody.Kind) 4392 } 4393 4394 if itemOut.Name != subresourceStorage.item.Name { 4395 t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, subresourceStorage.item, string(body)) 4396 } 4397 } 4398 4399 func readBodyOrDie(r io.Reader) []byte { 4400 body, err := ioutil.ReadAll(r) 4401 if err != nil { 4402 panic(err) 4403 } 4404 return body 4405 } 4406 4407 // BenchmarkUpdateProtobuf measures the cost of processing an update on the server in proto 4408 func BenchmarkUpdateProtobuf(b *testing.B) { 4409 items := benchmarkItems(b) 4410 4411 simpleStorage := &SimpleRESTStorage{} 4412 handler := handle(map[string]rest.Storage{"simples": simpleStorage}) 4413 server := httptest.NewServer(handler) 4414 defer server.Close() 4415 client := http.Client{} 4416 4417 dest, _ := url.Parse(server.URL) 4418 dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simples/bar" 4419 dest.RawQuery = "" 4420 4421 info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/vnd.kubernetes.protobuf") 4422 e := codecs.EncoderForVersion(info.Serializer, newGroupVersion) 4423 data, err := runtime.Encode(e, &items[0]) 4424 if err != nil { 4425 b.Fatal(err) 4426 } 4427 4428 b.ResetTimer() 4429 for i := 0; i < b.N; i++ { 4430 request, err := http.NewRequest("PUT", dest.String(), bytes.NewReader(data)) 4431 if err != nil { 4432 b.Fatalf("unexpected error: %v", err) 4433 } 4434 request.Header.Set("Accept", "application/vnd.kubernetes.protobuf") 4435 request.Header.Set("Content-Type", "application/vnd.kubernetes.protobuf") 4436 response, err := client.Do(request) 4437 if err != nil { 4438 b.Fatalf("unexpected error: %v", err) 4439 } 4440 if response.StatusCode != http.StatusBadRequest { 4441 body, _ := ioutil.ReadAll(response.Body) 4442 b.Fatalf("Unexpected response %#v\n%s", response, body) 4443 } 4444 _, _ = ioutil.ReadAll(response.Body) 4445 response.Body.Close() 4446 } 4447 b.StopTimer() 4448 } 4449 4450 func newTestServer(handler http.Handler) *httptest.Server { 4451 handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver()) 4452 return httptest.NewServer(handler) 4453 } 4454 4455 func newTestRequestInfoResolver() *request.RequestInfoFactory { 4456 return &request.RequestInfoFactory{ 4457 APIPrefixes: sets.NewString("api", "apis"), 4458 GrouplessAPIPrefixes: sets.NewString("api"), 4459 } 4460 } 4461 4462 const benchmarkSeed = 100 4463 4464 func benchmarkItems(b *testing.B) []example.Pod { 4465 clientapiObjectFuzzer := fuzzer.FuzzerFor(examplefuzzer.Funcs, rand.NewSource(benchmarkSeed), codecs) 4466 items := make([]example.Pod, 3) 4467 for i := range items { 4468 clientapiObjectFuzzer.Fuzz(&items[i]) 4469 } 4470 return items 4471 }