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