k8s.io/apiserver@v0.31.1/pkg/server/genericapiserver_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package server 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "net" 25 "net/http" 26 "net/http/httptest" 27 goruntime "runtime" 28 "strconv" 29 "sync" 30 "testing" 31 "time" 32 33 "github.com/stretchr/testify/assert" 34 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/runtime/schema" 38 "k8s.io/apimachinery/pkg/runtime/serializer" 39 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 40 "k8s.io/apimachinery/pkg/util/sets" 41 "k8s.io/apimachinery/pkg/util/wait" 42 "k8s.io/apimachinery/pkg/version" 43 "k8s.io/apiserver/pkg/apis/example" 44 examplev1 "k8s.io/apiserver/pkg/apis/example/v1" 45 "k8s.io/apiserver/pkg/authorization/authorizer" 46 "k8s.io/apiserver/pkg/endpoints/discovery" 47 genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" 48 openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" 49 "k8s.io/apiserver/pkg/registry/rest" 50 genericfilters "k8s.io/apiserver/pkg/server/filters" 51 utilversion "k8s.io/apiserver/pkg/util/version" 52 "k8s.io/apiserver/pkg/warning" 53 "k8s.io/client-go/informers" 54 "k8s.io/client-go/kubernetes/fake" 55 restclient "k8s.io/client-go/rest" 56 "k8s.io/klog/v2/ktesting" 57 kubeopenapi "k8s.io/kube-openapi/pkg/common" 58 "k8s.io/kube-openapi/pkg/validation/spec" 59 netutils "k8s.io/utils/net" 60 ) 61 62 const ( 63 extensionsGroupName = "extensions" 64 ) 65 66 var ( 67 v1GroupVersion = schema.GroupVersion{Group: "", Version: "v1"} 68 69 scheme = runtime.NewScheme() 70 codecs = serializer.NewCodecFactory(scheme) 71 parameterCodec = runtime.NewParameterCodec(scheme) 72 ) 73 74 func init() { 75 metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) 76 scheme.AddUnversionedTypes(v1GroupVersion, 77 &metav1.Status{}, 78 &metav1.APIVersions{}, 79 &metav1.APIGroupList{}, 80 &metav1.APIGroup{}, 81 &metav1.APIResourceList{}, 82 ) 83 utilruntime.Must(example.AddToScheme(scheme)) 84 utilruntime.Must(examplev1.AddToScheme(scheme)) 85 } 86 87 func buildTestOpenAPIDefinition() kubeopenapi.OpenAPIDefinition { 88 return kubeopenapi.OpenAPIDefinition{ 89 Schema: spec.Schema{ 90 SchemaProps: spec.SchemaProps{ 91 Description: "Description", 92 Properties: map[string]spec.Schema{}, 93 }, 94 VendorExtensible: spec.VendorExtensible{ 95 Extensions: spec.Extensions{ 96 "x-kubernetes-group-version-kind": []interface{}{ 97 map[string]interface{}{ 98 "group": "", 99 "version": "v1", 100 "kind": "Getter", 101 }, 102 map[string]interface{}{ 103 "group": "batch", 104 "version": "v1", 105 "kind": "Getter", 106 }, 107 map[string]interface{}{ 108 "group": "extensions", 109 "version": "v1", 110 "kind": "Getter", 111 }, 112 }, 113 }, 114 }, 115 }, 116 } 117 } 118 119 func testGetOpenAPIDefinitions(_ kubeopenapi.ReferenceCallback) map[string]kubeopenapi.OpenAPIDefinition { 120 return map[string]kubeopenapi.OpenAPIDefinition{ 121 "k8s.io/apimachinery/pkg/apis/meta/v1.Status": {}, 122 "k8s.io/apimachinery/pkg/apis/meta/v1.APIVersions": {}, 123 "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroupList": {}, 124 "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": buildTestOpenAPIDefinition(), 125 "k8s.io/apimachinery/pkg/apis/meta/v1.APIResourceList": {}, 126 } 127 } 128 129 // setUp is a convience function for setting up for (most) tests. 130 func setUp(t *testing.T) (Config, *assert.Assertions) { 131 config := NewConfig(codecs) 132 config.ExternalAddress = "192.168.10.4:443" 133 config.PublicAddress = netutils.ParseIPSloppy("192.168.10.4") 134 config.LegacyAPIGroupPrefixes = sets.NewString("/api") 135 config.LoopbackClientConfig = &restclient.Config{} 136 137 clientset := fake.NewSimpleClientset() 138 if clientset == nil { 139 t.Fatal("unable to create fake client set") 140 } 141 config.EffectiveVersion = utilversion.NewEffectiveVersion("") 142 config.OpenAPIConfig = DefaultOpenAPIConfig(testGetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme())) 143 config.OpenAPIConfig.Info.Version = "unversioned" 144 config.OpenAPIV3Config = DefaultOpenAPIV3Config(testGetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme())) 145 config.OpenAPIV3Config.Info.Version = "unversioned" 146 sharedInformers := informers.NewSharedInformerFactory(clientset, config.LoopbackClientConfig.Timeout) 147 config.Complete(sharedInformers) 148 149 return *config, assert.New(t) 150 } 151 152 func newMaster(t *testing.T) (*GenericAPIServer, Config, *assert.Assertions) { 153 config, assert := setUp(t) 154 155 s, err := config.Complete(nil).New("test", NewEmptyDelegate()) 156 if err != nil { 157 t.Fatalf("Error in bringing up the server: %v", err) 158 } 159 return s, config, assert 160 } 161 162 // TestNew verifies that the New function returns a GenericAPIServer 163 // using the configuration properly. 164 func TestNew(t *testing.T) { 165 s, config, assert := newMaster(t) 166 167 // Verify many of the variables match their config counterparts 168 assert.Equal(s.legacyAPIGroupPrefixes, config.LegacyAPIGroupPrefixes) 169 assert.Equal(s.admissionControl, config.AdmissionControl) 170 } 171 172 // Verifies that AddGroupVersions works as expected. 173 func TestInstallAPIGroups(t *testing.T) { 174 config, assert := setUp(t) 175 176 config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix") 177 config.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: "ExternalAddress"} 178 179 s, err := config.Complete(nil).New("test", NewEmptyDelegate()) 180 if err != nil { 181 t.Fatalf("Error in bringing up the server: %v", err) 182 } 183 184 testAPI := func(gv schema.GroupVersion) APIGroupInfo { 185 getter, noVerbs := testGetterStorage{}, testNoVerbsStorage{} 186 187 scheme := runtime.NewScheme() 188 scheme.AddKnownTypeWithName(gv.WithKind("Getter"), getter.New()) 189 scheme.AddKnownTypeWithName(gv.WithKind("NoVerb"), noVerbs.New()) 190 scheme.AddKnownTypes(v1GroupVersion, &metav1.Status{}) 191 metav1.AddToGroupVersion(scheme, v1GroupVersion) 192 193 return APIGroupInfo{ 194 PrioritizedVersions: []schema.GroupVersion{gv}, 195 VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ 196 gv.Version: { 197 "getter": &testGetterStorage{Version: gv.Version}, 198 "noverbs": &testNoVerbsStorage{Version: gv.Version}, 199 }, 200 }, 201 OptionsExternalVersion: &schema.GroupVersion{Version: "v1"}, 202 ParameterCodec: parameterCodec, 203 NegotiatedSerializer: codecs, 204 Scheme: scheme, 205 } 206 } 207 208 apis := []APIGroupInfo{ 209 testAPI(schema.GroupVersion{Group: "", Version: "v1"}), 210 testAPI(schema.GroupVersion{Group: extensionsGroupName, Version: "v1"}), 211 testAPI(schema.GroupVersion{Group: "batch", Version: "v1"}), 212 } 213 214 err = s.InstallLegacyAPIGroup("/apiPrefix", &apis[0]) 215 assert.NoError(err) 216 groupPaths := []string{ 217 config.LegacyAPIGroupPrefixes.List()[0], // /apiPrefix 218 } 219 for _, api := range apis[1:] { 220 err = s.InstallAPIGroup(&api) 221 assert.NoError(err) 222 groupPaths = append(groupPaths, APIGroupPrefix+"/"+api.PrioritizedVersions[0].Group) // /apis/<group> 223 } 224 225 server := httptest.NewServer(s.Handler) 226 defer server.Close() 227 228 for i := range apis { 229 // should serve APIGroup at group path 230 info := &apis[i] 231 path := groupPaths[i] 232 resp, err := http.Get(server.URL + path) 233 if err != nil { 234 t.Errorf("[%d] unexpected error getting path %q path: %v", i, path, err) 235 continue 236 } 237 238 body, err := io.ReadAll(resp.Body) 239 if err != nil { 240 t.Errorf("[%d] unexpected error reading body at path %q: %v", i, path, err) 241 continue 242 } 243 244 t.Logf("[%d] json at %s: %s", i, path, string(body)) 245 246 if i == 0 { 247 // legacy API returns APIVersions 248 group := metav1.APIVersions{} 249 err = json.Unmarshal(body, &group) 250 if err != nil { 251 t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err) 252 continue 253 } 254 } else { 255 // API groups return APIGroup 256 group := metav1.APIGroup{} 257 err = json.Unmarshal(body, &group) 258 if err != nil { 259 t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err) 260 continue 261 } 262 263 if got, expected := group.Name, info.PrioritizedVersions[0].Group; got != expected { 264 t.Errorf("[%d] unexpected group name at path %q: got=%q expected=%q", i, path, got, expected) 265 continue 266 } 267 268 if got, expected := group.PreferredVersion.Version, info.PrioritizedVersions[0].Version; got != expected { 269 t.Errorf("[%d] unexpected group version at path %q: got=%q expected=%q", i, path, got, expected) 270 continue 271 } 272 } 273 274 // should serve APIResourceList at group path + /<group-version> 275 path = path + "/" + info.PrioritizedVersions[0].Version 276 resp, err = http.Get(server.URL + path) 277 if err != nil { 278 t.Errorf("[%d] unexpected error getting path %q path: %v", i, path, err) 279 continue 280 } 281 282 body, err = io.ReadAll(resp.Body) 283 if err != nil { 284 t.Errorf("[%d] unexpected error reading body at path %q: %v", i, path, err) 285 continue 286 } 287 288 t.Logf("[%d] json at %s: %s", i, path, string(body)) 289 290 resources := metav1.APIResourceList{} 291 err = json.Unmarshal(body, &resources) 292 if err != nil { 293 t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err) 294 continue 295 } 296 297 if got, expected := resources.GroupVersion, info.PrioritizedVersions[0].String(); got != expected { 298 t.Errorf("[%d] unexpected groupVersion at path %q: got=%q expected=%q", i, path, got, expected) 299 continue 300 } 301 302 // the verbs should match the features of resources 303 for _, r := range resources.APIResources { 304 switch r.Name { 305 case "getter": 306 if got, expected := sets.NewString([]string(r.Verbs)...), sets.NewString("get"); !got.Equal(expected) { 307 t.Errorf("[%d] unexpected verbs for resource %s/%s: got=%v expected=%v", i, resources.GroupVersion, r.Name, got, expected) 308 } 309 case "noverbs": 310 if r.Verbs == nil { 311 t.Errorf("[%d] unexpected nil verbs slice. Expected: []string{}", i) 312 } 313 if got, expected := sets.NewString([]string(r.Verbs)...), sets.NewString(); !got.Equal(expected) { 314 t.Errorf("[%d] unexpected verbs for resource %s/%s: got=%v expected=%v", i, resources.GroupVersion, r.Name, got, expected) 315 } 316 } 317 } 318 } 319 } 320 321 func TestPrepareRun(t *testing.T) { 322 _, ctx := ktesting.NewTestContext(t) 323 s, config, assert := newMaster(t) 324 325 assert.NotNil(config.OpenAPIConfig) 326 327 server := httptest.NewServer(s.Handler.Director) 328 defer server.Close() 329 330 s.PrepareRun() 331 s.RunPostStartHooks(ctx) 332 333 // openapi is installed in PrepareRun 334 resp, err := http.Get(server.URL + "/openapi/v2") 335 assert.NoError(err) 336 assert.Equal(http.StatusOK, resp.StatusCode) 337 338 // wait for health (max-in-flight-filter is initialized asynchronously, can take a few milliseconds to initialize) 339 assert.NoError(wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { 340 // healthz checks are installed in PrepareRun 341 resp, err = http.Get(server.URL + "/healthz") 342 assert.NoError(err) 343 data, _ := io.ReadAll(resp.Body) 344 if http.StatusOK != resp.StatusCode { 345 t.Logf("got %d", resp.StatusCode) 346 t.Log(string(data)) 347 return false, nil 348 } 349 return true, nil 350 })) 351 resp, err = http.Get(server.URL + "/healthz/ping") 352 assert.NoError(err) 353 assert.Equal(http.StatusOK, resp.StatusCode) 354 } 355 356 func TestUpdateOpenAPISpec(t *testing.T) { 357 _, ctx := ktesting.NewTestContext(t) 358 s, _, assert := newMaster(t) 359 s.PrepareRun() 360 s.RunPostStartHooks(ctx) 361 362 server := httptest.NewServer(s.Handler.Director) 363 defer server.Close() 364 365 // verify the static spec in record is what we currently serve 366 oldSpec, err := json.Marshal(s.StaticOpenAPISpec) 367 assert.NoError(err) 368 369 resp, err := http.Get(server.URL + "/openapi/v2") 370 assert.NoError(err) 371 assert.Equal(http.StatusOK, resp.StatusCode) 372 373 body, err := io.ReadAll(resp.Body) 374 assert.NoError(err) 375 assert.Equal(oldSpec, body) 376 resp.Body.Close() 377 378 // verify we are able to update the served spec using the exposed service 379 newSpec := []byte(`{"swagger":"2.0","info":{"title":"Test Updated Generic API Server Swagger","version":"v0.1.0"},"paths":null}`) 380 swagger := new(spec.Swagger) 381 err = json.Unmarshal(newSpec, swagger) 382 assert.NoError(err) 383 384 err = s.OpenAPIVersionedService.UpdateSpec(swagger) 385 assert.NoError(err) 386 387 resp, err = http.Get(server.URL + "/openapi/v2") 388 assert.NoError(err) 389 defer resp.Body.Close() 390 assert.Equal(http.StatusOK, resp.StatusCode) 391 392 body, err = io.ReadAll(resp.Body) 393 assert.NoError(err) 394 assert.Equal(newSpec, body) 395 } 396 397 // TestCustomHandlerChain verifies the handler chain with custom handler chain builder functions. 398 func TestCustomHandlerChain(t *testing.T) { 399 config, _ := setUp(t) 400 401 var protected, called bool 402 403 config.BuildHandlerChainFunc = func(apiHandler http.Handler, c *Config) http.Handler { 404 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 405 protected = true 406 apiHandler.ServeHTTP(w, req) 407 }) 408 } 409 handler := http.HandlerFunc(func(r http.ResponseWriter, req *http.Request) { 410 called = true 411 }) 412 413 s, err := config.Complete(nil).New("test", NewEmptyDelegate()) 414 if err != nil { 415 t.Fatalf("Error in bringing up the server: %v", err) 416 } 417 418 s.Handler.NonGoRestfulMux.Handle("/nonswagger", handler) 419 s.Handler.NonGoRestfulMux.Handle("/secret", handler) 420 421 type Test struct { 422 handler http.Handler 423 path string 424 protected bool 425 } 426 for i, test := range []Test{ 427 {s.Handler, "/nonswagger", true}, 428 {s.Handler, "/secret", true}, 429 } { 430 protected, called = false, false 431 432 var w io.Reader 433 req, err := http.NewRequest("GET", test.path, w) 434 if err != nil { 435 t.Errorf("%d: Unexpected http error: %v", i, err) 436 continue 437 } 438 439 test.handler.ServeHTTP(httptest.NewRecorder(), req) 440 441 if !called { 442 t.Errorf("%d: Expected handler to be called.", i) 443 } 444 if test.protected != protected { 445 t.Errorf("%d: Expected protected=%v, got protected=%v.", i, test.protected, protected) 446 } 447 } 448 } 449 450 // TestNotRestRoutesHaveAuth checks that special non-routes are behind authz/authn. 451 func TestNotRestRoutesHaveAuth(t *testing.T) { 452 config, _ := setUp(t) 453 454 authz := mockAuthorizer{} 455 456 config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix") 457 config.Authorization.Authorizer = &authz 458 459 config.EnableIndex = true 460 config.EnableProfiling = true 461 462 kubeVersion := fakeVersion() 463 effectiveVersion := utilversion.NewEffectiveVersion(kubeVersion.String()) 464 effectiveVersion.Set(effectiveVersion.BinaryVersion().WithInfo(kubeVersion), effectiveVersion.EmulationVersion(), effectiveVersion.MinCompatibilityVersion()) 465 config.EffectiveVersion = effectiveVersion 466 467 s, err := config.Complete(nil).New("test", NewEmptyDelegate()) 468 if err != nil { 469 t.Fatalf("Error in bringing up the server: %v", err) 470 } 471 472 for _, test := range []struct { 473 route string 474 }{ 475 {"/"}, 476 {"/debug/pprof/"}, 477 {"/debug/flags/"}, 478 {"/version"}, 479 } { 480 resp := httptest.NewRecorder() 481 req, _ := http.NewRequest("GET", test.route, nil) 482 s.Handler.ServeHTTP(resp, req) 483 if resp.Code != 200 { 484 t.Errorf("route %q expected to work: code %d", test.route, resp.Code) 485 continue 486 } 487 488 if authz.lastURI != test.route { 489 t.Errorf("route %q expected to go through authorization, last route did: %q", test.route, authz.lastURI) 490 } 491 } 492 } 493 494 func TestMuxAndDiscoveryCompleteSignals(t *testing.T) { 495 // setup 496 cfg, assert := setUp(t) 497 498 // scenario 1: single server with some signals 499 root, err := cfg.Complete(nil).New("rootServer", NewEmptyDelegate()) 500 assert.NoError(err) 501 if len(root.MuxAndDiscoveryCompleteSignals()) != 0 { 502 assert.Error(fmt.Errorf("unexpected signals %v registered in the root server", root.MuxAndDiscoveryCompleteSignals())) 503 } 504 root.RegisterMuxAndDiscoveryCompleteSignal("rootTestSignal", make(chan struct{})) 505 if len(root.MuxAndDiscoveryCompleteSignals()) != 1 { 506 assert.Error(fmt.Errorf("unexpected signals %v registered in the root server", root.MuxAndDiscoveryCompleteSignals())) 507 } 508 509 // scenario 2: multiple servers with some signals 510 delegate, err := cfg.Complete(nil).New("delegateServer", NewEmptyDelegate()) 511 assert.NoError(err) 512 delegate.RegisterMuxAndDiscoveryCompleteSignal("delegateTestSignal", make(chan struct{})) 513 if len(delegate.MuxAndDiscoveryCompleteSignals()) != 1 { 514 assert.Error(fmt.Errorf("unexpected signals %v registered in the delegate server", delegate.MuxAndDiscoveryCompleteSignals())) 515 } 516 newRoot, err := cfg.Complete(nil).New("newRootServer", delegate) 517 assert.NoError(err) 518 if len(newRoot.MuxAndDiscoveryCompleteSignals()) != 1 { 519 assert.Error(fmt.Errorf("unexpected signals %v registered in the newRoot server", newRoot.MuxAndDiscoveryCompleteSignals())) 520 } 521 newRoot.RegisterMuxAndDiscoveryCompleteSignal("newRootTestSignal", make(chan struct{})) 522 if len(newRoot.MuxAndDiscoveryCompleteSignals()) != 2 { 523 assert.Error(fmt.Errorf("unexpected signals %v registered in the newRoot server", newRoot.MuxAndDiscoveryCompleteSignals())) 524 } 525 } 526 527 type mockAuthorizer struct { 528 lastURI string 529 } 530 531 func (authz *mockAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { 532 authz.lastURI = a.GetPath() 533 return authorizer.DecisionAllow, "", nil 534 } 535 536 type testGetterStorage struct { 537 Version string 538 } 539 540 func (p *testGetterStorage) NamespaceScoped() bool { 541 return true 542 } 543 544 func (p *testGetterStorage) New() runtime.Object { 545 return &metav1.APIGroup{ 546 TypeMeta: metav1.TypeMeta{ 547 Kind: "Getter", 548 APIVersion: p.Version, 549 }, 550 } 551 } 552 553 func (p *testGetterStorage) Destroy() { 554 } 555 556 func (p *testGetterStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { 557 return nil, nil 558 } 559 560 func (p *testGetterStorage) GetSingularName() string { 561 return "getter" 562 } 563 564 type testNoVerbsStorage struct { 565 Version string 566 } 567 568 func (p *testNoVerbsStorage) NamespaceScoped() bool { 569 return true 570 } 571 572 func (p *testNoVerbsStorage) New() runtime.Object { 573 return &metav1.APIGroup{ 574 TypeMeta: metav1.TypeMeta{ 575 Kind: "NoVerbs", 576 APIVersion: p.Version, 577 }, 578 } 579 } 580 581 func (p *testNoVerbsStorage) Destroy() { 582 } 583 584 func (p *testNoVerbsStorage) GetSingularName() string { 585 return "noverb" 586 } 587 588 func fakeVersion() version.Info { 589 return version.Info{ 590 Major: "42", 591 Minor: "42", 592 GitVersion: "42.42", 593 GitCommit: "34973274ccef6ab4dfaaf86599792fa9c3fe4689", 594 GitTreeState: "Dirty", 595 BuildDate: time.Now().String(), 596 GoVersion: goruntime.Version(), 597 Compiler: goruntime.Compiler, 598 Platform: fmt.Sprintf("%s/%s", goruntime.GOOS, goruntime.GOARCH), 599 } 600 } 601 602 // TestGracefulShutdown verifies server shutdown after request handler finish. 603 func TestGracefulShutdown(t *testing.T) { 604 config, _ := setUp(t) 605 606 var graceShutdown bool 607 wg := sync.WaitGroup{} 608 wg.Add(1) 609 610 config.BuildHandlerChainFunc = func(apiHandler http.Handler, c *Config) http.Handler { 611 handler := genericfilters.WithWaitGroup(apiHandler, c.LongRunningFunc, c.NonLongRunningRequestWaitGroup) 612 handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver) 613 return handler 614 } 615 616 s, err := config.Complete(nil).New("test", NewEmptyDelegate()) 617 if err != nil { 618 t.Fatalf("Error in bringing up the server: %v", err) 619 } 620 621 twoSecondHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 622 wg.Done() 623 time.Sleep(2 * time.Second) 624 w.WriteHeader(http.StatusOK) 625 graceShutdown = true 626 }) 627 okHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 628 w.WriteHeader(http.StatusOK) 629 }) 630 631 s.Handler.NonGoRestfulMux.Handle("/test", twoSecondHandler) 632 s.Handler.NonGoRestfulMux.Handle("/200", okHandler) 633 634 insecureServer := &http.Server{ 635 Addr: "0.0.0.0:0", 636 Handler: s.Handler, 637 } 638 stopCh := make(chan struct{}) 639 640 ln, err := net.Listen("tcp", insecureServer.Addr) 641 if err != nil { 642 t.Errorf("failed to listen on %v: %v", insecureServer.Addr, err) 643 } 644 645 // get port 646 serverPort := ln.Addr().(*net.TCPAddr).Port 647 stoppedCh, _, err := RunServer(insecureServer, ln, 10*time.Second, stopCh) 648 if err != nil { 649 t.Fatalf("RunServer err: %v", err) 650 } 651 652 graceCh := make(chan struct{}) 653 // mock a client request 654 go func() { 655 resp, err := http.Get("http://127.0.0.1:" + strconv.Itoa(serverPort) + "/test") 656 if err != nil { 657 t.Errorf("Unexpected http error: %v", err) 658 } 659 if resp.StatusCode != http.StatusOK { 660 t.Errorf("Unexpected http status code: %v", resp.StatusCode) 661 } 662 close(graceCh) 663 }() 664 665 // close stopCh after request sent to server to guarantee request handler is running. 666 wg.Wait() 667 close(stopCh) 668 669 time.Sleep(500 * time.Millisecond) 670 if _, err := http.Get("http://127.0.0.1:" + strconv.Itoa(serverPort) + "/200"); err == nil { 671 t.Errorf("Unexpected http success after stopCh was closed") 672 } 673 674 // wait for wait group handler finish 675 s.NonLongRunningRequestWaitGroup.Wait() 676 <-stoppedCh 677 678 // check server all handlers finished. 679 if !graceShutdown { 680 t.Errorf("server shutdown not gracefully.") 681 } 682 // check client to make sure receive response. 683 select { 684 case <-graceCh: 685 t.Logf("server shutdown gracefully.") 686 case <-time.After(30 * time.Second): 687 t.Errorf("Timed out waiting for response.") 688 } 689 } 690 691 func TestWarningWithRequestTimeout(t *testing.T) { 692 type result struct { 693 err interface{} 694 stackTrace string 695 } 696 clientDoneCh, resultCh := make(chan struct{}), make(chan result, 1) 697 testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 698 // this will catch recoverable panic like 'Header called after Handler finished'. 699 // go runtime crashes the program if it detects a program-ending 700 // panic like 'concurrent map iteration and map write', so this 701 // panic can not be caught. 702 defer func() { 703 result := result{} 704 result.err = recover() 705 if result.err != nil { 706 // Same as stdlib http server code. Manually allocate stack 707 // trace buffer size to prevent excessively large logs 708 const size = 64 << 10 709 buf := make([]byte, size) 710 buf = buf[:goruntime.Stack(buf, false)] 711 result.stackTrace = string(buf) 712 } 713 resultCh <- result 714 }() 715 716 // add warnings while we're waiting for the request to timeout to catch read/write races 717 loop: 718 for { 719 select { 720 case <-r.Context().Done(): 721 break loop 722 default: 723 warning.AddWarning(r.Context(), "a", "1") 724 } 725 } 726 // the request has just timed out, write to catch read/write races 727 warning.AddWarning(r.Context(), "agent", "text") 728 729 // give time for the timeout response to be written, then try to 730 // write a response header to catch the "Header after Handler finished" panic 731 <-clientDoneCh 732 733 warning.AddWarning(r.Context(), "agent", "text") 734 }) 735 handler := newGenericAPIServerHandlerChain(t, "/test", testHandler) 736 737 server := httptest.NewUnstartedServer(handler) 738 server.EnableHTTP2 = true 739 server.StartTLS() 740 defer server.Close() 741 742 request, err := http.NewRequest("GET", server.URL+"/test?timeout=100ms", nil) 743 if err != nil { 744 t.Fatalf("unexpected error: %v", err) 745 } 746 747 client := server.Client() 748 response, err := client.Do(request) 749 close(clientDoneCh) 750 if err != nil { 751 t.Errorf("expected server to return an HTTP response: %v", err) 752 } 753 if want := http.StatusGatewayTimeout; response == nil || response.StatusCode != want { 754 t.Errorf("expected server to return %d, but got: %v", want, response) 755 } 756 757 var resultGot result 758 select { 759 case resultGot = <-resultCh: 760 case <-time.After(wait.ForeverTestTimeout): 761 t.Errorf("the handler never returned a result") 762 } 763 if resultGot.err != nil { 764 t.Errorf("Expected no panic, but got: %v", resultGot.err) 765 t.Errorf("Stack Trace: %s", resultGot.stackTrace) 766 } 767 } 768 769 // builds a handler chain with the given user handler as used by GenericAPIServer. 770 func newGenericAPIServerHandlerChain(t *testing.T, path string, handler http.Handler) http.Handler { 771 config, _ := setUp(t) 772 s, err := config.Complete(nil).New("test", NewEmptyDelegate()) 773 if err != nil { 774 t.Fatalf("Error in bringing up the server: %v", err) 775 } 776 777 s.Handler.NonGoRestfulMux.Handle(path, handler) 778 return s.Handler 779 }