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  `