Skip to content

Commit 2718f49

Browse files
authored
Merge pull request #58 from arangodb-helper/feature-detach
Start & stop detached starter
2 parents b394fb0 + 019e96b commit 2718f49

File tree

6 files changed

+296
-14
lines changed

6 files changed

+296
-14
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Changes from version 0.7.2 to master
2+
3+
- Added `start` command to run starter in detached mode.
4+
- Added `stop` command to stop a running starter using its HTTP API.
5+
16
# Changes from version 0.7.1 to 0.7.2
27

38
- Added path containing starter executable to search path for `arangod`.

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,31 @@ docker run -it --name=adb --rm -p 8528:8528 \
145145
--starter.mode=single
146146
```
147147

148+
Starting & stopping in detached mode
149+
------------------------------------
150+
151+
If you want the starter to detach and run as a background process, use the `start`
152+
command. This is typically used by developers running tests only.
153+
154+
```
155+
arangodb start --starter.local=true [--starter.wait]
156+
```
157+
158+
This command will make the starter run another starter process in the background
159+
(that starts all ArangoDB servers), wait for it's HTTP API to be available and
160+
then exit. The starter that was started in the background will keep running until you stop it.
161+
162+
The `--starter.wait` option makes the `start` command wait until all ArangoDB server
163+
are really up, before ending the master process.
164+
165+
To stop a starter use this command.
166+
167+
```
168+
arangodb stop
169+
```
170+
171+
Make sure to match the arguments given to start the starter (`--starter.port` & `--ssl.*`).
172+
148173
Common options
149174
--------------
150175

main.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ var (
100100
)
101101

102102
func init() {
103-
f := cmdMain.Flags()
103+
f := cmdMain.PersistentFlags()
104104

105105
f.StringVar(&masterAddress, "starter.join", "", "join a cluster with master at given address")
106106
f.StringVar(&mode, "starter.mode", "cluster", "Set the mode of operation to use (cluster|single)")
@@ -316,12 +316,33 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
316316
}
317317

318318
// Setup log level
319+
configureLogging()
320+
321+
// Interrupt signal:
322+
sigChannel := make(chan os.Signal)
323+
rootCtx, cancel := context.WithCancel(context.Background())
324+
signal.Notify(sigChannel, os.Interrupt, syscall.SIGTERM)
325+
go handleSignal(sigChannel, cancel)
326+
327+
// Create service
328+
service := mustPrepareService(true)
329+
330+
// Run the service
331+
service.Run(rootCtx)
332+
}
333+
334+
// configureLogging configures the log object according to command line arguments.
335+
func configureLogging() {
319336
if verbose {
320337
logging.SetLevel(logging.DEBUG, projectName)
321338
} else {
322339
logging.SetLevel(logging.INFO, projectName)
323340
}
341+
}
324342

343+
// mustPrepareService creates a new Service for the configured arguments,
344+
// creating & checking settings where needed.
345+
func mustPrepareService(generateAutoKeyFile bool) *service.Service {
325346
// Auto detect docker container ID (if needed)
326347
runningInDocker := false
327348
if isRunningInDocker() {
@@ -399,7 +420,7 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
399420
}
400421

401422
// Auto create key file (if needed)
402-
if sslAutoKeyFile {
423+
if sslAutoKeyFile && generateAutoKeyFile {
403424
if sslKeyFile != "" {
404425
log.Fatalf("Cannot specify both --ssl.auto-key and --ssl.keyfile")
405426
}
@@ -422,12 +443,6 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
422443
log.Infof("Using self-signed certificate: %s", sslKeyFile)
423444
}
424445

425-
// Interrupt signal:
426-
sigChannel := make(chan os.Signal)
427-
rootCtx, cancel := context.WithCancel(context.Background())
428-
signal.Notify(sigChannel, os.Interrupt, syscall.SIGTERM)
429-
go handleSignal(sigChannel, cancel)
430-
431446
// Create service
432447
serviceConfig := service.Config{
433448
ID: id,
@@ -471,8 +486,7 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
471486
log.Fatalf("Failed to create service: %#v", err)
472487
}
473488

474-
// Run the service
475-
service.Run(rootCtx)
489+
return service
476490
}
477491

478492
// getEnvVar returns the value of the environment variable with given key of the given default

service/arangodb.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ func (s *Service) serverExecutable() string {
245245
return s.ArangodPath
246246
}
247247

248-
// testInstance checks the `up` status of an arangod server instance.
249-
func (s *Service) testInstance(ctx context.Context, address string, port int) (up bool, version string, cancelled bool) {
248+
// TestInstance checks the `up` status of an arangod server instance.
249+
func (s *Service) TestInstance(ctx context.Context, address string, port int) (up bool, version string, cancelled bool) {
250250
instanceUp := make(chan string)
251251
go func() {
252252
client := &http.Client{Timeout: time.Second * 10}
@@ -544,7 +544,7 @@ func (s *Service) startArangod(runner Runner, myHostAddress string, serverType S
544544
if p != nil {
545545
s.log.Infof("%s seems to be running already, checking port %d...", serverType, myPort)
546546
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
547-
up, _, _ := s.testInstance(ctx, myHostAddress, myPort)
547+
up, _, _ := s.TestInstance(ctx, myHostAddress, myPort)
548548
cancel()
549549
if up {
550550
s.log.Infof("%s is already running on %d. No need to start anything.", serverType, myPort)
@@ -639,7 +639,7 @@ func (s *Service) runArangod(runner Runner, myPeer Peer, serverType ServerType,
639639
if err != nil {
640640
s.log.Fatalf("Cannot collect serverPort: %#v", err)
641641
}
642-
if up, version, cancelled := s.testInstance(ctx, myHostAddress, port); !cancelled {
642+
if up, version, cancelled := s.TestInstance(ctx, myHostAddress, port); !cancelled {
643643
if up {
644644
s.log.Infof("%s up and running (version %s).", serverType, version)
645645
if (serverType == ServerTypeCoordinator && !s.isLocalSlave) || serverType == ServerTypeSingle {

start.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Ewout Prangsma
21+
//
22+
23+
package main
24+
25+
import (
26+
"context"
27+
"fmt"
28+
"net/url"
29+
"os"
30+
"os/exec"
31+
"time"
32+
33+
"github.com/arangodb-helper/arangodb/client"
34+
"github.com/spf13/cobra"
35+
"github.com/spf13/pflag"
36+
)
37+
38+
var (
39+
cmdStart = &cobra.Command{
40+
Use: "start",
41+
Short: "Start the ArangoDB starter in the background",
42+
Run: cmdStartRun,
43+
}
44+
waitForServers bool
45+
)
46+
47+
func init() {
48+
f := cmdStart.Flags()
49+
f.BoolVar(&waitForServers, "starter.wait", false, "If set, the (parent) starter waits until all database servers are ready before exiting.")
50+
51+
cmdMain.AddCommand(cmdStart)
52+
}
53+
54+
func cmdStartRun(cmd *cobra.Command, args []string) {
55+
log.Infof("Starting %s version %s, build %s in the background", projectName, projectVersion, projectBuild)
56+
57+
// Setup logging
58+
configureLogging()
59+
60+
// Build service
61+
service := mustPrepareService(true)
62+
63+
// Find executable
64+
exePath, err := os.Executable()
65+
if err != nil {
66+
log.Fatalf("Cannot find executable path: %#v", err)
67+
}
68+
69+
// Build command line
70+
childArgs := make([]string, 0, len(os.Args))
71+
cmd.InheritedFlags().VisitAll(func(f *pflag.Flag) {
72+
if f.Changed {
73+
switch f.Name {
74+
case "ssl.auto-key", "ssl.auto-server-name", "ssl.auto-organization", "ssl.keyfile":
75+
// Do not pass these along
76+
default:
77+
a := "--" + f.Name
78+
value := f.Value.String()
79+
if value != "" {
80+
a = a + "=" + value
81+
}
82+
childArgs = append(childArgs, a)
83+
}
84+
}
85+
})
86+
if service.SslKeyFile != "" {
87+
childArgs = append(childArgs, "--ssl.keyfile="+service.SslKeyFile)
88+
}
89+
90+
log.Debugf("Found child args: %#v", childArgs)
91+
92+
// Start detached child
93+
c := exec.Command(exePath, childArgs...)
94+
if err := c.Start(); err != nil {
95+
log.Fatalf("Failed to start detached child: %#v", err)
96+
}
97+
c.Process.Release()
98+
99+
// Create starter client
100+
scheme := "http"
101+
if sslAutoKeyFile || sslKeyFile != "" {
102+
scheme = "https"
103+
}
104+
starterURL, err := url.Parse(fmt.Sprintf("%s://127.0.0.1:%d", scheme, masterPort))
105+
if err != nil {
106+
log.Fatalf("Failed to create starter URL: %#v", err)
107+
}
108+
client, err := client.NewArangoStarterClient(*starterURL)
109+
if err != nil {
110+
log.Fatalf("Failed to create starter client: %#v", err)
111+
}
112+
113+
// Wait for detached starter to be alive
114+
log.Info("Waiting for starter API to be available...")
115+
rootCtx := context.Background()
116+
for {
117+
ctx, cancel := context.WithTimeout(rootCtx, time.Second)
118+
_, err := client.Version(ctx)
119+
cancel()
120+
if err == nil {
121+
break
122+
}
123+
time.Sleep(time.Millisecond * 100)
124+
}
125+
126+
// Wait until all servers ready (if needed)
127+
if waitForServers {
128+
log.Info("Waiting for database instances to be available...")
129+
for {
130+
var err error
131+
ctx, cancel := context.WithTimeout(rootCtx, time.Second)
132+
list, err := client.Processes(ctx)
133+
cancel()
134+
if err == nil && list.ServersStarted {
135+
// Start says it has started the servers, now wait for servers to be up.
136+
allUp := true
137+
for _, server := range list.Servers {
138+
ctx, cancel := context.WithTimeout(rootCtx, time.Second)
139+
up, _, _ := service.TestInstance(ctx, server.IP, server.Port)
140+
cancel()
141+
if !up {
142+
allUp = false
143+
break
144+
}
145+
}
146+
if allUp {
147+
break
148+
}
149+
}
150+
time.Sleep(time.Millisecond * 100)
151+
}
152+
log.Info("Database instances are available.")
153+
}
154+
}

stop.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Ewout Prangsma
21+
//
22+
23+
package main
24+
25+
import (
26+
"context"
27+
"fmt"
28+
"net/url"
29+
"time"
30+
31+
"github.com/arangodb-helper/arangodb/client"
32+
"github.com/spf13/cobra"
33+
)
34+
35+
var (
36+
cmdStop = &cobra.Command{
37+
Use: "stop",
38+
Short: "Stop a ArangoDB starter",
39+
Run: cmdStopRun,
40+
}
41+
)
42+
43+
func init() {
44+
cmdMain.AddCommand(cmdStop)
45+
}
46+
47+
func cmdStopRun(cmd *cobra.Command, args []string) {
48+
// Setup logging
49+
configureLogging()
50+
51+
// Create starter client
52+
scheme := "http"
53+
if sslAutoKeyFile || sslKeyFile != "" {
54+
scheme = "https"
55+
}
56+
starterURL, err := url.Parse(fmt.Sprintf("%s://127.0.0.1:%d", scheme, masterPort))
57+
if err != nil {
58+
log.Fatalf("Failed to create starter URL: %#v", err)
59+
}
60+
client, err := client.NewArangoStarterClient(*starterURL)
61+
if err != nil {
62+
log.Fatalf("Failed to create starter client: %#v", err)
63+
}
64+
65+
// Shutdown starter
66+
rootCtx := context.Background()
67+
ctx, cancel := context.WithTimeout(rootCtx, time.Minute)
68+
err = client.Shutdown(ctx, false)
69+
cancel()
70+
if err != nil {
71+
log.Fatalf("Failed to shutdown starter: %#v", err)
72+
}
73+
74+
// Wait for starter to be really gone
75+
for {
76+
ctx, cancel := context.WithTimeout(rootCtx, time.Second)
77+
_, err := client.Version(ctx)
78+
cancel()
79+
if err != nil {
80+
break
81+
}
82+
time.Sleep(time.Millisecond * 100)
83+
}
84+
}

0 commit comments

Comments
 (0)