agones.dev/agones@v1.54.0/pkg/util/apiserver/apiserver_test.go (about) 1 // Copyright 2019 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package apiserver 16 17 import ( 18 "encoding/json" 19 "io" 20 "net/http" 21 "net/http/httptest" 22 "testing" 23 24 "github.com/go-openapi/spec" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 k8sruntime "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/kube-openapi/pkg/handler3" 30 ) 31 32 var ( 33 gv = metav1.GroupVersion{Group: "allocation.agones.dev", Version: "v1"} 34 resource = metav1.APIResource{ 35 Name: "gameserverallocations", 36 SingularName: "gameserverallocation", 37 Namespaced: true, 38 Kind: "GameServerAllocation", 39 Verbs: []string{ 40 "create", 41 }, 42 ShortNames: []string{"gsa"}, 43 } 44 ) 45 46 func TestApiServerStubs(t *testing.T) { 47 t.Parallel() 48 // testing API endpoints that don't do a whole lot, but are needed for APIService endpoints. 49 mux := http.NewServeMux() 50 ts := httptest.NewUnstartedServer(mux) 51 _ = NewAPIServer(mux) 52 53 ts.Start() 54 defer ts.Close() 55 client := ts.Client() 56 57 resp, err := client.Get(ts.URL + "/openapi/v2") 58 require.NoError(t, err) 59 assert.Equal(t, k8sruntime.ContentTypeJSON, resp.Header.Get(ContentTypeHeader)) 60 v2data := spec.Swagger{} 61 require.NoError(t, json.NewDecoder(resp.Body).Decode(&v2data)) 62 require.NoError(t, resp.Body.Close()) 63 64 resp, err = client.Get(ts.URL + "/openapi/v3") 65 require.NoError(t, err) 66 assert.Equal(t, k8sruntime.ContentTypeJSON, resp.Header.Get(ContentTypeHeader)) 67 v3data := handler3.OpenAPIV3Discovery{} 68 require.NoError(t, json.NewDecoder(resp.Body).Decode(&v3data)) 69 require.NoError(t, resp.Body.Close()) 70 71 resp, err = client.Get(ts.URL + "/apis") 72 require.NoError(t, err) 73 assert.Equal(t, http.StatusNotAcceptable, resp.StatusCode) 74 require.NoError(t, resp.Body.Close()) 75 } 76 77 func TestAPIServerAddAPIResourceCRDHandler(t *testing.T) { 78 t.Parallel() 79 80 mux := http.NewServeMux() 81 ts := httptest.NewUnstartedServer(mux) 82 api := NewAPIServer(mux) 83 handled := false 84 85 api.AddAPIResource(gv.String(), resource, func(_ http.ResponseWriter, _ *http.Request, ns string) error { 86 handled = true 87 assert.Equal(t, "default", ns) 88 return nil 89 }) 90 91 ts.Start() 92 defer ts.Close() 93 94 client := ts.Client() 95 path := ts.URL + "/apis/allocation.agones.dev/v1/namespaces/default/gameserverallocations" 96 97 resp, err := client.Get(path) 98 assert.NoError(t, err) 99 assert.Equal(t, http.StatusOK, resp.StatusCode) 100 assert.True(t, handled, "not handled!") 101 defer resp.Body.Close() // nolint: errcheck 102 103 handled = false 104 path = ts.URL + "/apis/allocation.agones.dev/v1/namespaces/default/gameserverallZZZZions" 105 resp, err = client.Get(path) 106 assert.NoError(t, err) 107 assert.Equal(t, http.StatusNotFound, resp.StatusCode) 108 assert.False(t, handled, "not handled!") 109 defer resp.Body.Close() // nolint: errcheck 110 } 111 112 func TestAPIServerAddAPIResourceDiscovery(t *testing.T) { 113 t.Parallel() 114 115 mux := http.NewServeMux() 116 ts := httptest.NewUnstartedServer(mux) 117 api := NewAPIServer(mux) 118 119 api.AddAPIResource(gv.String(), resource, func(_ http.ResponseWriter, _ *http.Request, _ string) error { 120 return nil 121 }) 122 123 ts.Start() 124 defer ts.Close() 125 126 client := ts.Client() 127 path := ts.URL + "/apis/allocation.agones.dev/v1" 128 129 t.Run("No Accept Header", func(t *testing.T) { 130 resp, err := client.Get(path) 131 if resp != nil { 132 defer resp.Body.Close() // nolint: errcheck 133 } 134 if !assert.NoError(t, err) { 135 assert.FailNow(t, "should not error") 136 } 137 assert.Equal(t, k8sruntime.ContentTypeJSON, resp.Header.Get("Content-Type")) 138 139 // default is json 140 list := &metav1.APIResourceList{} 141 err = json.NewDecoder(resp.Body).Decode(list) 142 assert.NoError(t, err) 143 144 assert.Equal(t, "v1", list.TypeMeta.APIVersion) 145 assert.Equal(t, "APIResourceList", list.TypeMeta.Kind) 146 assert.Equal(t, gv.String(), list.GroupVersion) 147 assert.Equal(t, resource, list.APIResources[0]) 148 }) 149 150 t.Run("Accept */*", func(t *testing.T) { 151 request, err := http.NewRequest(http.MethodGet, path, nil) 152 assert.NoError(t, err) 153 154 request.Header.Set("Accept", "*/*") 155 resp, err := client.Do(request) 156 assert.NoError(t, err) 157 if resp != nil { 158 defer resp.Body.Close() // nolint: errcheck 159 } 160 assert.Equal(t, k8sruntime.ContentTypeJSON, resp.Header.Get("Content-Type")) 161 162 list := &metav1.APIResourceList{} 163 err = json.NewDecoder(resp.Body).Decode(list) 164 assert.NoError(t, err) 165 166 assert.Equal(t, "v1", list.TypeMeta.APIVersion) 167 assert.Equal(t, "APIResourceList", list.TypeMeta.Kind) 168 assert.Equal(t, gv.String(), list.GroupVersion) 169 assert.Equal(t, resource, list.APIResources[0]) 170 }) 171 172 t.Run("Accept Protobuf, */*", func(t *testing.T) { 173 request, err := http.NewRequest(http.MethodGet, path, nil) 174 assert.NoError(t, err) 175 176 request.Header.Set("Accept", "application/vnd.kubernetes.protobuf, */*") 177 resp, err := client.Do(request) 178 assert.NoError(t, err) 179 if resp != nil { 180 defer resp.Body.Close() // nolint: errcheck 181 } 182 assert.Equal(t, "application/vnd.kubernetes.protobuf", resp.Header.Get("Content-Type")) 183 184 list := &metav1.APIResourceList{} 185 b, err := io.ReadAll(resp.Body) 186 assert.NoError(t, err) 187 188 info, ok := k8sruntime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), "application/vnd.kubernetes.protobuf") 189 assert.True(t, ok) 190 191 gvk := unversionedVersion.WithKind("APIResourceList") 192 _, _, err = info.Serializer.Decode(b, &gvk, list) 193 assert.NoError(t, err) 194 195 assert.Equal(t, "v1", list.TypeMeta.APIVersion) 196 assert.Equal(t, "APIResourceList", list.TypeMeta.Kind) 197 assert.Equal(t, gv.String(), list.GroupVersion) 198 assert.Equal(t, resource, list.APIResources[0]) 199 }) 200 } 201 202 func TestAPIServerAddAPIResourceDiscoveryParallel(t *testing.T) { 203 t.Parallel() 204 mux := http.NewServeMux() 205 ts := httptest.NewUnstartedServer(mux) 206 api := NewAPIServer(mux) 207 208 api.AddAPIResource(gv.String(), resource, func(_ http.ResponseWriter, _ *http.Request, _ string) error { 209 return nil 210 }) 211 212 ts.Start() 213 defer ts.Close() 214 215 t.Run("Parallel Tests", func(t *testing.T) { 216 // Run 10 concurrent requests to exercise multithreading 217 for i := 0; i < 10; i++ { 218 t.Run("Accept */*", func(t *testing.T) { 219 t.Parallel() 220 client := ts.Client() 221 path := ts.URL + "/apis/allocation.agones.dev/v1" 222 request, err := http.NewRequest(http.MethodGet, path, nil) 223 assert.NoError(t, err) 224 225 request.Header.Set("Accept", "*/*") 226 resp, err := client.Do(request) 227 assert.NoError(t, err) 228 if resp != nil { 229 defer resp.Body.Close() // nolint: errcheck 230 } 231 assert.Equal(t, k8sruntime.ContentTypeJSON, resp.Header.Get("Content-Type")) 232 233 list := &metav1.APIResourceList{} 234 err = json.NewDecoder(resp.Body).Decode(list) 235 assert.NoError(t, err) 236 237 assert.Equal(t, "v1", list.TypeMeta.APIVersion) 238 assert.Equal(t, "APIResourceList", list.TypeMeta.Kind) 239 assert.Equal(t, gv.String(), list.GroupVersion) 240 assert.Equal(t, resource, list.APIResources[0]) 241 }) 242 } 243 }) 244 } 245 246 func TestSplitNameSpaceResource(t *testing.T) { 247 type expected struct { 248 namespace string 249 resource string 250 isError bool 251 } 252 253 fixtures := []struct { 254 path string 255 expected expected 256 }{ 257 { 258 path: "/apis/allocation.agones.dev/v1/namespaces/default/gameserverallocations", 259 expected: expected{ 260 namespace: "default", 261 resource: "gameserverallocations", 262 }, 263 }, 264 { 265 path: "/apis/allocation.agones.dev/v1/namespaces/default/gameserverallocations/", 266 expected: expected{ 267 namespace: "default", 268 resource: "gameserverallocations", 269 }, 270 }, 271 { 272 path: "/apis/allocation.agones.dev/v1/", 273 expected: expected{ 274 isError: true, 275 }, 276 }, 277 { 278 path: "/apis/allocation.agones.dev/v1/blarg/default/gameserverallocations/", 279 expected: expected{ 280 isError: true, 281 }, 282 }, 283 } 284 285 for _, test := range fixtures { 286 t.Run(test.path, func(t *testing.T) { 287 n, r, err := splitNameSpaceResource(test.path) 288 if test.expected.isError { 289 assert.Error(t, err) 290 } else { 291 assert.NoError(t, err) 292 } 293 294 assert.Equal(t, test.expected.namespace, n) 295 assert.Equal(t, test.expected.resource, r) 296 }) 297 } 298 }