-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmain.go
More file actions
280 lines (235 loc) · 8.34 KB
/
Copy pathmain.go
File metadata and controls
280 lines (235 loc) · 8.34 KB
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package ozone
import (
"fmt"
"io"
"os"
"sync"
"syscall"
"time"
"github.com/One-com/gone/daemon"
"github.com/One-com/gone/daemon/ctrl"
"github.com/One-com/gone/log"
"github.com/One-com/gone/log/syslog"
"github.com/One-com/gone/signals"
)
func init() {
// Default to a simple systemd compatible log on stdout
log.Minimal()
}
// configDumper writes configuration to the supplied writer.
type configDumper interface {
Dump(io.Writer)
}
type dryRunFunc func() (configDumper, error)
// loadConfig will generate 2 functions to act on the result on using the config to instantiate
// everything from all the subconfigs.
// Calling either of these 2 functions will parse all config and initalize a server based on it.
// Calling configF will then return the initalized server ready for running.
// Calling dryrunF will throw away the server and instead dump the total parsed config to the
// provided configDumper
func loadConfig(cfgSpec interface{}) (configF daemon.ConfigFunc, dryrunF dryRunFunc) {
configFunc := func() (s []daemon.Server, c []daemon.CleanupFunc, newCfg configDumper, err error) {
log.INFO("Loading config")
s, c, newCfg, err = instantiateServersFromConfig(cfgSpec)
if err != nil {
var filename string
if f, ok := cfgSpec.(string); ok {
filename = f
}
log.CRIT("Error configuring services", "file", filename, "err", err)
}
return
}
configF = daemon.ConfigFunc(
func() ([]daemon.Server, []daemon.CleanupFunc, error) {
servers, cleanup, _, err := configFunc()
return servers, cleanup, err
})
dryrunF = dryRunFunc(
func() (configDumper, error) {
_, _, config, err := configFunc()
return config, err
})
return
}
// A generic log function for lower level code.
func serverLogFunc(level int, message string) {
log.Default().LogFromCaller(3, syslog.Priority(level), message)
}
/**************** SIGNAL HANDLING *******************/
// onSignalExit will ask the daemon to exit without waiting for graceful shutdown.
func onSignalExit() {
log.Println("Signal Exit")
daemon.Exit(false)
}
// onSignalExitGraceful will ask the daemon to exit waiting for graceful shutdown
func onSignalExitGraceful() {
log.Println("Signal Exit")
daemon.Exit(true)
}
// onSignalReload will ask the daemon to configure a new set of server objects
// to replace the old server objects letting the old shutdown gracefully.
func onSignalReload() {
log.Println("Signal Reload")
daemon.Reload()
}
// onSignalRespawn will fork/exec a new daemon process, re-initialized from config
// which will then ask the old daemon process to shutdown.
func onSignalRespawn() {
log.Println("Signal Respawn")
daemon.ReplaceProcess(syscall.SIGTERM)
}
// onSignalIncLogLevel will increase the log level for the default logger.
func onSignalIncLogLevel() {
log.IncLevel()
log.Print(fmt.Sprintf("Log level: %d", log.Level()))
}
// onSignalDecLogLevel will decrease the log level for the default logger.
func onSignalDecLogLevel() {
log.DecLevel()
log.Print(fmt.Sprintf("Log level: %d\n", log.Level()))
}
// onSignalReopenAccessLogFiles will ask any configured HTTP handler access log files
// to be re-opened. (for external log rotation)
func onSignalReopenAccessLogFiles() {
ReopenAccessLogFiles()
}
// HandledSignals is a map (syscall.Signal->func()) defining default
// OS signals to handle and how.
// Change this by assigning to HandledSignals before calling Init() if you need.
// Default signals:
//
// SIGINT: Exit immediately
// SIGTERM: Wait for shutdown timeout before closing and exiting.
// SIGHUP: Retain open file descriptors, but configure new servers from config reusing any open sockets.
// SIGUSR2: Respawn the process with the same arguments inheriting file descriptors. The new process will send SIGTERM to the parent once it's configured.
// SIGTTIN: Increase log level
// SIGTTOU: Decrease log level
// SIGUSR1: Reopen all access log files.
//
var HandledSignals = signals.Mappings{
syscall.SIGINT: onSignalExit,
syscall.SIGTERM: onSignalExitGraceful,
syscall.SIGHUP: onSignalReload,
syscall.SIGUSR2: onSignalRespawn,
syscall.SIGTTIN: onSignalIncLogLevel,
syscall.SIGTTOU: onSignalDecLogLevel,
syscall.SIGUSR1: onSignalReopenAccessLogFiles,
}
/******************* Options ***********************************/
type runcfg struct {
dryrun bool // just call dryrunF and exit.
controlsocket string // path of the UNIX control socket
shutdowntimeout time.Duration // default delay to wait for graceful shutdown
readymessage string // Message to send over systemd notify socket when ready
}
// Option to pass to Init()
type Option func(*runcfg)
// Default Config
var cfg = runcfg{
readymessage: "Ready and serving",
controlsocket: "./ozone-control.sock",
}
// DumpConfig makes Ozone dry-run and exit after dumping the parsed configuration to os.Stdout
func DumpConfig(dryrun bool) Option {
return Option(func(c *runcfg) {
c.dryrun = dryrun
})
}
// ControlSocket specifies an alternative path for the daemon
// control socket. If "", the socket is disabled.
// The socket defaults to "ozone-control.sock" in the current working directory.
func ControlSocket(socketfile string) Option {
return Option(func(c *runcfg) {
c.controlsocket = socketfile
})
}
// ShutdownTimeout changes how long to wait for Servers to do graceful
// Shutdown before forcefully closing them. You can use the control socket
// to shutdown with any timeout later, but this is the default which is
// used by SIGTERM.
func ShutdownTimeout(to time.Duration) Option {
return Option(func(c *runcfg) {
c.shutdowntimeout = to
})
}
// SdNotifyReadyMessage changes the default message to send to systemd
// via the notify socket.
func SdNotifyReadyMessage(msg string) Option {
return Option(func(c *runcfg) {
c.readymessage = msg
})
}
/******************* Init logic ********************************/
var initOnce sync.Once
// DisableInit disables Ozones default initialization of
// Logging, and OS signals using gone/log, gone/daemon and gone/signals.
// You are on your own now to handle signal and configure logging.
func DisableInit() {
internalInit(false)
}
// Init initializes Ozone.
// If you don't call it, it is called for you by Main(). You
// can force initialization from your init() function by calling it here.
// Init will: 1) Set the github.com/One-com/gone/daemon logger function. 2) Register a daemon controlling control socket command. 3) Start the signal handler processing HandledSignals.
// If you don't want this and control daemon and logging your self, call DisableInit early.
func Init(opts ...Option) {
internalInit(true, opts...)
}
func internalInit(doinit bool, opts ...Option) {
// initialize config from options
for _, o := range opts {
o(&cfg)
}
// Setup default ozone configuration unless disabled.
// If you want something else, call DisableInit() instead of Init()
// Any options can be passed to Main()
// * configure logging
// * configure basic "daemon" control socket command
// * Run the default signal handler
initOnce.Do(func() {
if doinit {
daemon.SetLogger(serverLogFunc)
ctrl.RegisterCommand("daemon", procControl)
signals.RunSignalHandler(HandledSignals)
}
})
}
// Main starts Ozone serving, by parsing the provided config file
// and serving everything defined in it by calling github.com/One-com/gone/daemon.Run().
// Main can be provided options which will overwrite any options given to Init()
func Main(filename string, opts ...Option) error {
return ozonemain(filename, opts...)
}
// ozonemain takes config as an interface to allow for an in memory buffer during tests
func ozonemain(config interface{}, opts ...Option) error {
Init(opts...)
configureFunc, dryrunFunc := loadConfig(config)
if cfg.dryrun {
newCfg, err := dryrunFunc()
if err != nil {
var filename string
if f, ok := config.(string); ok {
filename = f
}
log.CRIT("Error parsing config file", "file", filename, "err", err)
return err
}
newCfg.Dump(os.Stdout)
return err
}
runoptions := []daemon.RunOption{
daemon.Configurator(configureFunc),
daemon.ControlSocket("", cfg.controlsocket),
daemon.ShutdownTimeout(cfg.shutdowntimeout),
daemon.SdNotifyOnReady(true, cfg.readymessage),
daemon.SignalParentOnReady(),
}
log.NOTICE("Starting server", "pid", os.Getpid())
err := daemon.Run(runoptions...)
if err != nil {
log.CRIT("Daemon exit error", "err", err)
}
log.NOTICE("Halted")
return err
}