Permalink
Newer
Older
100644 320 lines (285 sloc) 9.41 KB
1
// Copyright 2015 The Cockroach Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
// implied. See the License for the specific language governing
13
// permissions and limitations under the License.
14
//
15
// Author: Andrew Bonventre (andybons@gmail.com)
16
// Author: Spencer Kimball (spencer.kimball@gmail.com)
17
18
package cli
19
20
import (
23
"fmt"
24
"net"
25
"os"
26
"os/signal"
32
"github.com/cockroachdb/cockroach/build"
33
"github.com/cockroachdb/cockroach/client"
34
"github.com/cockroachdb/cockroach/security"
35
"github.com/cockroachdb/cockroach/server"
36
"github.com/cockroachdb/cockroach/storage/engine"
37
"github.com/cockroachdb/cockroach/util"
38
"github.com/cockroachdb/cockroach/util/log"
39
"github.com/cockroachdb/cockroach/util/stop"
40
41
"github.com/spf13/cobra"
44
var errMissingParams = errors.New("missing or invalid parameters")
45
46
// panicGuard wraps an errorless command into one wrapping panics into errors.
47
// This simplifies error handling for many commands for which more elaborate
48
// error handling isn't needed and would otherwise bloat the code.
49
//
50
// Deprecated: When introducing a new cobra.Command, simply return an error.
51
func panicGuard(cmdFn func(*cobra.Command, []string)) func(*cobra.Command, []string) error {
52
return func(c *cobra.Command, args []string) (err error) {
53
defer func() {
54
if r := recover(); r != nil {
55
err = fmt.Errorf("%v", r)
56
}
57
}()
58
cmdFn(c, args)
59
return nil
60
}
61
}
62
63
// panicf is only to be used when wrapped through panicGuard, since the
64
// stack trace doesn't matter then.
65
func panicf(format string, args ...interface{}) {
66
panic(fmt.Sprintf(format, args...))
67
}
68
69
// getJSON is a convenience wrapper around util.GetJSON that uses our Context to populate
70
// parts of the request.
71
func getJSON(hostport, path string, v interface{}) error {
72
httpClient, err := cliContext.GetHTTPClient()
73
if err != nil {
74
return err
75
}
76
return util.GetJSON(httpClient, cliContext.HTTPRequestScheme(), hostport, path, v)
79
// startCmd starts a node by initializing the stores and joining
80
// the cluster.
81
var startCmd = &cobra.Command{
82
Use: "start",
83
Short: "start a node",
85
Start a CockroachDB node, which will export data from one or more
86
storage devices, specified via --store flags.
87
88
If no cluster exists yet and this is the first node, no additional
89
flags are required. If the cluster already exists, and this node is
90
uninitialized, specify the --join flag to point to any healthy node
91
(or list of nodes) already part of the cluster.
92
`,
93
Example: ` cockroach start --insecure --store=attrs=ssd,path=/mnt/ssd1 [--join=host:port,[host:port]]`,
94
SilenceUsage: true,
95
RunE: runStart,
98
func setDefaultCacheSize(ctx *server.Context) {
99
if size, err := server.GetTotalMemory(); err == nil {
100
// Default the cache size to 1/4 of total memory. A larger cache size
101
// doesn't necessarily improve performance as this is memory that is
102
// dedicated to uncompressed blocks in RocksDB. A larger value here will
103
// compete with the OS buffer cache which holds compressed blocks.
104
ctx.CacheSize = size / 4
108
func initInsecure() error {
109
if !cliContext.Insecure || insecure.isSet {
110
return nil
111
}
112
// The --insecure flag was not specified on the command line, verify that the
113
// host refers to a loopback address.
114
if connHost != "" {
115
addr, err := net.ResolveIPAddr("ip", connHost)
116
if err != nil {
117
return err
118
}
119
if !addr.IP.IsLoopback() {
120
return fmt.Errorf("specify --insecure to listen on external address %s", connHost)
121
}
122
} else {
123
cliContext.Addr = net.JoinHostPort("localhost", connPort)
124
cliContext.HTTPAddr = net.JoinHostPort("localhost", httpPort)
125
}
126
return nil
127
}
128
129
// runStart starts the cockroach node using --store as the list of
130
// storage devices ("stores") on this machine and --join as the list
131
// of other active nodes used to join this node to the cockroach
132
// cluster, if this is its first time connecting.
133
func runStart(_ *cobra.Command, _ []string) error {
134
if err := initInsecure(); err != nil {
135
return err
136
}
138
// Default the log directory to the the "logs" subdirectory of the first
139
// non-memory store. We only do this for the "start" command which is why
140
// this work occurs here and not in an OnInitialize function.
141
f := flag.Lookup("log-dir")
142
if !log.DirSet() {
143
for _, spec := range cliContext.Stores.Specs {
144
if spec.InMemory {
147
if err := f.Value.Set(filepath.Join(spec.Path, "logs")); err != nil {
148
return err
149
}
150
break
151
}
152
}
153
154
// Make sure the path exists
155
if err := os.MkdirAll(f.Value.String(), 0755); err != nil {
156
return err
157
}
158
159
// Default user for servers.
160
cliContext.User = security.NodeUser
161
162
stopper := stop.NewStopper()
163
if err := cliContext.InitStores(stopper); err != nil {
164
return fmt.Errorf("failed to initialize stores: %s", err)
165
}
166
167
if err := cliContext.InitNode(); err != nil {
168
return fmt.Errorf("failed to initialize node: %s", err)
171
log.Info("starting cockroach node")
172
s, err := server.NewServer(&cliContext.Context, stopper)
173
if err != nil {
174
return fmt.Errorf("failed to start Cockroach server: %s", err)
175
}
176
177
// We don't do this in NewServer since we don't want it in tests.
178
if err := s.SetupReportingURLs(); err != nil {
179
return err
180
}
181
182
if err := s.Start(); err != nil {
183
return fmt.Errorf("cockroach server exited with error: %s", err)
186
pgURL, err := cliContext.PGURL(connUser)
187
if err != nil {
188
return err
189
}
190
Apr 18, 2016
191
info := build.GetInfo()
192
tw := tabwriter.NewWriter(os.Stdout, 2, 1, 2, ' ', 0)
193
fmt.Fprintf(tw, "build:\t%s @ %s (%s)\n", info.Tag, info.Time, info.GoVersion)
194
fmt.Fprintf(tw, "admin:\t%s\n", cliContext.AdminURL())
195
fmt.Fprintf(tw, "sql:\t%s\n", pgURL)
196
if len(cliContext.SocketFile) != 0 {
197
fmt.Fprintf(tw, "socket:\t%s\n", cliContext.SocketFile)
198
}
199
fmt.Fprintf(tw, "logs:\t%s\n", flag.Lookup("log-dir").Value)
200
for i, spec := range cliContext.Stores.Specs {
201
fmt.Fprintf(tw, "store[%d]:\t%s\n", i, spec)
202
}
203
if err := tw.Flush(); err != nil {
204
return err
205
}
207
signalCh := make(chan os.Signal, 1)
208
signal.Notify(signalCh, os.Interrupt, os.Kill)
209
// TODO(spencer): move this behind a build tag.
210
signal.Notify(signalCh, syscall.SIGTERM)
212
// Block until one of the signals above is received or the stopper
213
// is stopped externally (for example, via the quit endpoint).
214
select {
215
case <-stopper.ShouldStop():
216
case <-signalCh:
217
go s.Stop()
220
const msgDrain = "initiating graceful shutdown of server"
221
log.Info(msgDrain)
222
fmt.Fprintln(os.Stdout, msgDrain)
224
go func() {
225
ticker := time.NewTicker(5 * time.Second)
226
defer ticker.Stop()
227
for {
228
select {
229
case <-ticker.C:
230
if log.V(1) {
231
log.Infof("running tasks:\n%s", stopper.RunningTasks())
232
}
233
log.Infof("%d running tasks", stopper.NumTasks())
234
case <-stopper.ShouldStop():
235
return
236
}
237
}
238
}()
239
241
case <-signalCh:
242
log.Errorf("second signal received, initiating hard shutdown")
244
log.Errorf("time limit reached, initiating hard shutdown")
246
const msgDone = "server drained and shutdown completed"
247
log.Infof(msgDone)
248
fmt.Fprintln(os.Stdout, msgDone)
250
log.Flush()
Jul 28, 2015
254
// exterminateCmd command shuts down the node server and
255
// destroys all data held by the node.
256
var exterminateCmd = &cobra.Command{
257
Use: "exterminate",
258
Short: "destroy all data held by the node",
259
Long: `
260
First shuts down the system and then destroys all data held by the
261
node, cycling through each store specified by --store flags.
263
SilenceUsage: true,
Mar 3, 2016
264
RunE: runExterminate,
265
}
266
267
// runExterminate destroys the data held in the specified stores.
Mar 3, 2016
268
func runExterminate(_ *cobra.Command, _ []string) error {
269
stopper := stop.NewStopper()
270
defer stopper.Stop()
271
if err := cliContext.InitStores(stopper); err != nil {
Mar 3, 2016
272
return util.Errorf("failed to initialize context: %s", err)
Mar 3, 2016
275
if err := runQuit(nil, nil); err != nil {
276
return util.Errorf("shutdown node error: %s", err)
277
}
278
279
// Exterminate all data held in specified stores.
280
for _, e := range cliContext.Engines {
281
if rocksdb, ok := e.(*engine.RocksDB); ok {
282
log.Infof("exterminating data from store %s", e)
283
if err := rocksdb.Destroy(); err != nil {
Mar 3, 2016
284
return util.Errorf("unable to destroy store %s: %s", e, err)
288
log.Infof("exterminated all data from stores %s", cliContext.Engines)
Mar 3, 2016
289
return nil
Jul 28, 2015
292
// quitCmd command shuts down the node server.
293
var quitCmd = &cobra.Command{
294
Use: "quit",
295
Short: "drain and shutdown node\n",
296
Long: `
297
Shutdown the server. The first stage is drain, where any new requests
298
will be ignored by the server. When all extant requests have been
299
completed, the server exits.
300
`,
301
SilenceUsage: true,
302
RunE: runQuit,
305
// runQuit accesses the quit shutdown path.
306
func runQuit(_ *cobra.Command, _ []string) error {
307
admin, err := client.NewAdminClient(&cliContext.Context.Context, cliContext.HTTPAddr, client.Quit)
308
if err != nil {
309
return err
310
}
311
body, err := admin.Get()
312
// TODO(tschottdorf): needs cleanup. An error here can happen if the shutdown
313
// happened faster than the HTTP request made it back.
314
if err != nil {
317
fmt.Printf("node drained and shutdown: %s\n", body)