instance_vm.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // +build !appengine
  2. package aetest
  3. import (
  4. "bufio"
  5. "crypto/rand"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "os"
  13. "os/exec"
  14. "path/filepath"
  15. "regexp"
  16. "time"
  17. "golang.org/x/net/context"
  18. "google.golang.org/appengine/internal"
  19. )
  20. // NewInstance launches a running instance of api_server.py which can be used
  21. // for multiple test Contexts that delegate all App Engine API calls to that
  22. // instance.
  23. // If opts is nil the default values are used.
  24. func NewInstance(opts *Options) (Instance, error) {
  25. i := &instance{
  26. opts: opts,
  27. appID: "testapp",
  28. }
  29. if opts != nil && opts.AppID != "" {
  30. i.appID = opts.AppID
  31. }
  32. if err := i.startChild(); err != nil {
  33. return nil, err
  34. }
  35. return i, nil
  36. }
  37. func newSessionID() string {
  38. var buf [16]byte
  39. io.ReadFull(rand.Reader, buf[:])
  40. return fmt.Sprintf("%x", buf[:])
  41. }
  42. // instance implements the Instance interface.
  43. type instance struct {
  44. opts *Options
  45. child *exec.Cmd
  46. apiURL *url.URL // base URL of API HTTP server
  47. adminURL string // base URL of admin HTTP server
  48. appDir string
  49. appID string
  50. relFuncs []func() // funcs to release any associated contexts
  51. }
  52. // NewRequest returns an *http.Request associated with this instance.
  53. func (i *instance) NewRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
  54. req, err := http.NewRequest(method, urlStr, body)
  55. if err != nil {
  56. return nil, err
  57. }
  58. // Associate this request.
  59. release := internal.RegisterTestRequest(req, i.apiURL, func(ctx context.Context) context.Context {
  60. ctx = internal.WithAppIDOverride(ctx, "dev~"+i.appID)
  61. return ctx
  62. })
  63. i.relFuncs = append(i.relFuncs, release)
  64. return req, nil
  65. }
  66. // Close kills the child api_server.py process, releasing its resources.
  67. func (i *instance) Close() (err error) {
  68. for _, rel := range i.relFuncs {
  69. rel()
  70. }
  71. i.relFuncs = nil
  72. if i.child == nil {
  73. return nil
  74. }
  75. defer func() {
  76. i.child = nil
  77. err1 := os.RemoveAll(i.appDir)
  78. if err == nil {
  79. err = err1
  80. }
  81. }()
  82. if p := i.child.Process; p != nil {
  83. errc := make(chan error, 1)
  84. go func() {
  85. errc <- i.child.Wait()
  86. }()
  87. // Call the quit handler on the admin server.
  88. res, err := http.Get(i.adminURL + "/quit")
  89. if err != nil {
  90. p.Kill()
  91. return fmt.Errorf("unable to call /quit handler: %v", err)
  92. }
  93. res.Body.Close()
  94. select {
  95. case <-time.After(15 * time.Second):
  96. p.Kill()
  97. return errors.New("timeout killing child process")
  98. case err = <-errc:
  99. // Do nothing.
  100. }
  101. }
  102. return
  103. }
  104. func fileExists(path string) bool {
  105. _, err := os.Stat(path)
  106. return err == nil
  107. }
  108. func findPython() (path string, err error) {
  109. for _, name := range []string{"python2.7", "python"} {
  110. path, err = exec.LookPath(name)
  111. if err == nil {
  112. return
  113. }
  114. }
  115. return
  116. }
  117. func findDevAppserver() (string, error) {
  118. if p := os.Getenv("APPENGINE_DEV_APPSERVER"); p != "" {
  119. if fileExists(p) {
  120. return p, nil
  121. }
  122. return "", fmt.Errorf("invalid APPENGINE_DEV_APPSERVER environment variable; path %q doesn't exist", p)
  123. }
  124. return exec.LookPath("dev_appserver.py")
  125. }
  126. var apiServerAddrRE = regexp.MustCompile(`Starting API server at: (\S+)`)
  127. var adminServerAddrRE = regexp.MustCompile(`Starting admin server at: (\S+)`)
  128. func (i *instance) startChild() (err error) {
  129. if PrepareDevAppserver != nil {
  130. if err := PrepareDevAppserver(); err != nil {
  131. return err
  132. }
  133. }
  134. python, err := findPython()
  135. if err != nil {
  136. return fmt.Errorf("Could not find python interpreter: %v", err)
  137. }
  138. devAppserver, err := findDevAppserver()
  139. if err != nil {
  140. return fmt.Errorf("Could not find dev_appserver.py: %v", err)
  141. }
  142. i.appDir, err = ioutil.TempDir("", "appengine-aetest")
  143. if err != nil {
  144. return err
  145. }
  146. defer func() {
  147. if err != nil {
  148. os.RemoveAll(i.appDir)
  149. }
  150. }()
  151. err = os.Mkdir(filepath.Join(i.appDir, "app"), 0755)
  152. if err != nil {
  153. return err
  154. }
  155. err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "app.yaml"), []byte(i.appYAML()), 0644)
  156. if err != nil {
  157. return err
  158. }
  159. err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "stubapp.go"), []byte(appSource), 0644)
  160. if err != nil {
  161. return err
  162. }
  163. appserverArgs := []string{
  164. devAppserver,
  165. "--port=0",
  166. "--api_port=0",
  167. "--admin_port=0",
  168. "--automatic_restart=false",
  169. "--skip_sdk_update_check=true",
  170. "--clear_datastore=true",
  171. "--clear_search_indexes=true",
  172. "--datastore_path", filepath.Join(i.appDir, "datastore"),
  173. }
  174. if i.opts != nil && i.opts.StronglyConsistentDatastore {
  175. appserverArgs = append(appserverArgs, "--datastore_consistency_policy=consistent")
  176. }
  177. appserverArgs = append(appserverArgs, filepath.Join(i.appDir, "app"))
  178. i.child = exec.Command(python,
  179. appserverArgs...,
  180. )
  181. i.child.Stdout = os.Stdout
  182. var stderr io.Reader
  183. stderr, err = i.child.StderrPipe()
  184. if err != nil {
  185. return err
  186. }
  187. stderr = io.TeeReader(stderr, os.Stderr)
  188. if err = i.child.Start(); err != nil {
  189. return err
  190. }
  191. // Read stderr until we have read the URLs of the API server and admin interface.
  192. errc := make(chan error, 1)
  193. go func() {
  194. s := bufio.NewScanner(stderr)
  195. for s.Scan() {
  196. if match := apiServerAddrRE.FindStringSubmatch(s.Text()); match != nil {
  197. u, err := url.Parse(match[1])
  198. if err != nil {
  199. errc <- fmt.Errorf("failed to parse API URL %q: %v", match[1], err)
  200. return
  201. }
  202. i.apiURL = u
  203. }
  204. if match := adminServerAddrRE.FindStringSubmatch(s.Text()); match != nil {
  205. i.adminURL = match[1]
  206. }
  207. if i.adminURL != "" && i.apiURL != nil {
  208. break
  209. }
  210. }
  211. errc <- s.Err()
  212. }()
  213. select {
  214. case <-time.After(15 * time.Second):
  215. if p := i.child.Process; p != nil {
  216. p.Kill()
  217. }
  218. return errors.New("timeout starting child process")
  219. case err := <-errc:
  220. if err != nil {
  221. return fmt.Errorf("error reading child process stderr: %v", err)
  222. }
  223. }
  224. if i.adminURL == "" {
  225. return errors.New("unable to find admin server URL")
  226. }
  227. if i.apiURL == nil {
  228. return errors.New("unable to find API server URL")
  229. }
  230. return nil
  231. }
  232. func (i *instance) appYAML() string {
  233. return fmt.Sprintf(appYAMLTemplate, i.appID)
  234. }
  235. const appYAMLTemplate = `
  236. application: %s
  237. version: 1
  238. runtime: go
  239. api_version: go1
  240. vm: true
  241. handlers:
  242. - url: /.*
  243. script: _go_app
  244. `
  245. const appSource = `
  246. package main
  247. import "google.golang.org/appengine"
  248. func main() { appengine.Main() }
  249. `