api_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. // Copyright 2014 Google Inc. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. // +build !appengine
  5. package internal
  6. import (
  7. "bufio"
  8. "bytes"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. "net/http"
  13. "net/http/httptest"
  14. "net/url"
  15. "os"
  16. "os/exec"
  17. "strings"
  18. "sync/atomic"
  19. "testing"
  20. "time"
  21. "github.com/golang/protobuf/proto"
  22. netcontext "golang.org/x/net/context"
  23. basepb "google.golang.org/appengine/internal/base"
  24. remotepb "google.golang.org/appengine/internal/remote_api"
  25. )
  26. const testTicketHeader = "X-Magic-Ticket-Header"
  27. func init() {
  28. ticketHeader = testTicketHeader
  29. }
  30. type fakeAPIHandler struct {
  31. hang chan int // used for RunSlowly RPC
  32. LogFlushes int32 // atomic
  33. }
  34. func (f *fakeAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  35. writeResponse := func(res *remotepb.Response) {
  36. hresBody, err := proto.Marshal(res)
  37. if err != nil {
  38. http.Error(w, fmt.Sprintf("Failed encoding API response: %v", err), 500)
  39. return
  40. }
  41. w.Write(hresBody)
  42. }
  43. if r.URL.Path != "/rpc_http" {
  44. http.NotFound(w, r)
  45. return
  46. }
  47. hreqBody, err := ioutil.ReadAll(r.Body)
  48. if err != nil {
  49. http.Error(w, fmt.Sprintf("Bad body: %v", err), 500)
  50. return
  51. }
  52. apiReq := &remotepb.Request{}
  53. if err := proto.Unmarshal(hreqBody, apiReq); err != nil {
  54. http.Error(w, fmt.Sprintf("Bad encoded API request: %v", err), 500)
  55. return
  56. }
  57. if *apiReq.RequestId != "s3cr3t" && *apiReq.RequestId != DefaultTicket() {
  58. writeResponse(&remotepb.Response{
  59. RpcError: &remotepb.RpcError{
  60. Code: proto.Int32(int32(remotepb.RpcError_SECURITY_VIOLATION)),
  61. Detail: proto.String("bad security ticket"),
  62. },
  63. })
  64. return
  65. }
  66. if got, want := r.Header.Get(dapperHeader), "trace-001"; got != want {
  67. writeResponse(&remotepb.Response{
  68. RpcError: &remotepb.RpcError{
  69. Code: proto.Int32(int32(remotepb.RpcError_BAD_REQUEST)),
  70. Detail: proto.String(fmt.Sprintf("trace info = %q, want %q", got, want)),
  71. },
  72. })
  73. return
  74. }
  75. service, method := *apiReq.ServiceName, *apiReq.Method
  76. var resOut proto.Message
  77. if service == "actordb" && method == "LookupActor" {
  78. req := &basepb.StringProto{}
  79. res := &basepb.StringProto{}
  80. if err := proto.Unmarshal(apiReq.Request, req); err != nil {
  81. http.Error(w, fmt.Sprintf("Bad encoded request: %v", err), 500)
  82. return
  83. }
  84. if *req.Value == "Doctor Who" {
  85. res.Value = proto.String("David Tennant")
  86. }
  87. resOut = res
  88. }
  89. if service == "errors" {
  90. switch method {
  91. case "Non200":
  92. http.Error(w, "I'm a little teapot.", 418)
  93. return
  94. case "ShortResponse":
  95. w.Header().Set("Content-Length", "100")
  96. w.Write([]byte("way too short"))
  97. return
  98. case "OverQuota":
  99. writeResponse(&remotepb.Response{
  100. RpcError: &remotepb.RpcError{
  101. Code: proto.Int32(int32(remotepb.RpcError_OVER_QUOTA)),
  102. Detail: proto.String("you are hogging the resources!"),
  103. },
  104. })
  105. return
  106. case "RunSlowly":
  107. // TestAPICallRPCFailure creates f.hang, but does not strobe it
  108. // until Call returns with remotepb.RpcError_CANCELLED.
  109. // This is here to force a happens-before relationship between
  110. // the httptest server handler and shutdown.
  111. <-f.hang
  112. resOut = &basepb.VoidProto{}
  113. }
  114. }
  115. if service == "logservice" && method == "Flush" {
  116. // Pretend log flushing is slow.
  117. time.Sleep(50 * time.Millisecond)
  118. atomic.AddInt32(&f.LogFlushes, 1)
  119. resOut = &basepb.VoidProto{}
  120. }
  121. encOut, err := proto.Marshal(resOut)
  122. if err != nil {
  123. http.Error(w, fmt.Sprintf("Failed encoding response: %v", err), 500)
  124. return
  125. }
  126. writeResponse(&remotepb.Response{
  127. Response: encOut,
  128. })
  129. }
  130. func setup() (f *fakeAPIHandler, c *context, cleanup func()) {
  131. f = &fakeAPIHandler{}
  132. srv := httptest.NewServer(f)
  133. u, err := url.Parse(srv.URL + apiPath)
  134. if err != nil {
  135. panic(fmt.Sprintf("url.Parse(%q): %v", srv.URL+apiPath, err))
  136. }
  137. return f, &context{
  138. req: &http.Request{
  139. Header: http.Header{
  140. ticketHeader: []string{"s3cr3t"},
  141. dapperHeader: []string{"trace-001"},
  142. },
  143. },
  144. apiURL: u,
  145. }, srv.Close
  146. }
  147. func TestAPICall(t *testing.T) {
  148. _, c, cleanup := setup()
  149. defer cleanup()
  150. req := &basepb.StringProto{
  151. Value: proto.String("Doctor Who"),
  152. }
  153. res := &basepb.StringProto{}
  154. err := Call(toContext(c), "actordb", "LookupActor", req, res)
  155. if err != nil {
  156. t.Fatalf("API call failed: %v", err)
  157. }
  158. if got, want := *res.Value, "David Tennant"; got != want {
  159. t.Errorf("Response is %q, want %q", got, want)
  160. }
  161. }
  162. func TestAPICallTicketUnavailable(t *testing.T) {
  163. resetEnv := SetTestEnv()
  164. defer resetEnv()
  165. _, c, cleanup := setup()
  166. defer cleanup()
  167. c.req.Header.Set(ticketHeader, "")
  168. req := &basepb.StringProto{
  169. Value: proto.String("Doctor Who"),
  170. }
  171. res := &basepb.StringProto{}
  172. err := Call(toContext(c), "actordb", "LookupActor", req, res)
  173. if err != nil {
  174. t.Fatalf("API call failed: %v", err)
  175. }
  176. if got, want := *res.Value, "David Tennant"; got != want {
  177. t.Errorf("Response is %q, want %q", got, want)
  178. }
  179. }
  180. func TestAPICallRPCFailure(t *testing.T) {
  181. f, c, cleanup := setup()
  182. defer cleanup()
  183. testCases := []struct {
  184. method string
  185. code remotepb.RpcError_ErrorCode
  186. }{
  187. {"Non200", remotepb.RpcError_UNKNOWN},
  188. {"ShortResponse", remotepb.RpcError_UNKNOWN},
  189. {"OverQuota", remotepb.RpcError_OVER_QUOTA},
  190. {"RunSlowly", remotepb.RpcError_CANCELLED},
  191. }
  192. f.hang = make(chan int) // only for RunSlowly
  193. for _, tc := range testCases {
  194. ctx, _ := netcontext.WithTimeout(toContext(c), 100*time.Millisecond)
  195. err := Call(ctx, "errors", tc.method, &basepb.VoidProto{}, &basepb.VoidProto{})
  196. ce, ok := err.(*CallError)
  197. if !ok {
  198. t.Errorf("%s: API call error is %T (%v), want *CallError", tc.method, err, err)
  199. continue
  200. }
  201. if ce.Code != int32(tc.code) {
  202. t.Errorf("%s: ce.Code = %d, want %d", tc.method, ce.Code, tc.code)
  203. }
  204. if tc.method == "RunSlowly" {
  205. f.hang <- 1 // release the HTTP handler
  206. }
  207. }
  208. }
  209. func TestAPICallDialFailure(t *testing.T) {
  210. // See what happens if the API host is unresponsive.
  211. // This should time out quickly, not hang forever.
  212. _, c, cleanup := setup()
  213. defer cleanup()
  214. // Reset the URL to the production address so that dialing fails.
  215. c.apiURL = apiURL()
  216. start := time.Now()
  217. err := Call(toContext(c), "foo", "bar", &basepb.VoidProto{}, &basepb.VoidProto{})
  218. const max = 1 * time.Second
  219. if taken := time.Since(start); taken > max {
  220. t.Errorf("Dial hang took too long: %v > %v", taken, max)
  221. }
  222. if err == nil {
  223. t.Error("Call did not fail")
  224. }
  225. }
  226. func TestDelayedLogFlushing(t *testing.T) {
  227. f, c, cleanup := setup()
  228. defer cleanup()
  229. http.HandleFunc("/slow_log", func(w http.ResponseWriter, r *http.Request) {
  230. logC := WithContext(netcontext.Background(), r)
  231. fromContext(logC).apiURL = c.apiURL // Otherwise it will try to use the default URL.
  232. Logf(logC, 1, "It's a lovely day.")
  233. w.WriteHeader(200)
  234. time.Sleep(1200 * time.Millisecond)
  235. w.Write(make([]byte, 100<<10)) // write 100 KB to force HTTP flush
  236. })
  237. r := &http.Request{
  238. Method: "GET",
  239. URL: &url.URL{
  240. Scheme: "http",
  241. Path: "/slow_log",
  242. },
  243. Header: c.req.Header,
  244. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  245. }
  246. w := httptest.NewRecorder()
  247. handled := make(chan struct{})
  248. go func() {
  249. defer close(handled)
  250. handleHTTP(w, r)
  251. }()
  252. // Check that the log flush eventually comes in.
  253. time.Sleep(1200 * time.Millisecond)
  254. if f := atomic.LoadInt32(&f.LogFlushes); f != 1 {
  255. t.Errorf("After 1.2s: f.LogFlushes = %d, want 1", f)
  256. }
  257. <-handled
  258. const hdr = "X-AppEngine-Log-Flush-Count"
  259. if got, want := w.HeaderMap.Get(hdr), "1"; got != want {
  260. t.Errorf("%s header = %q, want %q", hdr, got, want)
  261. }
  262. if got, want := atomic.LoadInt32(&f.LogFlushes), int32(2); got != want {
  263. t.Errorf("After HTTP response: f.LogFlushes = %d, want %d", got, want)
  264. }
  265. }
  266. func TestLogFlushing(t *testing.T) {
  267. f, c, cleanup := setup()
  268. defer cleanup()
  269. http.HandleFunc("/quick_log", func(w http.ResponseWriter, r *http.Request) {
  270. logC := WithContext(netcontext.Background(), r)
  271. fromContext(logC).apiURL = c.apiURL // Otherwise it will try to use the default URL.
  272. Logf(logC, 1, "It's a lovely day.")
  273. w.WriteHeader(200)
  274. w.Write(make([]byte, 100<<10)) // write 100 KB to force HTTP flush
  275. })
  276. r := &http.Request{
  277. Method: "GET",
  278. URL: &url.URL{
  279. Scheme: "http",
  280. Path: "/quick_log",
  281. },
  282. Header: c.req.Header,
  283. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  284. }
  285. w := httptest.NewRecorder()
  286. handleHTTP(w, r)
  287. const hdr = "X-AppEngine-Log-Flush-Count"
  288. if got, want := w.HeaderMap.Get(hdr), "1"; got != want {
  289. t.Errorf("%s header = %q, want %q", hdr, got, want)
  290. }
  291. if got, want := atomic.LoadInt32(&f.LogFlushes), int32(1); got != want {
  292. t.Errorf("After HTTP response: f.LogFlushes = %d, want %d", got, want)
  293. }
  294. }
  295. func TestRemoteAddr(t *testing.T) {
  296. var addr string
  297. http.HandleFunc("/remote_addr", func(w http.ResponseWriter, r *http.Request) {
  298. addr = r.RemoteAddr
  299. })
  300. testCases := []struct {
  301. headers http.Header
  302. addr string
  303. }{
  304. {http.Header{"X-Appengine-User-Ip": []string{"10.5.2.1"}}, "10.5.2.1:80"},
  305. {http.Header{"X-Appengine-Remote-Addr": []string{"1.2.3.4"}}, "1.2.3.4:80"},
  306. {http.Header{"X-Appengine-Remote-Addr": []string{"1.2.3.4:8080"}}, "1.2.3.4:8080"},
  307. {
  308. http.Header{"X-Appengine-Remote-Addr": []string{"2401:fa00:9:1:7646:a0ff:fe90:ca66"}},
  309. "[2401:fa00:9:1:7646:a0ff:fe90:ca66]:80",
  310. },
  311. {
  312. http.Header{"X-Appengine-Remote-Addr": []string{"[::1]:http"}},
  313. "[::1]:http",
  314. },
  315. {http.Header{}, "127.0.0.1:80"},
  316. }
  317. for _, tc := range testCases {
  318. r := &http.Request{
  319. Method: "GET",
  320. URL: &url.URL{Scheme: "http", Path: "/remote_addr"},
  321. Header: tc.headers,
  322. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  323. }
  324. handleHTTP(httptest.NewRecorder(), r)
  325. if addr != tc.addr {
  326. t.Errorf("Header %v, got %q, want %q", tc.headers, addr, tc.addr)
  327. }
  328. }
  329. }
  330. func TestPanickingHandler(t *testing.T) {
  331. http.HandleFunc("/panic", func(http.ResponseWriter, *http.Request) {
  332. panic("whoops!")
  333. })
  334. r := &http.Request{
  335. Method: "GET",
  336. URL: &url.URL{Scheme: "http", Path: "/panic"},
  337. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  338. }
  339. rec := httptest.NewRecorder()
  340. handleHTTP(rec, r)
  341. if rec.Code != 500 {
  342. t.Errorf("Panicking handler returned HTTP %d, want HTTP %d", rec.Code, 500)
  343. }
  344. }
  345. var raceDetector = false
  346. func TestAPICallAllocations(t *testing.T) {
  347. if raceDetector {
  348. t.Skip("not running under race detector")
  349. }
  350. // Run the test API server in a subprocess so we aren't counting its allocations.
  351. u, cleanup := launchHelperProcess(t)
  352. defer cleanup()
  353. c := &context{
  354. req: &http.Request{
  355. Header: http.Header{
  356. ticketHeader: []string{"s3cr3t"},
  357. dapperHeader: []string{"trace-001"},
  358. },
  359. },
  360. apiURL: u,
  361. }
  362. req := &basepb.StringProto{
  363. Value: proto.String("Doctor Who"),
  364. }
  365. res := &basepb.StringProto{}
  366. var apiErr error
  367. avg := testing.AllocsPerRun(100, func() {
  368. ctx, _ := netcontext.WithTimeout(toContext(c), 100*time.Millisecond)
  369. if err := Call(ctx, "actordb", "LookupActor", req, res); err != nil && apiErr == nil {
  370. apiErr = err // get the first error only
  371. }
  372. })
  373. if apiErr != nil {
  374. t.Errorf("API call failed: %v", apiErr)
  375. }
  376. // Lots of room for improvement...
  377. const min, max float64 = 60, 85
  378. if avg < min || max < avg {
  379. t.Errorf("Allocations per API call = %g, want in [%g,%g]", avg, min, max)
  380. }
  381. }
  382. func launchHelperProcess(t *testing.T) (apiURL *url.URL, cleanup func()) {
  383. cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess")
  384. cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
  385. stdin, err := cmd.StdinPipe()
  386. if err != nil {
  387. t.Fatalf("StdinPipe: %v", err)
  388. }
  389. stdout, err := cmd.StdoutPipe()
  390. if err != nil {
  391. t.Fatalf("StdoutPipe: %v", err)
  392. }
  393. if err := cmd.Start(); err != nil {
  394. t.Fatalf("Starting helper process: %v", err)
  395. }
  396. scan := bufio.NewScanner(stdout)
  397. var u *url.URL
  398. for scan.Scan() {
  399. line := scan.Text()
  400. if hp := strings.TrimPrefix(line, helperProcessMagic); hp != line {
  401. var err error
  402. u, err = url.Parse(hp)
  403. if err != nil {
  404. t.Fatalf("Failed to parse %q: %v", hp, err)
  405. }
  406. break
  407. }
  408. }
  409. if err := scan.Err(); err != nil {
  410. t.Fatalf("Scanning helper process stdout: %v", err)
  411. }
  412. if u == nil {
  413. t.Fatal("Helper process never reported")
  414. }
  415. return u, func() {
  416. stdin.Close()
  417. if err := cmd.Wait(); err != nil {
  418. t.Errorf("Helper process did not exit cleanly: %v", err)
  419. }
  420. }
  421. }
  422. const helperProcessMagic = "A lovely helper process is listening at "
  423. // This isn't a real test. It's used as a helper process.
  424. func TestHelperProcess(*testing.T) {
  425. if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
  426. return
  427. }
  428. defer os.Exit(0)
  429. f := &fakeAPIHandler{}
  430. srv := httptest.NewServer(f)
  431. defer srv.Close()
  432. fmt.Println(helperProcessMagic + srv.URL + apiPath)
  433. // Wait for stdin to be closed.
  434. io.Copy(ioutil.Discard, os.Stdin)
  435. }
  436. func TestBackgroundContext(t *testing.T) {
  437. resetEnv := SetTestEnv()
  438. defer resetEnv()
  439. ctx, key := fromContext(BackgroundContext()), "X-Magic-Ticket-Header"
  440. if g, w := ctx.req.Header.Get(key), "my-app-id/default.20150612t184001.0"; g != w {
  441. t.Errorf("%v = %q, want %q", key, g, w)
  442. }
  443. // Check that using the background context doesn't panic.
  444. req := &basepb.StringProto{
  445. Value: proto.String("Doctor Who"),
  446. }
  447. res := &basepb.StringProto{}
  448. Call(BackgroundContext(), "actordb", "LookupActor", req, res) // expected to fail
  449. }