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  }