handlers_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package apiserver
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "net/http"
  18. "net/http/httptest"
  19. "reflect"
  20. "regexp"
  21. "strings"
  22. "sync"
  23. "testing"
  24. "time"
  25. "k8s.io/kubernetes/pkg/api"
  26. "k8s.io/kubernetes/pkg/api/errors"
  27. "k8s.io/kubernetes/pkg/api/testapi"
  28. "k8s.io/kubernetes/pkg/apis/extensions"
  29. "k8s.io/kubernetes/pkg/auth/authorizer"
  30. "k8s.io/kubernetes/pkg/util/sets"
  31. )
  32. type fakeRL bool
  33. func (fakeRL) Stop() {}
  34. func (f fakeRL) TryAccept() bool { return bool(f) }
  35. func (f fakeRL) Accept() {}
  36. func expectHTTP(url string, code int) error {
  37. r, err := http.Get(url)
  38. if err != nil {
  39. return fmt.Errorf("unexpected error: %v", err)
  40. }
  41. if r.StatusCode != code {
  42. return fmt.Errorf("unexpected response: %v", r.StatusCode)
  43. }
  44. return nil
  45. }
  46. func getPath(resource, namespace, name string) string {
  47. return testapi.Default.ResourcePath(resource, namespace, name)
  48. }
  49. func pathWithPrefix(prefix, resource, namespace, name string) string {
  50. return testapi.Default.ResourcePathWithPrefix(prefix, resource, namespace, name)
  51. }
  52. // Tests that MaxInFlightLimit works, i.e.
  53. // - "long" requests such as proxy or watch, identified by regexp are not accounted despite
  54. // hanging for the long time,
  55. // - "short" requests are correctly accounted, i.e. there can be only size of channel passed to the
  56. // constructor in flight at any given moment,
  57. // - subsequent "short" requests are rejected instantly with appropriate error,
  58. // - subsequent "long" requests are handled normally,
  59. // - we correctly recover after some "short" requests finish, i.e. we can process new ones.
  60. func TestMaxInFlight(t *testing.T) {
  61. const AllowedInflightRequestsNo = 3
  62. // Size of inflightRequestsChannel determines how many concurrent inflight requests
  63. // are allowed.
  64. inflightRequestsChannel := make(chan bool, AllowedInflightRequestsNo)
  65. // notAccountedPathsRegexp specifies paths requests to which we don't account into
  66. // requests in flight.
  67. notAccountedPathsRegexp := regexp.MustCompile(".*\\/watch")
  68. longRunningRequestCheck := BasicLongRunningRequestCheck(notAccountedPathsRegexp, map[string]string{"watch": "true"})
  69. // Calls is used to wait until all server calls are received. We are sending
  70. // AllowedInflightRequestsNo of 'long' not-accounted requests and the same number of
  71. // 'short' accounted ones.
  72. calls := &sync.WaitGroup{}
  73. calls.Add(AllowedInflightRequestsNo * 2)
  74. // Responses is used to wait until all responses are
  75. // received. This prevents some async requests getting EOF
  76. // errors from prematurely closing the server
  77. responses := sync.WaitGroup{}
  78. responses.Add(AllowedInflightRequestsNo * 2)
  79. // Block is used to keep requests in flight for as long as we need to. All requests will
  80. // be unblocked at the same time.
  81. block := sync.WaitGroup{}
  82. block.Add(1)
  83. server := httptest.NewServer(
  84. MaxInFlightLimit(
  85. inflightRequestsChannel,
  86. longRunningRequestCheck,
  87. http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  88. // A short, accounted request that does not wait for block WaitGroup.
  89. if strings.Contains(r.URL.Path, "dontwait") {
  90. return
  91. }
  92. if calls != nil {
  93. calls.Done()
  94. }
  95. block.Wait()
  96. }),
  97. ),
  98. )
  99. defer server.Close()
  100. // These should hang, but not affect accounting. use a query param match
  101. for i := 0; i < AllowedInflightRequestsNo; i++ {
  102. // These should hang waiting on block...
  103. go func() {
  104. if err := expectHTTP(server.URL+"/foo/bar?watch=true", http.StatusOK); err != nil {
  105. t.Error(err)
  106. }
  107. responses.Done()
  108. }()
  109. }
  110. // Check that sever is not saturated by not-accounted calls
  111. if err := expectHTTP(server.URL+"/dontwait", http.StatusOK); err != nil {
  112. t.Error(err)
  113. }
  114. // These should hang and be accounted, i.e. saturate the server
  115. for i := 0; i < AllowedInflightRequestsNo; i++ {
  116. // These should hang waiting on block...
  117. go func() {
  118. if err := expectHTTP(server.URL, http.StatusOK); err != nil {
  119. t.Error(err)
  120. }
  121. responses.Done()
  122. }()
  123. }
  124. // We wait for all calls to be received by the server
  125. calls.Wait()
  126. // Disable calls notifications in the server
  127. calls = nil
  128. // Do this multiple times to show that it rate limit rejected requests don't block.
  129. for i := 0; i < 2; i++ {
  130. if err := expectHTTP(server.URL, errors.StatusTooManyRequests); err != nil {
  131. t.Error(err)
  132. }
  133. }
  134. // Validate that non-accounted URLs still work. use a path regex match
  135. if err := expectHTTP(server.URL+"/dontwait/watch", http.StatusOK); err != nil {
  136. t.Error(err)
  137. }
  138. // Let all hanging requests finish
  139. block.Done()
  140. // Show that we recover from being blocked up.
  141. // Too avoid flakyness we need to wait until at least one of the requests really finishes.
  142. responses.Wait()
  143. if err := expectHTTP(server.URL, http.StatusOK); err != nil {
  144. t.Error(err)
  145. }
  146. }
  147. func TestReadOnly(t *testing.T) {
  148. server := httptest.NewServer(ReadOnly(http.HandlerFunc(
  149. func(w http.ResponseWriter, req *http.Request) {
  150. if req.Method != "GET" {
  151. t.Errorf("Unexpected call: %v", req.Method)
  152. }
  153. },
  154. )))
  155. defer server.Close()
  156. for _, verb := range []string{"GET", "POST", "PUT", "DELETE", "CREATE"} {
  157. req, err := http.NewRequest(verb, server.URL, nil)
  158. if err != nil {
  159. t.Fatalf("Couldn't make request: %v", err)
  160. }
  161. http.DefaultClient.Do(req)
  162. }
  163. }
  164. func TestTimeout(t *testing.T) {
  165. sendResponse := make(chan struct{}, 1)
  166. writeErrors := make(chan error, 1)
  167. timeout := make(chan time.Time, 1)
  168. resp := "test response"
  169. timeoutResp := "test timeout"
  170. ts := httptest.NewServer(TimeoutHandler(http.HandlerFunc(
  171. func(w http.ResponseWriter, r *http.Request) {
  172. <-sendResponse
  173. _, err := w.Write([]byte(resp))
  174. writeErrors <- err
  175. }),
  176. func(*http.Request) (<-chan time.Time, string) {
  177. return timeout, timeoutResp
  178. }))
  179. defer ts.Close()
  180. // No timeouts
  181. sendResponse <- struct{}{}
  182. res, err := http.Get(ts.URL)
  183. if err != nil {
  184. t.Error(err)
  185. }
  186. if res.StatusCode != http.StatusOK {
  187. t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusOK)
  188. }
  189. body, _ := ioutil.ReadAll(res.Body)
  190. if string(body) != resp {
  191. t.Errorf("got body %q; expected %q", string(body), resp)
  192. }
  193. if err := <-writeErrors; err != nil {
  194. t.Errorf("got unexpected Write error on first request: %v", err)
  195. }
  196. // Times out
  197. timeout <- time.Time{}
  198. res, err = http.Get(ts.URL)
  199. if err != nil {
  200. t.Error(err)
  201. }
  202. if res.StatusCode != http.StatusGatewayTimeout {
  203. t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusServiceUnavailable)
  204. }
  205. body, _ = ioutil.ReadAll(res.Body)
  206. if string(body) != timeoutResp {
  207. t.Errorf("got body %q; expected %q", string(body), timeoutResp)
  208. }
  209. // Now try to send a response
  210. sendResponse <- struct{}{}
  211. if err := <-writeErrors; err != http.ErrHandlerTimeout {
  212. t.Errorf("got Write error of %v; expected %v", err, http.ErrHandlerTimeout)
  213. }
  214. }
  215. func TestGetAttribs(t *testing.T) {
  216. r := &requestAttributeGetter{api.NewRequestContextMapper(), &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}}
  217. testcases := map[string]struct {
  218. Verb string
  219. Path string
  220. ExpectedAttributes *authorizer.AttributesRecord
  221. }{
  222. "non-resource root": {
  223. Verb: "POST",
  224. Path: "/",
  225. ExpectedAttributes: &authorizer.AttributesRecord{
  226. Verb: "post",
  227. Path: "/",
  228. },
  229. },
  230. "non-resource api prefix": {
  231. Verb: "GET",
  232. Path: "/api/",
  233. ExpectedAttributes: &authorizer.AttributesRecord{
  234. Verb: "get",
  235. Path: "/api/",
  236. },
  237. },
  238. "non-resource group api prefix": {
  239. Verb: "GET",
  240. Path: "/apis/extensions/",
  241. ExpectedAttributes: &authorizer.AttributesRecord{
  242. Verb: "get",
  243. Path: "/apis/extensions/",
  244. },
  245. },
  246. "resource": {
  247. Verb: "POST",
  248. Path: "/api/v1/nodes/mynode",
  249. ExpectedAttributes: &authorizer.AttributesRecord{
  250. Verb: "create",
  251. Path: "/api/v1/nodes/mynode",
  252. ResourceRequest: true,
  253. Resource: "nodes",
  254. APIVersion: "v1",
  255. Name: "mynode",
  256. },
  257. },
  258. "namespaced resource": {
  259. Verb: "PUT",
  260. Path: "/api/v1/namespaces/myns/pods/mypod",
  261. ExpectedAttributes: &authorizer.AttributesRecord{
  262. Verb: "update",
  263. Path: "/api/v1/namespaces/myns/pods/mypod",
  264. ResourceRequest: true,
  265. Namespace: "myns",
  266. Resource: "pods",
  267. APIVersion: "v1",
  268. Name: "mypod",
  269. },
  270. },
  271. "API group resource": {
  272. Verb: "GET",
  273. Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
  274. ExpectedAttributes: &authorizer.AttributesRecord{
  275. Verb: "list",
  276. Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
  277. ResourceRequest: true,
  278. APIGroup: extensions.GroupName,
  279. APIVersion: "v1beta1",
  280. Namespace: "myns",
  281. Resource: "jobs",
  282. },
  283. },
  284. }
  285. for k, tc := range testcases {
  286. req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
  287. attribs := r.GetAttribs(req)
  288. if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
  289. t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
  290. }
  291. }
  292. }
  293. func TestGetAPIRequestInfo(t *testing.T) {
  294. successCases := []struct {
  295. method string
  296. url string
  297. expectedVerb string
  298. expectedAPIPrefix string
  299. expectedAPIGroup string
  300. expectedAPIVersion string
  301. expectedNamespace string
  302. expectedResource string
  303. expectedSubresource string
  304. expectedName string
  305. expectedParts []string
  306. }{
  307. // resource paths
  308. {"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}},
  309. {"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "other", []string{"namespaces", "other"}},
  310. {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
  311. {"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  312. {"HEAD", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  313. {"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
  314. {"HEAD", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
  315. {"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  316. {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
  317. // special verbs
  318. {"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  319. {"GET", "/api/v1/proxy/namespaces/other/pods/foo/subpath/not/a/subresource", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}},
  320. {"GET", "/api/v1/redirect/namespaces/other/pods/foo", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  321. {"GET", "/api/v1/redirect/namespaces/other/pods/foo/subpath/not/a/subresource", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}},
  322. {"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
  323. {"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
  324. // subresource identification
  325. {"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "foo", []string{"pods", "foo", "status"}},
  326. {"GET", "/api/v1/namespaces/other/pods/foo/proxy/subpath", "get", "api", "", "v1", "other", "pods", "proxy", "foo", []string{"pods", "foo", "proxy", "subpath"}},
  327. {"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "namespaces", "finalize", "other", []string{"namespaces", "other", "finalize"}},
  328. {"PUT", "/api/v1/namespaces/other/status", "update", "api", "", "v1", "other", "namespaces", "status", "other", []string{"namespaces", "other", "status"}},
  329. // verb identification
  330. {"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  331. {"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
  332. {"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
  333. // deletecollection verb identification
  334. {"DELETE", "/api/v1/nodes", "deletecollection", "api", "", "v1", "", "nodes", "", "", []string{"nodes"}},
  335. {"DELETE", "/api/v1/namespaces", "deletecollection", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}},
  336. {"DELETE", "/api/v1/namespaces/other/pods", "deletecollection", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
  337. {"DELETE", "/apis/extensions/v1/namespaces/other/pods", "deletecollection", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}},
  338. // api group identification
  339. {"POST", "/apis/extensions/v1/namespaces/other/pods", "create", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}},
  340. // api version identification
  341. {"POST", "/apis/extensions/v1beta3/namespaces/other/pods", "create", "api", "extensions", "v1beta3", "other", "pods", "", "", []string{"pods"}},
  342. }
  343. requestInfoResolver := newTestRequestInfoResolver()
  344. for _, successCase := range successCases {
  345. req, _ := http.NewRequest(successCase.method, successCase.url, nil)
  346. apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req)
  347. if err != nil {
  348. t.Errorf("Unexpected error for url: %s %v", successCase.url, err)
  349. }
  350. if !apiRequestInfo.IsResourceRequest {
  351. t.Errorf("Expected resource request")
  352. }
  353. if successCase.expectedVerb != apiRequestInfo.Verb {
  354. t.Errorf("Unexpected verb for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedVerb, apiRequestInfo.Verb)
  355. }
  356. if successCase.expectedAPIVersion != apiRequestInfo.APIVersion {
  357. t.Errorf("Unexpected apiVersion for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedAPIVersion, apiRequestInfo.APIVersion)
  358. }
  359. if successCase.expectedNamespace != apiRequestInfo.Namespace {
  360. t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
  361. }
  362. if successCase.expectedResource != apiRequestInfo.Resource {
  363. t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
  364. }
  365. if successCase.expectedSubresource != apiRequestInfo.Subresource {
  366. t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedSubresource, apiRequestInfo.Subresource)
  367. }
  368. if successCase.expectedName != apiRequestInfo.Name {
  369. t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name)
  370. }
  371. if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
  372. t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
  373. }
  374. }
  375. errorCases := map[string]string{
  376. "no resource path": "/",
  377. "just apiversion": "/api/version/",
  378. "just prefix, group, version": "/apis/group/version/",
  379. "apiversion with no resource": "/api/version/",
  380. "bad prefix": "/badprefix/version/resource",
  381. "missing api group": "/apis/version/resource",
  382. }
  383. for k, v := range errorCases {
  384. req, err := http.NewRequest("GET", v, nil)
  385. if err != nil {
  386. t.Errorf("Unexpected error %v", err)
  387. }
  388. apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req)
  389. if err != nil {
  390. t.Errorf("%s: Unexpected error %v", k, err)
  391. }
  392. if apiRequestInfo.IsResourceRequest {
  393. t.Errorf("%s: expected non-resource request", k)
  394. }
  395. }
  396. }
  397. func TestGetNonAPIRequestInfo(t *testing.T) {
  398. tests := map[string]struct {
  399. url string
  400. expected bool
  401. }{
  402. "simple groupless": {"/api/version/resource", true},
  403. "simple group": {"/apis/group/version/resource/name/subresource", true},
  404. "more steps": {"/api/version/resource/name/subresource", true},
  405. "group list": {"/apis/extensions/v1beta1/job", true},
  406. "group get": {"/apis/extensions/v1beta1/job/foo", true},
  407. "group subresource": {"/apis/extensions/v1beta1/job/foo/scale", true},
  408. "bad root": {"/not-api/version/resource", false},
  409. "group without enough steps": {"/apis/extensions/v1beta1", false},
  410. "group without enough steps 2": {"/apis/extensions/v1beta1/", false},
  411. "not enough steps": {"/api/version", false},
  412. "one step": {"/api", false},
  413. "zero step": {"/", false},
  414. "empty": {"", false},
  415. }
  416. requestInfoResolver := newTestRequestInfoResolver()
  417. for testName, tc := range tests {
  418. req, _ := http.NewRequest("GET", tc.url, nil)
  419. apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req)
  420. if err != nil {
  421. t.Errorf("%s: Unexpected error %v", testName, err)
  422. }
  423. if e, a := tc.expected, apiRequestInfo.IsResourceRequest; e != a {
  424. t.Errorf("%s: expected %v, actual %v", testName, e, a)
  425. }
  426. }
  427. }