Skip to content

Commit b271510

Browse files
committed
devapp, status: start of status handler for monitoring
Also bump Go from 1.8 to 1.10, and change how the static binary is built to avoid warnings during link. Updates golang/go#21315 Updates golang/go#22603 Change-Id: I426491d48f787a77cb3eea4dff4d11f474236548 Reviewed-on: https://go-review.googlesource.com/96416 Reviewed-by: Andrew Bonventre <[email protected]>
1 parent d4bc8ed commit b271510

File tree

5 files changed

+208
-4
lines changed

5 files changed

+208
-4
lines changed

devapp/Dockerfile.0

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Copyright 2017 The Go Authors. All rights reserved.
22
# Use of this source code is governed by a BSD-style
33
# license that can be found in the LICENSE file.
4-
FROM golang:1.8
4+
5+
FROM golang:1.10
56
LABEL maintainer "[email protected]"
67

8+
ENV CGO_ENABLED=0
9+
710
# BEGIN deps (run `make update-deps` to update)
811

912
# Repo cloud.google.com/go at ba25346 (2017-10-13)
@@ -152,4 +155,4 @@ RUN go install cloud.google.com/go/compute/metadata \
152155

153156
COPY . /go/src/golang.org/x/build/
154157

155-
RUN go install -ldflags "-linkmode=external -extldflags '-static -pthread'" golang.org/x/build/devapp
158+
RUN go install golang.org/x/build/devapp

devapp/devapp.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ func main() {
6262
go func() {
6363
handler := http.Handler(s)
6464
if *autocertBucket != "" {
65-
handler = http.HandlerFunc(redirectHTTP)
65+
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
redirectHTTP(w, r, s)
67+
})
6668
}
6769
errc <- fmt.Errorf("http.Serve = %v", http.Serve(ln, handler))
6870
}()
@@ -77,11 +79,19 @@ func main() {
7779
log.Fatal(<-errc)
7880
}
7981

80-
func redirectHTTP(w http.ResponseWriter, r *http.Request) {
82+
func redirectHTTP(w http.ResponseWriter, r *http.Request, h http.Handler) {
8183
if r.TLS != nil || r.Host == "" {
8284
http.NotFound(w, r)
8385
return
8486
}
87+
88+
// Serve /status directly, without a forced redirect.
89+
// This lets monitoring avoid a dependency on LetsEncrypt being up.
90+
if strings.HasPrefix(r.RequestURI, "/status") {
91+
h.ServeHTTP(w, r)
92+
return
93+
}
94+
8595
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusFound)
8696
}
8797

devapp/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
"golang.org/x/build/maintner"
2121
"golang.org/x/build/maintner/godata"
22+
"golang.org/x/build/status/statusserver"
2223
)
2324

2425
// A server is an http.Handler that serves content within staticDir at root and
@@ -62,6 +63,10 @@ func newServer(mux *http.ServeMux, staticDir, templateDir string) *server {
6263
s.mux.HandleFunc("/release", s.withTemplate("/release.tmpl", s.handleRelease))
6364
s.mux.HandleFunc("/reviews", s.withTemplate("/reviews.tmpl", s.handleReviews))
6465
s.mux.HandleFunc("/dir/", handleDirRedirect)
66+
67+
ss := statusserver.NewHandler()
68+
ss.Register(s.mux) // at /status
69+
6570
for _, p := range []string{"/imfeelinghelpful", "/imfeelinglucky"} {
6671
s.mux.HandleFunc(p, s.handleRandomHelpWantedIssue)
6772
}

status/status.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package status contains code for monitoring the golang.org infrastructure.
6+
// It is not intended for use outside of the Go project.
7+
package status
8+
9+
import (
10+
"os"
11+
"sync"
12+
"time"
13+
14+
"cloud.google.com/go/compute/metadata"
15+
)
16+
17+
var (
18+
statusTokenMu sync.Mutex
19+
statusTokenVal string
20+
statusTokenErr error
21+
lastCheck time.Time
22+
)
23+
24+
// UpdateToken returns the value of "monitor-probe-token" from the GCE
25+
// metadata server, or from the environment variable
26+
// $DEV_STATUS_UPDATE_TOKEN. This is the the shared secret that must
27+
// be sent to dev.golang.org in prober status update requests.
28+
// Most probers will not use this directly.
29+
func UpdateToken() (string, error) {
30+
statusTokenMu.Lock()
31+
defer statusTokenMu.Unlock()
32+
if statusTokenVal != "" {
33+
return statusTokenVal, nil
34+
}
35+
if lastCheck.After(time.Now().Add(-10 * time.Second)) {
36+
return "", statusTokenErr
37+
}
38+
if v := os.Getenv("DEV_STATUS_UPDATE_TOKEN"); v != "" {
39+
statusTokenVal = v
40+
return v, nil
41+
}
42+
v, err := metadata.Get("monitor-probe-token")
43+
if err == nil {
44+
statusTokenVal = v
45+
return v, nil
46+
}
47+
lastCheck = time.Now()
48+
statusTokenErr = err
49+
return "", err
50+
}

status/statusserver/statusserver.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package statusserver contains the status server for the golang.org
6+
// properties and build infrastructure.
7+
package statusserver
8+
9+
import (
10+
"io"
11+
"log"
12+
"net/http"
13+
"sort"
14+
"sync"
15+
"time"
16+
17+
"golang.org/x/build/status"
18+
)
19+
20+
// Handler is the status server handler.
21+
// It expects to be mounted at /status.
22+
type Handler struct {
23+
mu sync.Mutex
24+
items map[string]*monItem
25+
sortedItems []string // sorted keys of the items map
26+
}
27+
28+
func NewHandler() *Handler {
29+
h := &Handler{
30+
items: map[string]*monItem{
31+
"pubsubhelper": &monItem{
32+
goodFor: 10 * time.Minute,
33+
},
34+
},
35+
}
36+
h.updateSorted()
37+
return h
38+
}
39+
40+
// Register registers h in mux at /status.
41+
func (h *Handler) Register(mux *http.ServeMux) {
42+
mux.Handle("/status", mux)
43+
}
44+
45+
// requires h.mu is held
46+
func (h *Handler) getItemLocked(name string) *monItem {
47+
mi, ok := h.items[name]
48+
if !ok {
49+
mi = new(monItem)
50+
mi.goodFor = 10 * time.Minute // default
51+
h.items[name] = mi
52+
h.updateSorted()
53+
}
54+
return mi
55+
}
56+
57+
// requires h.mu is held
58+
func (h *Handler) updateSorted() {
59+
h.sortedItems = nil
60+
for name := range h.items {
61+
h.sortedItems = append(h.sortedItems, name)
62+
}
63+
sort.Strings(h.sortedItems)
64+
}
65+
66+
// monItem is an item that is monitored. It can be monitored
67+
// externally from the server hosting this handler, or it can be
68+
// self-reported, in which case it'll come in via /status/update.
69+
//
70+
// All state is guarded by h.mu.
71+
type monItem struct {
72+
goodFor time.Duration
73+
74+
lastUpdate time.Time
75+
ok bool
76+
errText string
77+
warn string
78+
}
79+
80+
func (mi *monItem) updateFromRequest(r *http.Request) {
81+
// TODO: record state changes over time, so we have a history of the last
82+
// N transitions.
83+
84+
mi.lastUpdate = time.Now()
85+
if s := r.FormValue("state"); s == "ok" {
86+
mi.ok = true
87+
mi.errText = ""
88+
} else {
89+
mi.ok = false
90+
mi.errText = s
91+
}
92+
mi.warn = r.FormValue("warn")
93+
}
94+
95+
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
96+
path := r.URL.Path
97+
if path == "/status/update" {
98+
h.handleUpdate(w, r)
99+
return
100+
}
101+
io.WriteString(w, "ok\n")
102+
}
103+
104+
func (h *Handler) handleUpdate(w http.ResponseWriter, r *http.Request) {
105+
switch {
106+
case r.TLS == nil:
107+
http.Error(w, "request requires TLS", http.StatusBadRequest)
108+
return
109+
case r.Method != "POST":
110+
http.Error(w, "request requires POST", http.StatusBadRequest)
111+
return
112+
case r.Header.Get("Content-Type") != "application/x-www-form-urlencoded":
113+
http.Error(w, "request requires Content-Type x-www-form-urlencoded", http.StatusBadRequest)
114+
return
115+
case r.ContentLength > 0 && r.ContentLength <= 1<<20:
116+
http.Error(w, "request requires explicit Content-Length under 1MB", http.StatusBadRequest)
117+
return
118+
}
119+
wantToken, err := status.UpdateToken()
120+
if err != nil {
121+
log.Printf("error: status.UpdateToken: %v", err)
122+
http.Error(w, "failed to get status update token to validate against", 500)
123+
return
124+
}
125+
if r.Header.Get("X-Status-Update-Token") != wantToken {
126+
http.Error(w, "invalid X-Status-Update-Token value", http.StatusUnauthorized)
127+
return
128+
}
129+
name := r.FormValue("name")
130+
131+
h.mu.Lock()
132+
defer h.mu.Unlock()
133+
mi := h.getItemLocked(name)
134+
mi.updateFromRequest(r)
135+
w.WriteHeader(http.StatusNoContent)
136+
}

0 commit comments

Comments
 (0)