/
start.go
401 lines (330 loc) · 10.4 KB
/
start.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
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
/*
Copyright 2017 The OpenEBS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package command
import (
"fmt"
"os"
"os/signal"
"reflect"
"sort"
"sync"
"strings"
"syscall"
"time"
"github.com/openebs/maya/cmd/maya-apiserver/app/config"
"github.com/openebs/maya/cmd/maya-apiserver/app/server"
"github.com/openebs/maya/cmd/maya-apiserver/cstor-operator/spc"
bd "github.com/openebs/maya/pkg/blockdevice/v1alpha2"
bdc "github.com/openebs/maya/pkg/blockdeviceclaim/v1alpha1"
env "github.com/openebs/maya/pkg/env/v1alpha1"
install "github.com/openebs/maya/pkg/install/v1alpha1"
"github.com/openebs/maya/pkg/usage"
"github.com/openebs/maya/pkg/util"
"github.com/openebs/maya/pkg/version"
errors "github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
)
var (
helpText = `
Usage: m-apiserver start [options]
Starts maya-apiserver and runs until an interrupt is received.
The maya apiserver's configuration primarily comes from the config
files used, but a subset of the options may also be passed directly
as CLI arguments, listed below.
General Options :
-bind=<addr>
The address the server will bind to for all of its various network
services. The individual services that run bind to individual
ports on this address. Defaults to the loopback 127.0.0.1.
-config=<path>
The path to either a single config file or a directory of config
files to use for configuring maya api server. This option may be
specified multiple times. If multiple config files are used, the
values from each will be merged together. During merging, values
from files found later in the list are merged over values from
previously parsed files.
-log-level=<level>
Specify the verbosity level of maya api server's logs. Valid values include
DEBUG, INFO, and WARN, in decreasing order of verbosity. The
default is INFO.
`
)
// gracefulTimeout controls how long we wait before forcefully terminating
const gracefulTimeout = 5 * time.Second
// CmdStartOptions is a cli implementation that runs a maya apiserver.
// The command will not end unless a shutdown message is sent on the
// ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly
// exit.
type CmdStartOptions struct {
BindAddr string
LogLevel string
ConfigPath string
ShutdownCh <-chan struct{}
args []string
// TODO
// Check if both maya & httpServer instances are required ?
// Can httpServer or maya embed one of the other ?
// Need to take care of shuting down & graceful exit scenarios !!
maya *server.MayaApiServer
httpServer *server.HTTPServer
}
// NewCmdStart creates start command for maya-apiserver
func NewCmdStart() *cobra.Command {
options := CmdStartOptions{}
cmd := &cobra.Command{
Use: "start",
Short: "start maya-apiserver",
Long: helpText,
Run: func(cmd *cobra.Command, args []string) {
util.CheckErr(Run(cmd, &options), util.Fatal)
},
}
cmd.Flags().StringVarP(&options.BindAddr, "bind", "", options.BindAddr,
"IP Address to bind for maya apiserver.")
cmd.Flags().StringVarP(&options.LogLevel, "log-level", "", options.LogLevel,
"Log level for maya apiserver DEBUG INFO WARN.")
cmd.Flags().StringVarP(&options.ConfigPath, "config", "", options.ConfigPath,
"Path to a single config file or directory.")
return cmd
}
// This fn performs preflight checks related to m-api server
// so that server can start properly
func performPreflightChecks() error {
return checkForNDMrelatedCRDs()
}
// checks existence of NDM related CRDs
func checkForNDMrelatedCRDs() error {
_, err := bdc.NewKubeClient().List(metav1.ListOptions{})
if err != nil {
return errors.Errorf("precheck for bdc failed: %v", err)
}
_, err = bd.NewKubeClient().List(metav1.ListOptions{})
if err != nil {
return errors.Errorf("precheck for bd failed: %v", err)
}
return nil
}
// Run does tasks related to mayaserver.
func Run(cmd *cobra.Command, c *CmdStartOptions) error {
klog.Infof("Initializing maya-apiserver...")
var ControllerMutex = sync.RWMutex{}
// Read and merge with default configuration
mconfig := c.readMayaConfig()
if mconfig == nil {
return errors.New("Unable to load the configuration")
}
//TODO Setup Log Level
// perform preflight checks
err := performPreflightChecks()
if err != nil {
return errors.Errorf("preflight checks failed: %v", err)
}
// Setup Maya server
if err = c.setupMayaServer(mconfig); err != nil {
return err
}
defer c.maya.Shutdown()
// Check and shut down at the end
defer func() {
if c.httpServer != nil {
c.httpServer.Shutdown()
}
}()
// Compile Maya server information for output later
info := make(map[string]string)
info["version"] = fmt.Sprintf("%s%s", mconfig.Version, mconfig.VersionPrerelease)
info["log level"] = mconfig.LogLevel
info["region"] = fmt.Sprintf("%s (DC: %s)", mconfig.Region, mconfig.Datacenter)
// Sort the keys for output
infoKeys := make([]string, 0, len(info))
for key := range info {
infoKeys = append(infoKeys, key)
}
sort.Strings(infoKeys)
// Maya server configuration output
padding := 18
klog.Info("Maya api server configuration:\n")
for _, k := range infoKeys {
klog.Infof(
"%s%s: %s",
strings.Repeat(" ", padding-len(k)),
strings.Title(k),
info[k])
}
klog.Infof("")
// Output the header that the server has started
klog.Info("Maya api server started! Log data will stream in below:\n")
// start storage pool controller
go func() {
err := spc.Start(&ControllerMutex)
if err != nil {
klog.Errorf("Failed to start storage pool controller: %s", err.Error())
}
}()
if env.Truthy(env.OpenEBSEnableAnalytics) {
usage.New().Build().InstallBuilder(true).Send()
go usage.PingCheck()
}
// Wait for exit
if c.handleSignals(mconfig) > 0 {
return errors.New("Ungraceful exit")
}
return nil
}
func (c *CmdStartOptions) readMayaConfig() *config.MayaConfig {
// Load the configuration
mconfig := config.DefaultMayaConfig()
if c.ConfigPath != "" {
current, err := config.LoadMayaConfig(c.ConfigPath)
if err != nil {
klog.Errorf(
"Error loading configuration from %s: %s", c.ConfigPath, err)
return nil
}
// The user asked us to load some config here but we didn't find any,
// so we'll complain but continue.
if current == nil || reflect.DeepEqual(current, &config.MayaConfig{}) {
klog.Warningf("No configuration loaded from %s", c.ConfigPath)
}
if mconfig == nil {
mconfig = current
} else {
mconfig = mconfig.Merge(current)
}
}
// Merge any CLI options over config file options
// Set the version info
mconfig.Revision = version.GetGitCommit()
mconfig.Version = version.GetVersion()
mconfig.VersionPrerelease = version.GetBuildMeta()
// Set the details from command line
if c.BindAddr != "" {
mconfig.BindAddr = c.BindAddr
}
if c.LogLevel != "" {
mconfig.LogLevel = c.LogLevel
}
// Normalize binds, ports, addresses, and advertise
if err := mconfig.NormalizeAddrs(); err != nil {
klog.Errorf(err.Error())
return nil
}
return mconfig
}
// setupMayaServer is used to start Maya server
func (c *CmdStartOptions) setupMayaServer(mconfig *config.MayaConfig) error {
klog.Info("Starting maya api server ...")
// run maya installer
installErrs := install.SimpleInstaller().Install()
if len(installErrs) != 0 {
klog.Errorf("failed to apply resources: %+v", installErrs)
return errors.New("failed to apply resources")
}
klog.Info("resources applied successfully by installer")
// clean older CASTemplates and RunTasks
cleanErr := install.SimpleInstaller().Clean()
if cleanErr != nil {
klog.Errorf("failed to clean old resources: %s", cleanErr.Error())
}
// Setup maya service i.e. maya api server
maya, err := server.NewMayaApiServer(mconfig, os.Stdout)
if err != nil {
klog.Errorf("failed to start api server: %+v", err)
return err
}
c.maya = maya
// Setup the HTTP server
http, err := server.NewHTTPServer(maya, mconfig, os.Stdout)
if err != nil {
maya.Shutdown()
klog.Errorf("failed to start http server: %+v", err)
return err
}
c.httpServer = http
return nil
}
// handleSignals blocks until we get an exit-causing signal
func (c *CmdStartOptions) handleSignals(mconfig *config.MayaConfig) int {
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGPIPE)
// Wait for a signal
WAIT:
var sig os.Signal
select {
case s := <-signalCh:
sig = s
case <-c.ShutdownCh:
sig = os.Interrupt
}
klog.Infof("Caught signal: %v", sig)
// Skip any SIGPIPE signal (See issue #1798)
if sig == syscall.SIGPIPE {
goto WAIT
}
// Check if this is a SIGHUP
if sig == syscall.SIGHUP {
if conf := c.handleReload(mconfig); conf != nil {
// Update the value only, not address
*mconfig = *conf
}
goto WAIT
}
// Check if we should do a graceful leave
graceful := false
if sig == os.Interrupt && mconfig.LeaveOnInt {
graceful = true
} else if sig == syscall.SIGTERM && mconfig.LeaveOnTerm {
graceful = true
}
// Bail fast if not doing a graceful leave
if !graceful {
return 1
}
// Attempt a graceful leave
gracefulCh := make(chan struct{})
klog.Info("Gracefully shutting maya api server...")
go func() {
if err := c.maya.Leave(); err != nil {
klog.Errorf("Error: %s", err)
return
}
close(gracefulCh)
}()
// Wait for leave or another signal
select {
case <-signalCh:
return 1
case <-time.After(gracefulTimeout):
return 1
case <-gracefulCh:
return 0
}
}
// handleReload is invoked when we should reload our configs, e.g. SIGHUP
// TODO
// The current reload code is very basic.
// Add ways to reload the orchestrator & plugins without shuting down the
// process
func (c *CmdStartOptions) handleReload(mconfig *config.MayaConfig) *config.MayaConfig {
klog.Info("Reloading maya api server configuration...")
newConf := c.readMayaConfig()
if newConf == nil {
klog.Error("Failed to reload config")
return mconfig
}
//TODO Change the log level dynamically
klog.Infof("Log level is : %s", strings.ToUpper(newConf.LogLevel))
return newConf
}