forked from evergreen-ci/evergreen
/
service_web.go
172 lines (138 loc) · 4.41 KB
/
service_web.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package operations
import (
"context"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/evergreen-ci/evergreen"
"github.com/evergreen-ci/evergreen/service"
"github.com/mongodb/amboy"
"github.com/mongodb/grip"
"github.com/mongodb/grip/message"
"github.com/mongodb/grip/recovery"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
func startWebService() cli.Command {
return cli.Command{
Name: "web",
Usage: "start web services for API and UI",
Flags: mergeFlagSlices(serviceConfigFlags(), addDbSettingsFlags()),
Action: func(c *cli.Context) error {
confPath := c.String(confFlagName)
db := parseDB(c)
ctx, cancel := context.WithCancel(context.Background())
env := evergreen.GetEnvironment()
grip.CatchEmergencyFatal(errors.Wrap(env.Configure(ctx, confPath, db), "problem configuring application environment"))
grip.CatchEmergencyFatal(errors.Wrap(env.RemoteQueue().Start(ctx), "problem starting remote queue"))
settings := env.Settings()
sender, err := settings.GetSender(env)
grip.CatchEmergencyFatal(err)
grip.CatchEmergencyFatal(grip.SetSender(sender))
queue := env.RemoteQueue()
defer cancel()
defer sender.Close()
defer recovery.LogStackTraceAndExit("evergreen service")
grip.SetName("evergreen.service")
grip.Notice(message.Fields{"build": evergreen.BuildRevision, "process": grip.Name()})
startSystemCronJobs(ctx, env)
var (
apiServer *http.Server
uiServer *http.Server
)
pprof, err := service.GetHandlerPprof(settings)
if err != nil {
return errors.WithStack(err)
}
serviceHandler, err := getServiceRouter(settings, queue)
if err != nil {
return errors.WithStack(err)
}
apiServer = service.GetServer(settings.Api.HttpListenAddr, serviceHandler)
uiServer = service.GetServer(settings.Ui.HttpListenAddr, serviceHandler)
catcher := grip.NewBasicCatcher()
apiWait := make(chan struct{})
go func() {
defer recovery.LogStackTraceAndContinue("api server")
catcher.Add(apiServer.ListenAndServe())
close(apiWait)
}()
uiWait := make(chan struct{})
go func() {
defer recovery.LogStackTraceAndContinue("ui server")
catcher.Add(uiServer.ListenAndServe())
close(uiWait)
}()
pprofServer := service.GetServer(settings.PprofPort, pprof)
pprofWait := make(chan struct{})
go func() {
defer recovery.LogStackTraceAndContinue("proff server")
if settings.PprofPort != "" {
catcher.Add(pprofServer.ListenAndServe())
}
close(pprofWait)
}()
gracefulWait := make(chan struct{})
go gracefulShutdownForSIGTERM(ctx, []*http.Server{pprofServer, uiServer, apiServer}, gracefulWait, catcher)
<-apiWait
<-uiWait
<-pprofWait
grip.Notice("waiting for web services to terminate gracefully")
<-gracefulWait
grip.Notice("waiting for background tasks to finish")
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
catcher.Add(env.Close(ctx))
return catcher.Resolve()
},
}
}
func gracefulShutdownForSIGTERM(ctx context.Context, servers []*http.Server, wait chan struct{}, catcher grip.Catcher) {
defer recovery.LogStackTraceAndContinue("graceful shutdown")
sigChan := make(chan os.Signal, len(servers))
signal.Notify(sigChan, syscall.SIGTERM)
<-sigChan
waiters := make([]chan struct{}, 0)
grip.Info("received SIGTERM, terminating web service")
for _, s := range servers {
if s == nil {
continue
}
waiter := make(chan struct{})
go func(server *http.Server) {
defer recovery.LogStackTraceAndContinue("server shutdown")
catcher.Add(server.Shutdown(ctx))
close(waiter)
}(s)
waiters = append(waiters, waiter)
}
for _, waiter := range waiters {
if waiter == nil {
continue
}
<-waiter
}
close(wait)
}
func getServiceRouter(settings *evergreen.Settings, queue amboy.Queue) (http.Handler, error) {
home := evergreen.FindEvergreenHome()
if home == "" {
return nil, errors.New("EVGHOME environment variable must be set to run UI server")
}
functionOptions := service.TemplateFunctionOptions{
WebHome: filepath.Join(home, "public"),
HelpHome: settings.Ui.HelpUrl,
}
uis, err := service.NewUIServer(settings, queue, home, functionOptions)
if err != nil {
return nil, errors.Wrap(err, "failed to create UI server")
}
as, err := service.NewAPIServer(settings, queue)
if err != nil {
return nil, errors.Wrap(err, "failed to create API server")
}
return service.GetRouter(as, uis)
}