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