api_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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" {
  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 TestAPICallRPCFailure(t *testing.T) {
  163. f, c, cleanup := setup()
  164. defer cleanup()
  165. testCases := []struct {
  166. method string
  167. code remotepb.RpcError_ErrorCode
  168. }{
  169. {"Non200", remotepb.RpcError_UNKNOWN},
  170. {"ShortResponse", remotepb.RpcError_UNKNOWN},
  171. {"OverQuota", remotepb.RpcError_OVER_QUOTA},
  172. {"RunSlowly", remotepb.RpcError_CANCELLED},
  173. }
  174. f.hang = make(chan int) // only for RunSlowly
  175. for _, tc := range testCases {
  176. ctx, _ := netcontext.WithTimeout(toContext(c), 100*time.Millisecond)
  177. err := Call(ctx, "errors", tc.method, &basepb.VoidProto{}, &basepb.VoidProto{})
  178. ce, ok := err.(*CallError)
  179. if !ok {
  180. t.Errorf("%s: API call error is %T (%v), want *CallError", tc.method, err, err)
  181. continue
  182. }
  183. if ce.Code != int32(tc.code) {
  184. t.Errorf("%s: ce.Code = %d, want %d", tc.method, ce.Code, tc.code)
  185. }
  186. if tc.method == "RunSlowly" {
  187. f.hang <- 1 // release the HTTP handler
  188. }
  189. }
  190. }
  191. func TestAPICallDialFailure(t *testing.T) {
  192. // See what happens if the API host is unresponsive.
  193. // This should time out quickly, not hang forever.
  194. _, c, cleanup := setup()
  195. defer cleanup()
  196. // Reset the URL to the production address so that dialing fails.
  197. c.apiURL = apiURL()
  198. start := time.Now()
  199. err := Call(toContext(c), "foo", "bar", &basepb.VoidProto{}, &basepb.VoidProto{})
  200. const max = 1 * time.Second
  201. if taken := time.Since(start); taken > max {
  202. t.Errorf("Dial hang took too long: %v > %v", taken, max)
  203. }
  204. if err == nil {
  205. t.Error("Call did not fail")
  206. }
  207. }
  208. func TestDelayedLogFlushing(t *testing.T) {
  209. f, c, cleanup := setup()
  210. defer cleanup()
  211. http.HandleFunc("/quick_log", func(w http.ResponseWriter, r *http.Request) {
  212. logC := WithContext(netcontext.Background(), r)
  213. fromContext(logC).apiURL = c.apiURL // Otherwise it will try to use the default URL.
  214. Logf(logC, 1, "It's a lovely day.")
  215. w.WriteHeader(200)
  216. w.Write(make([]byte, 100<<10)) // write 100 KB to force HTTP flush
  217. })
  218. r := &http.Request{
  219. Method: "GET",
  220. URL: &url.URL{
  221. Scheme: "http",
  222. Path: "/quick_log",
  223. },
  224. Header: c.req.Header,
  225. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  226. }
  227. w := httptest.NewRecorder()
  228. // Check that log flushing does not hold up the HTTP response.
  229. start := time.Now()
  230. handleHTTP(w, r)
  231. if d := time.Since(start); d > 10*time.Millisecond {
  232. t.Errorf("handleHTTP took %v, want under 10ms", d)
  233. }
  234. const hdr = "X-AppEngine-Log-Flush-Count"
  235. if h := w.HeaderMap.Get(hdr); h != "1" {
  236. t.Errorf("%s header = %q, want %q", hdr, h, "1")
  237. }
  238. if f := atomic.LoadInt32(&f.LogFlushes); f != 0 {
  239. t.Errorf("After HTTP response: f.LogFlushes = %d, want 0", f)
  240. }
  241. // Check that the log flush eventually comes in.
  242. time.Sleep(100 * time.Millisecond)
  243. if f := atomic.LoadInt32(&f.LogFlushes); f != 1 {
  244. t.Errorf("After 100ms: f.LogFlushes = %d, want 1", f)
  245. }
  246. }
  247. func TestRemoteAddr(t *testing.T) {
  248. var addr string
  249. http.HandleFunc("/remote_addr", func(w http.ResponseWriter, r *http.Request) {
  250. addr = r.RemoteAddr
  251. })
  252. testCases := []struct {
  253. headers http.Header
  254. addr string
  255. }{
  256. {http.Header{"X-Appengine-User-Ip": []string{"10.5.2.1"}}, "10.5.2.1:80"},
  257. {http.Header{"X-Appengine-Remote-Addr": []string{"1.2.3.4"}}, "1.2.3.4:80"},
  258. {http.Header{"X-Appengine-Remote-Addr": []string{"1.2.3.4:8080"}}, "1.2.3.4:8080"},
  259. {
  260. http.Header{"X-Appengine-Remote-Addr": []string{"2401:fa00:9:1:7646:a0ff:fe90:ca66"}},
  261. "[2401:fa00:9:1:7646:a0ff:fe90:ca66]:80",
  262. },
  263. {
  264. http.Header{"X-Appengine-Remote-Addr": []string{"[::1]:http"}},
  265. "[::1]:http",
  266. },
  267. {http.Header{}, "127.0.0.1:80"},
  268. }
  269. for _, tc := range testCases {
  270. r := &http.Request{
  271. Method: "GET",
  272. URL: &url.URL{Scheme: "http", Path: "/remote_addr"},
  273. Header: tc.headers,
  274. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  275. }
  276. handleHTTP(httptest.NewRecorder(), r)
  277. if addr != tc.addr {
  278. t.Errorf("Header %v, got %q, want %q", tc.headers, addr, tc.addr)
  279. }
  280. }
  281. }
  282. func TestPanickingHandler(t *testing.T) {
  283. http.HandleFunc("/panic", func(http.ResponseWriter, *http.Request) {
  284. panic("whoops!")
  285. })
  286. r := &http.Request{
  287. Method: "GET",
  288. URL: &url.URL{Scheme: "http", Path: "/panic"},
  289. Body: ioutil.NopCloser(bytes.NewReader(nil)),
  290. }
  291. rec := httptest.NewRecorder()
  292. handleHTTP(rec, r)
  293. if rec.Code != 500 {
  294. t.Errorf("Panicking handler returned HTTP %d, want HTTP %d", rec.Code, 500)
  295. }
  296. }
  297. var raceDetector = false
  298. func TestAPICallAllocations(t *testing.T) {
  299. if raceDetector {
  300. t.Skip("not running under race detector")
  301. }
  302. // Run the test API server in a subprocess so we aren't counting its allocations.
  303. u, cleanup := launchHelperProcess(t)
  304. defer cleanup()
  305. c := &context{
  306. req: &http.Request{
  307. Header: http.Header{
  308. ticketHeader: []string{"s3cr3t"},
  309. dapperHeader: []string{"trace-001"},
  310. },
  311. },
  312. apiURL: u,
  313. }
  314. req := &basepb.StringProto{
  315. Value: proto.String("Doctor Who"),
  316. }
  317. res := &basepb.StringProto{}
  318. var apiErr error
  319. avg := testing.AllocsPerRun(100, func() {
  320. ctx, _ := netcontext.WithTimeout(toContext(c), 100*time.Millisecond)
  321. if err := Call(ctx, "actordb", "LookupActor", req, res); err != nil && apiErr == nil {
  322. apiErr = err // get the first error only
  323. }
  324. })
  325. if apiErr != nil {
  326. t.Errorf("API call failed: %v", apiErr)
  327. }
  328. // Lots of room for improvement...
  329. const min, max float64 = 70, 85
  330. if avg < min || max < avg {
  331. t.Errorf("Allocations per API call = %g, want in [%g,%g]", avg, min, max)
  332. }
  333. }
  334. func launchHelperProcess(t *testing.T) (apiURL *url.URL, cleanup func()) {
  335. cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess")
  336. cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
  337. stdin, err := cmd.StdinPipe()
  338. if err != nil {
  339. t.Fatalf("StdinPipe: %v", err)
  340. }
  341. stdout, err := cmd.StdoutPipe()
  342. if err != nil {
  343. t.Fatalf("StdoutPipe: %v", err)
  344. }
  345. if err := cmd.Start(); err != nil {
  346. t.Fatalf("Starting helper process: %v", err)
  347. }
  348. scan := bufio.NewScanner(stdout)
  349. var u *url.URL
  350. for scan.Scan() {
  351. line := scan.Text()
  352. if hp := strings.TrimPrefix(line, helperProcessMagic); hp != line {
  353. var err error
  354. u, err = url.Parse(hp)
  355. if err != nil {
  356. t.Fatalf("Failed to parse %q: %v", hp, err)
  357. }
  358. break
  359. }
  360. }
  361. if err := scan.Err(); err != nil {
  362. t.Fatalf("Scanning helper process stdout: %v", err)
  363. }
  364. if u == nil {
  365. t.Fatal("Helper process never reported")
  366. }
  367. return u, func() {
  368. stdin.Close()
  369. if err := cmd.Wait(); err != nil {
  370. t.Errorf("Helper process did not exit cleanly: %v", err)
  371. }
  372. }
  373. }
  374. const helperProcessMagic = "A lovely helper process is listening at "
  375. // This isn't a real test. It's used as a helper process.
  376. func TestHelperProcess(*testing.T) {
  377. if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
  378. return
  379. }
  380. defer os.Exit(0)
  381. f := &fakeAPIHandler{}
  382. srv := httptest.NewServer(f)
  383. defer srv.Close()
  384. fmt.Println(helperProcessMagic + srv.URL + apiPath)
  385. // Wait for stdin to be closed.
  386. io.Copy(ioutil.Discard, os.Stdin)
  387. }
  388. func TestBackgroundContext(t *testing.T) {
  389. environ := []struct {
  390. key, value string
  391. }{
  392. {"GAE_LONG_APP_ID", "my-app-id"},
  393. {"GAE_MINOR_VERSION", "067924799508853122"},
  394. {"GAE_MODULE_INSTANCE", "0"},
  395. {"GAE_MODULE_NAME", "default"},
  396. {"GAE_MODULE_VERSION", "20150612t184001"},
  397. }
  398. for _, v := range environ {
  399. old := os.Getenv(v.key)
  400. os.Setenv(v.key, v.value)
  401. v.value = old
  402. }
  403. defer func() { // Restore old environment after the test completes.
  404. for _, v := range environ {
  405. if v.value == "" {
  406. os.Unsetenv(v.key)
  407. continue
  408. }
  409. os.Setenv(v.key, v.value)
  410. }
  411. }()
  412. ctx, key := fromContext(BackgroundContext()), "X-Magic-Ticket-Header"
  413. if g, w := ctx.req.Header.Get(key), "my-app-id/default.20150612t184001.0"; g != w {
  414. t.Errorf("%v = %q, want %q", key, g, w)
  415. }
  416. // Check that using the background context doesn't panic.
  417. req := &basepb.StringProto{
  418. Value: proto.String("Doctor Who"),
  419. }
  420. res := &basepb.StringProto{}
  421. Call(BackgroundContext(), "actordb", "LookupActor", req, res) // expected to fail
  422. }