1
16
17 package aggregator
18
19 import (
20 "bytes"
21 "encoding/json"
22 "net/http"
23 "strings"
24 "testing"
25
26 "github.com/emicklei/go-restful/v3"
27 "github.com/stretchr/testify/assert"
28
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apiserver/pkg/endpoints/metrics"
31 openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
32 genericapiserver "k8s.io/apiserver/pkg/server"
33 "k8s.io/apiserver/pkg/server/mux"
34 "k8s.io/component-base/metrics/legacyregistry"
35 "k8s.io/component-base/metrics/testutil"
36 v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
37 openapicommon "k8s.io/kube-openapi/pkg/common"
38 "k8s.io/kube-openapi/pkg/handler3"
39 kubeopenapispec "k8s.io/kube-openapi/pkg/validation/spec"
40 )
41
42 type testV3APIService struct {
43 etag string
44 data []byte
45 }
46
47 var _ http.Handler = testV3APIService{}
48
49 func (h testV3APIService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
50
51 if r.URL.Path == "/openapi/v3" {
52 group := &handler3.OpenAPIV3Discovery{
53 Paths: map[string]handler3.OpenAPIV3DiscoveryGroupVersion{
54 "apis/group.example.com/v1": {
55 ServerRelativeURL: "/openapi/v3/apis/group.example.com/v1?hash=" + h.etag,
56 },
57 },
58 }
59
60 j, _ := json.Marshal(group)
61 w.Write(j)
62 return
63 }
64
65 if r.URL.Path == "/openapi/v3/apis/group.example.com/v1" {
66 if len(h.etag) > 0 {
67 w.Header().Add("Etag", h.etag)
68 }
69 ifNoneMatches := r.Header["If-None-Match"]
70 for _, match := range ifNoneMatches {
71 if match == h.etag {
72 w.WriteHeader(http.StatusNotModified)
73 return
74 }
75 }
76 w.Write(h.data)
77 }
78 }
79
80 type testV2APIService struct{}
81
82 var _ http.Handler = testV2APIService{}
83
84 func (h testV2APIService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
85
86 if r.URL.Path == "/openapi/v2" {
87 w.Write([]byte(`{"swagger":"2.0","info":{"title":"Kubernetes","version":"unversioned"}}`))
88 return
89 }
90 w.WriteHeader(404)
91 }
92
93 func TestV2APIService(t *testing.T) {
94 downloader := Downloader{}
95 pathHandler := mux.NewPathRecorderMux("aggregator_test")
96 var serveHandler http.Handler = pathHandler
97 specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), nil, nil, pathHandler)
98 if err != nil {
99 t.Error(err)
100 }
101 handler := testV2APIService{}
102 apiService := &v1.APIService{
103 Spec: v1.APIServiceSpec{
104 Group: "group.example.com",
105 Version: "v1",
106 },
107 }
108 apiService.Name = "v1.group.example.com"
109 specProxier.AddUpdateAPIService(handler, apiService)
110 specProxier.UpdateAPIServiceSpec("v1.group.example.com")
111
112 data := sendReq(t, serveHandler, "/openapi/v3")
113 groupVersionList := handler3.OpenAPIV3Discovery{}
114 if err := json.Unmarshal(data, &groupVersionList); err != nil {
115 t.Fatal(err)
116 }
117
118
119
120 path, ok := groupVersionList.Paths["apis/group.example.com/v1"]
121 if !ok {
122 t.Error("Expected group.example.com/v1 to be in group version list")
123 }
124 gotSpecJSON := sendReq(t, serveHandler, path.ServerRelativeURL)
125
126 expectedV3Bytes := []byte(`{"openapi":"3.0.0","info":{"title":"Kubernetes","version":"unversioned"},"components":{}}`)
127
128 if bytes.Compare(gotSpecJSON, expectedV3Bytes) != 0 {
129 t.Errorf("Spec mismatch, expected %s, got %s", expectedV3Bytes, gotSpecJSON)
130 }
131
132 apiServiceNames := specProxier.GetAPIServiceNames()
133 assert.ElementsMatch(t, []string{openAPIV2Converter, apiService.Name}, apiServiceNames)
134
135
136 specProxier.RemoveAPIServiceSpec(apiService.Name)
137 data = sendReq(t, serveHandler, "/openapi/v3")
138 groupVersionList = handler3.OpenAPIV3Discovery{}
139 if err := json.Unmarshal(data, &groupVersionList); err != nil {
140 t.Fatal(err)
141 }
142
143 path, ok = groupVersionList.Paths["apis/group.example.com/v1"]
144 if ok {
145 t.Error("Expected group.example.com/v1 not to be in group version list")
146 }
147 }
148
149 func TestV3APIService(t *testing.T) {
150 downloader := Downloader{}
151
152 pathHandler := mux.NewPathRecorderMux("aggregator_test")
153 var serveHandler http.Handler = pathHandler
154 specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), nil, nil, pathHandler)
155 if err != nil {
156 t.Error(err)
157 }
158 specJSON := []byte(`{"openapi":"3.0.0","info":{"title":"Kubernetes","version":"unversioned"}}`)
159 handler := testV3APIService{
160 etag: "6E8F849B434D4B98A569B9D7718876E9-356ECAB19D7FBE1336BABB1E70F8F3025050DE218BE78256BE81620681CFC9A268508E542B8B55974E17B2184BBFC8FFFAA577E51BE195D32B3CA2547818ABE4",
161 data: specJSON,
162 }
163 apiService := &v1.APIService{
164 Spec: v1.APIServiceSpec{
165 Group: "group.example.com",
166 Version: "v1",
167 },
168 }
169 apiService.Name = "v1.group.example.com"
170 specProxier.AddUpdateAPIService(handler, apiService)
171 specProxier.UpdateAPIServiceSpec("v1.group.example.com")
172
173 data := sendReq(t, serveHandler, "/openapi/v3")
174 groupVersionList := handler3.OpenAPIV3Discovery{}
175 if err := json.Unmarshal(data, &groupVersionList); err != nil {
176 t.Fatal(err)
177 }
178 path, ok := groupVersionList.Paths["apis/group.example.com/v1"]
179 if !ok {
180 t.Error("Expected group.example.com/v1 to be in group version list")
181 }
182 gotSpecJSON := sendReq(t, serveHandler, path.ServerRelativeURL)
183 if bytes.Compare(gotSpecJSON, specJSON) != 0 {
184 t.Errorf("Spec mismatch, expected %s, got %s", specJSON, gotSpecJSON)
185 }
186
187 apiServiceNames := specProxier.GetAPIServiceNames()
188 assert.ElementsMatch(t, []string{openAPIV2Converter, apiService.Name}, apiServiceNames)
189 }
190
191 func TestV3RootAPIService(t *testing.T) {
192 ws := new(restful.WebService)
193 {
194 ws.Path("/apis/apiregistration.k8s.io/v1")
195 ws.Doc("API at/apis/apiregistration.k8s.io/v1 ")
196 ws.Consumes("*/*")
197 ws.Produces("application/json")
198 ws.ApiVersion("apiregistration.k8s.io/v1")
199 routeBuilder := ws.GET("apiservices").
200 To(func(request *restful.Request, response *restful.Response) {}).
201 Doc("list or watch objects of kind APIService").
202 Operation("listAPIService").
203 Produces("application/json").
204 Returns(http.StatusOK, "OK", v1.APIService{}).
205 Writes(v1.APIService{})
206 ws.Route(routeBuilder)
207 }
208 openapiConfig := genericapiserver.DefaultOpenAPIV3Config(getTestAPIServiceOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme()))
209
210 downloader := Downloader{}
211 goRestfulContainer := restful.NewContainer()
212 goRestfulContainer.Add(ws)
213 pathHandler := mux.NewPathRecorderMux("aggregator_test")
214 var serveHandler http.Handler = pathHandler
215 specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), goRestfulContainer, openapiConfig, pathHandler)
216 if err != nil {
217 t.Error(err)
218 }
219 expectedSpecJSON := []byte(`{"openapi":"3.0.0","info":{"title":"Generic API Server"},"paths":{"/apis/apiregistration.k8s.io/v1/apiservices":{"get":{"tags":["apiregistration_v1"],"description":"list or watch objects of kind APIService","operationId":"listApiregistrationV1APIService","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService"}}}}}}}},"components":{"schemas":{"io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService":{"description":"APIService represents a server for a particular GroupVersion. Name must be \"version.group\".","type":"object"}}}}`)
220
221 data := sendReq(t, serveHandler, "/openapi/v3")
222 groupVersionList := handler3.OpenAPIV3Discovery{}
223 if err := json.Unmarshal(data, &groupVersionList); err != nil {
224 t.Fatal(err)
225 }
226 path, ok := groupVersionList.Paths["apis/apiregistration.k8s.io/v1"]
227 if !ok {
228 t.Error("Expected apiregistration.k8s.io/v1 to be in group version list")
229 }
230 gotSpecJSON := sendReq(t, serveHandler, path.ServerRelativeURL)
231 if bytes.Compare(gotSpecJSON, expectedSpecJSON) != 0 {
232 t.Errorf("Spec mismatch, expected %s, got %s", expectedSpecJSON, gotSpecJSON)
233 }
234
235 apiServiceNames := specProxier.GetAPIServiceNames()
236 assert.ElementsMatch(t, []string{"k8s_internal_local_kube_aggregator_types", openAPIV2Converter}, apiServiceNames)
237 }
238
239 func TestOpenAPIRequestMetrics(t *testing.T) {
240 metrics.Register()
241 metrics.Reset()
242
243 downloader := Downloader{}
244
245 pathHandler := mux.NewPathRecorderMux("aggregator_metrics_test")
246 var serveHandler http.Handler = pathHandler
247 specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), nil, nil, pathHandler)
248 if err != nil {
249 t.Error(err)
250 }
251 specJSON := []byte(`{"openapi":"3.0.0","info":{"title":"Kubernetes","version":"unversioned"}}`)
252 handler := testV3APIService{
253 etag: "6E8F849B434D4B98A569B9D7718876E9-356ECAB19D7FBE1336BABB1E70F8F3025050DE218BE78256BE81620681CFC9A268508E542B8B55974E17B2184BBFC8FFFAA577E51BE195D32B3CA2547818ABE4",
254 data: specJSON,
255 }
256 apiService := &v1.APIService{
257 Spec: v1.APIServiceSpec{
258 Group: "group.example.com",
259 Version: "v1",
260 },
261 }
262 apiService.Name = "v1.group.example.com"
263 specProxier.AddUpdateAPIService(handler, apiService)
264 specProxier.UpdateAPIServiceSpec("v1.group.example.com")
265
266 data := sendReq(t, serveHandler, "/openapi/v3")
267 groupVersionList := handler3.OpenAPIV3Discovery{}
268 if err := json.Unmarshal(data, &groupVersionList); err != nil {
269 t.Fatal(err)
270 }
271 _, ok := groupVersionList.Paths["apis/group.example.com/v1"]
272 if !ok {
273 t.Error("Expected group.example.com/v1 to be in group version list")
274 }
275
276
277 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(`
278 # HELP apiserver_request_total [STABLE] Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code.
279 # TYPE apiserver_request_total counter
280 apiserver_request_total{code="200",component="",dry_run="",group="",resource="",scope="",subresource="openapi/v3",verb="GET",version=""} 1
281 `), "apiserver_request_total"); err != nil {
282 t.Fatal(err)
283 }
284
285 _ = sendReq(t, serveHandler, "/openapi/v3/apis/group.example.com/v1")
286
287
288 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(`
289 # HELP apiserver_request_total [STABLE] Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code.
290 # TYPE apiserver_request_total counter
291 apiserver_request_total{code="200",component="",dry_run="",group="",resource="",scope="",subresource="openapi/v3",verb="GET",version=""} 1
292 apiserver_request_total{code="200",component="",dry_run="",group="",resource="",scope="",subresource="openapi/v3/",verb="GET",version=""} 1
293 `), "apiserver_request_total"); err != nil {
294 t.Fatal(err)
295 }
296
297 }
298
299 func sendReq(t *testing.T, handler http.Handler, path string) []byte {
300 req, err := http.NewRequest("GET", path, nil)
301 if err != nil {
302 t.Fatal(err)
303 }
304 writer := newInMemoryResponseWriter()
305 handler.ServeHTTP(writer, req)
306 return writer.data
307 }
308
309 func getTestAPIServiceOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapicommon.OpenAPIDefinition {
310 return map[string]openapicommon.OpenAPIDefinition{
311 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1.APIService": buildTestAPIServiceOpenAPIDefinition(),
312 }
313 }
314
315 func buildTestAPIServiceOpenAPIDefinition() openapicommon.OpenAPIDefinition {
316 return openapicommon.OpenAPIDefinition{
317 Schema: kubeopenapispec.Schema{
318 SchemaProps: kubeopenapispec.SchemaProps{
319 Description: "APIService represents a server for a particular GroupVersion. Name must be \"version.group\".",
320 Type: []string{"object"},
321 },
322 },
323 }
324 }
325
View as plain text