cmd/tip: serve talks.golang.org
Also make health check test the backup process. Change-Id: I9d2ed2780c07bb08683d231fccad4674c2ac22a1 Reviewed-on: https://go-review.googlesource.com/14668 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
ac6d9c1d84
commit
1d593709ec
|
@ -1,3 +1,7 @@
|
||||||
To deploy tip.golang.org:
|
To deploy tip.golang.org:
|
||||||
|
|
||||||
$ gcloud --project golang-org preview app deploy --set-default godoc.yaml
|
$ gcloud --project golang-org preview app deploy --set-default godoc.yaml
|
||||||
|
|
||||||
|
To deploy talks.golang.org:
|
||||||
|
|
||||||
|
$ gcloud --project golang-org preview app deploy --set-default talks.yaml
|
||||||
|
|
|
@ -6,14 +6,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type godocBuilder struct {
|
type godocBuilder struct {
|
||||||
|
@ -23,8 +20,6 @@ func (b godocBuilder) Signature(heads map[string]string) string {
|
||||||
return heads["go"] + "-" + heads["tools"]
|
return heads["go"] + "-" + heads["tools"]
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexingMsg = []byte("Indexing in progress: result may be inaccurate")
|
|
||||||
|
|
||||||
func (b godocBuilder) Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error) {
|
func (b godocBuilder) Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error) {
|
||||||
goDir := filepath.Join(dir, "go")
|
goDir := filepath.Join(dir, "go")
|
||||||
toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools")
|
toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools")
|
||||||
|
@ -60,29 +55,18 @@ func (b godocBuilder) Init(dir, hostport string, heads map[string]string) (*exec
|
||||||
if err := godoc.Start(); err != nil {
|
if err := godoc.Start(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go func() {
|
return godoc, nil
|
||||||
// TODO(bradfitz): tell the proxy that this side is dead
|
}
|
||||||
if err := godoc.Wait(); err != nil {
|
|
||||||
log.Printf("process in %v exited: %v", dir, err)
|
var indexingMsg = []byte("Indexing in progress: result may be inaccurate")
|
||||||
}
|
|
||||||
}()
|
func (b godocBuilder) HealthCheck(hostport string) error {
|
||||||
|
body, err := getOK(fmt.Sprintf("http://%v/search?q=FALLTHROUGH", hostport))
|
||||||
var err error
|
if err != nil {
|
||||||
deadline := time.Now().Add(startTimeout)
|
return err
|
||||||
for time.Now().Before(deadline) {
|
}
|
||||||
time.Sleep(time.Second)
|
if bytes.Contains(body, indexingMsg) {
|
||||||
var res *http.Response
|
return errors.New("still indexing")
|
||||||
res, err = http.Get(fmt.Sprintf("http://%v/search?q=FALLTHROUGH", hostport))
|
}
|
||||||
if err != nil {
|
return nil
|
||||||
continue
|
|
||||||
}
|
|
||||||
rbody, err := ioutil.ReadAll(res.Body)
|
|
||||||
res.Body.Close()
|
|
||||||
if err == nil && res.StatusCode == http.StatusOK &&
|
|
||||||
!bytes.Contains(rbody, indexingMsg) {
|
|
||||||
return godoc, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
godoc.Process.Kill()
|
|
||||||
return nil, fmt.Errorf("timed out waiting for process in %v at %v (%v)", dir, hostport, err)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type talksBuilder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b talksBuilder) Signature(heads map[string]string) string {
|
||||||
|
return heads["talks"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const talksToolsRev = "ac6d9c1d842f9b6482f39f7a172e0251a0f7cbc0"
|
||||||
|
|
||||||
|
func (b talksBuilder) Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error) {
|
||||||
|
toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools")
|
||||||
|
if err := checkout(repoURL+"tools", talksToolsRev, toolsDir); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
talksDir := filepath.Join(dir, "gopath/src/golang.org/x/talks")
|
||||||
|
if err := checkout(repoURL+"talks", heads["talks"], talksDir); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
goDir := os.Getenv("GOROOT_BOOTSTRAP")
|
||||||
|
if goDir == "" {
|
||||||
|
goDir = runtime.GOROOT()
|
||||||
|
}
|
||||||
|
goBin := filepath.Join(goDir, "bin/go")
|
||||||
|
goPath := filepath.Join(dir, "gopath")
|
||||||
|
presentPath := "golang.org/x/tools/cmd/present"
|
||||||
|
install := exec.Command(goBin, "install", "-tags=appenginevm", presentPath)
|
||||||
|
install.Env = []string{"GOROOT=" + goDir, "GOPATH=" + goPath}
|
||||||
|
if err := runErr(install); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
talksBin := filepath.Join(goPath, "bin/present")
|
||||||
|
presentSrc := filepath.Join(goPath, "src", presentPath)
|
||||||
|
present := exec.Command(talksBin, "-http="+hostport, "-base="+presentSrc)
|
||||||
|
present.Dir = talksDir
|
||||||
|
// TODO(adg): log this somewhere useful
|
||||||
|
present.Stdout = os.Stdout
|
||||||
|
present.Stderr = os.Stderr
|
||||||
|
if err := present.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return present, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var talksMsg = []byte("Talks - The Go Programming Language")
|
||||||
|
|
||||||
|
func (b talksBuilder) HealthCheck(hostport string) error {
|
||||||
|
body, err := getOK(fmt.Sprintf("http://%v/", hostport))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !bytes.Contains(body, talksMsg) {
|
||||||
|
return errors.New("couldn't match string")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
module: talks
|
||||||
|
runtime: custom
|
||||||
|
api_version: go1
|
||||||
|
vm: true
|
||||||
|
|
||||||
|
automatic_scaling:
|
||||||
|
min_num_instances: 1
|
||||||
|
max_num_instances: 5
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /.*
|
||||||
|
script: _go_app
|
||||||
|
|
||||||
|
env_variables:
|
||||||
|
TIP_BUILDER: 'talks'
|
|
@ -9,6 +9,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -35,6 +36,8 @@ func main() {
|
||||||
switch os.Getenv(k) {
|
switch os.Getenv(k) {
|
||||||
case "godoc":
|
case "godoc":
|
||||||
b = godocBuilder{}
|
b = godocBuilder{}
|
||||||
|
case "talks":
|
||||||
|
b = talksBuilder{}
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown %v value: %q", k, os.Getenv(k))
|
log.Fatalf("Unknown %v value: %q", k, os.Getenv(k))
|
||||||
}
|
}
|
||||||
|
@ -54,17 +57,19 @@ func main() {
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
builder Builder
|
builder Builder
|
||||||
|
|
||||||
mu sync.Mutex // protects the followin'
|
mu sync.Mutex // protects the followin'
|
||||||
proxy http.Handler
|
proxy http.Handler
|
||||||
cur string // signature of gorepo+toolsrepo
|
cur string // signature of gorepo+toolsrepo
|
||||||
cmd *exec.Cmd // live godoc instance, or nil for none
|
cmd *exec.Cmd // live godoc instance, or nil for none
|
||||||
side string
|
side string
|
||||||
err error
|
hostport string // host and port of the live instance
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Builder interface {
|
type Builder interface {
|
||||||
Signature(heads map[string]string) string
|
Signature(heads map[string]string) string
|
||||||
Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error)
|
Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error)
|
||||||
|
HealthCheck(hostport string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -85,6 +90,10 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.URL.Path == "/_ah/health" {
|
if r.URL.Path == "/_ah/health" {
|
||||||
|
if err := p.builder.HealthCheck(p.hostport); err != nil {
|
||||||
|
http.Error(w, "Health check failde: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Fprintln(w, "OK")
|
fmt.Fprintln(w, "OK")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -148,6 +157,15 @@ func (p *Proxy) poll() {
|
||||||
hostport = "localhost:8082"
|
hostport = "localhost:8082"
|
||||||
}
|
}
|
||||||
cmd, err := p.builder.Init(dir, hostport, heads)
|
cmd, err := p.builder.Init(dir, hostport, heads)
|
||||||
|
if err == nil {
|
||||||
|
go func() {
|
||||||
|
// TODO(adg,bradfitz): be smarter about dead processes
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
log.Printf("process in %v exited: %v", dir, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err = waitReady(p.builder, hostport)
|
||||||
|
}
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
|
@ -165,12 +183,25 @@ func (p *Proxy) poll() {
|
||||||
}
|
}
|
||||||
p.proxy = httputil.NewSingleHostReverseProxy(u)
|
p.proxy = httputil.NewSingleHostReverseProxy(u)
|
||||||
p.side = newSide
|
p.side = newSide
|
||||||
|
p.hostport = hostport
|
||||||
if p.cmd != nil {
|
if p.cmd != nil {
|
||||||
p.cmd.Process.Kill()
|
p.cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
p.cmd = cmd
|
p.cmd = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func waitReady(b Builder, hostport string) error {
|
||||||
|
var err error
|
||||||
|
deadline := time.Now().Add(startTimeout)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
if err = b.HealthCheck(hostport); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("timed out waiting for process at %v: %v", hostport, err)
|
||||||
|
}
|
||||||
|
|
||||||
func runErr(cmd *exec.Cmd) error {
|
func runErr(cmd *exec.Cmd) error {
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -252,3 +283,19 @@ func gerritMetaMap() map[string]string {
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOK(url string) (body []byte, err error) {
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(res.Status)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue