blob: 46c2987bb9e6d623a94a660f865489f0914315ef [file] [log] [blame]
Serge Bazanski3fd70d82018-10-14 08:12:46 -07001package mirko
2
3import (
Serge Bazanskiaa81aa22018-10-14 08:36:05 -07004 "context"
Serge Bazanski3fd70d82018-10-14 08:12:46 -07005 "flag"
6 "fmt"
7 "net"
8 "net/http"
Serge Bazanski4f7ee512018-10-14 18:20:59 +01009 "os"
10 "os/signal"
Serge Bazanski3fd70d82018-10-14 08:12:46 -070011 "time"
12
Serge Bazanskidd3d40f2018-10-25 12:14:18 +010013 "code.hackerspace.pl/hscloud/go/pki"
Serge Bazanski3fd70d82018-10-14 08:12:46 -070014 "github.com/golang/glog"
15 "github.com/q3k/statusz"
16 "golang.org/x/net/trace"
17 "google.golang.org/grpc"
18 "google.golang.org/grpc/reflection"
19)
20
21var (
22 flagListenAddress string
23 flagDebugAddress string
Serge Bazanski446c9e12018-10-14 17:06:09 +010024 flagDebugAllowAll bool
Serge Bazanski3fd70d82018-10-14 08:12:46 -070025)
26
27func init() {
Serge Bazanski69de9cb2018-10-14 08:49:04 -070028 flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:4200", "gRPC listen address")
29 flag.StringVar(&flagDebugAddress, "debug_address", "127.0.0.1:4201", "HTTP debug/status listen address")
Serge Bazanskif77e7d32018-10-14 17:11:08 +010030 flag.BoolVar(&flagDebugAllowAll, "debug_allow_all", false, "HTTP debug/status available to everyone")
Serge Bazanskiaa81aa22018-10-14 08:36:05 -070031 flag.Set("logtostderr", "true")
Serge Bazanski3fd70d82018-10-14 08:12:46 -070032}
33
34type Mirko struct {
35 grpcListen net.Listener
36 grpcServer *grpc.Server
37 httpListen net.Listener
38 httpServer *http.Server
39 httpMux *http.ServeMux
Serge Bazanski4f7ee512018-10-14 18:20:59 +010040
41 ctx context.Context
42 cancel context.CancelFunc
43 waiters []chan bool
Serge Bazanski3fd70d82018-10-14 08:12:46 -070044}
45
46func New() *Mirko {
Serge Bazanski4f7ee512018-10-14 18:20:59 +010047 ctx, cancel := context.WithCancel(context.Background())
48 return &Mirko{
49 ctx: ctx,
50 cancel: cancel,
51 waiters: []chan bool{},
52 }
Serge Bazanski3fd70d82018-10-14 08:12:46 -070053}
54
Serge Bazanski446c9e12018-10-14 17:06:09 +010055func authRequest(req *http.Request) (any, sensitive bool) {
56 host, _, err := net.SplitHostPort(req.RemoteAddr)
57 if err != nil {
58 host = req.RemoteAddr
59 }
60
61 if flagDebugAllowAll {
62 return true, true
63 }
64
65 switch host {
66 case "localhost", "127.0.0.1", "::1":
67 return true, true
68 default:
69 return false, false
70 }
71}
72
Serge Bazanski3fd70d82018-10-14 08:12:46 -070073func (m *Mirko) Listen() error {
74 grpc.EnableTracing = true
Serge Bazanski446c9e12018-10-14 17:06:09 +010075 trace.AuthRequest = authRequest
76
Serge Bazanski3fd70d82018-10-14 08:12:46 -070077 grpcLis, err := net.Listen("tcp", flagListenAddress)
78 if err != nil {
79 return fmt.Errorf("net.Listen: %v", err)
80 }
81 m.grpcListen = grpcLis
Serge Bazanskidd3d40f2018-10-25 12:14:18 +010082 m.grpcServer = grpc.NewServer(pki.WithServerHSPKI()...)
Serge Bazanski3fd70d82018-10-14 08:12:46 -070083 reflection.Register(m.grpcServer)
84
85 httpLis, err := net.Listen("tcp", flagDebugAddress)
86 if err != nil {
87 return fmt.Errorf("net.Listen: %v", err)
88 }
89
90 m.httpMux = http.NewServeMux()
91 // Canonical URLs
Serge Bazanski446c9e12018-10-14 17:06:09 +010092 m.httpMux.HandleFunc("/debug/status", func(w http.ResponseWriter, r *http.Request) {
Serge Bazanskibbb5b5f2018-10-14 17:13:16 +010093 any, _ := authRequest(r)
Serge Bazanski446c9e12018-10-14 17:06:09 +010094 if !any {
95 http.Error(w, "not allowed", http.StatusUnauthorized)
96 return
97 }
98 statusz.StatusHandler(w, r)
99 })
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700100 m.httpMux.HandleFunc("/debug/requests", trace.Traces)
101
102 // -z legacy URLs
103 m.httpMux.HandleFunc("/statusz", func(w http.ResponseWriter, r *http.Request) {
104 http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
105 })
106 m.httpMux.HandleFunc("/rpcz", func(w http.ResponseWriter, r *http.Request) {
107 http.Redirect(w, r, "/debug/requests", http.StatusSeeOther)
108 })
109 m.httpMux.HandleFunc("/requestz", func(w http.ResponseWriter, r *http.Request) {
110 http.Redirect(w, r, "/debug/requests", http.StatusSeeOther)
111 })
112
113 // root redirect
114 m.httpMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
115 http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
116 })
117
118 m.httpListen = httpLis
119 m.httpServer = &http.Server{
120 Addr: flagDebugAddress,
121 Handler: m.httpMux,
122 }
123
124 return nil
125}
126
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100127// Trace logs debug information to either a context trace (if present)
128// or stderr (if not)
Serge Bazanskic99e0b92018-10-14 18:03:48 +0100129func Trace(ctx context.Context, f string, args ...interface{}) {
Serge Bazanskiaa81aa22018-10-14 08:36:05 -0700130 tr, ok := trace.FromContext(ctx)
131 if !ok {
132 fmtd := fmt.Sprintf(f, args...)
133 glog.Warningf("No trace in %v: %s", ctx, fmtd)
134 return
135 }
136 tr.LazyPrintf(f, args...)
137}
138
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100139// GRPC returns the microservice's grpc.Server object
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700140func (m *Mirko) GRPC() *grpc.Server {
141 if m.grpcServer == nil {
142 panic("GRPC() called before Listen()")
143 }
144 return m.grpcServer
145}
146
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100147// HTTPMux returns the microservice's debug HTTP mux
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700148func (m *Mirko) HTTPMux() *http.ServeMux {
149 if m.httpMux == nil {
150 panic("HTTPMux() called before Listen()")
151 }
152 return m.httpMux
153}
154
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100155// Context returns a background microservice context that will be canceled
156// when the service is shut down
157func (m *Mirko) Context() context.Context {
158 return m.ctx
159}
160
161// Done() returns a channel that will emit a value when the service is
162// shut down. This should be used in the main() function instead of a select{}
163// call, to allow the background context to be canceled fully.
164func (m *Mirko) Done() chan bool {
165 c := make(chan bool, 1)
166 m.waiters = append(m.waiters, c)
167 return c
168}
169
170// Serve starts serving HTTP and gRPC requests
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700171func (m *Mirko) Serve() error {
172 errs := make(chan error, 1)
173 go func() {
174 if err := m.grpcServer.Serve(m.grpcListen); err != nil {
175 errs <- err
176 }
177 }()
178 go func() {
179 if err := m.httpServer.Serve(m.httpListen); err != nil {
180 errs <- err
181 }
182 }()
183
Serge Bazanski4f7ee512018-10-14 18:20:59 +0100184 signalCh := make(chan os.Signal, 1)
185 signal.Notify(signalCh, os.Interrupt)
186 go func() {
187 select {
188 case <-signalCh:
189 m.cancel()
190 time.Sleep(time.Second)
191 for _, w := range m.waiters {
192 w <- true
193 }
194 }
195 }()
196
Serge Bazanski3fd70d82018-10-14 08:12:46 -0700197 ticker := time.NewTicker(1 * time.Second)
198 select {
199 case <-ticker.C:
200 glog.Infof("gRPC listening on %s", flagListenAddress)
201 glog.Infof("HTTP listening on %s", flagDebugAddress)
202 return nil
203 case err := <-errs:
204 return err
205 }
206}