@@ -3,25 +3,34 @@ package main
33import (
44 "context"
55 "fmt"
6+ "io/ioutil"
67 "log"
78 "math/rand"
89 "os"
910 "os/signal"
11+ "strconv"
1012 "strings"
13+ "syscall"
1114
1215 "github.com/alecthomas/kingpin"
1316 "github.com/globalsign/mgo"
1417 "github.com/percona/mongodb-backup/grpc/client"
18+ "github.com/pkg/errors"
19+ "github.com/sirupsen/logrus"
1520 "google.golang.org/grpc"
1621 "google.golang.org/grpc/credentials"
1722)
1823
1924type cliOptios struct {
2025 app * kingpin.Application
2126 dsn * string
27+ pidFile * string
2228 serverAddress * string
29+ backupDir * string
2330 tls * bool
2431 caFile * string
32+ debug * bool
33+ quiet * bool
2534
2635 // MongoDB connection options
2736 mongodbConnOptions struct {
@@ -48,43 +57,64 @@ type cliOptios struct {
4857}
4958
5059func main () {
60+ log := logrus .New ()
5161 opts , err := processCliArgs ()
5262 if err != nil {
5363 log .Fatalf ("Cannot parse command line arguments: %s" , err )
5464 }
5565
66+ log .SetLevel (logrus .InfoLevel )
67+ if * opts .quiet {
68+ log .SetLevel (logrus .ErrorLevel )
69+ }
70+ if * opts .debug {
71+ log .SetLevel (logrus .DebugLevel )
72+ }
73+ log .SetLevel (logrus .DebugLevel )
74+
5675 grpcOpts := getgRPCOptions (opts )
5776 clientID := fmt .Sprintf ("ABC%04d" , rand .Int63n (10000 ))
58- log .Printf ("Using Client ID: %s" , clientID )
77+ log .Infof ("Using Client ID: %s" , clientID )
5978
6079 // Connect to the mongodb-backup gRPC server
6180 conn , err := grpc .Dial (* opts .serverAddress , grpcOpts ... )
6281 if err != nil {
63- log .Fatalf ("fail to dial : %v" , err )
82+ log .Fatalf ("Fail to connect to the gRPC server at %q : %v" , * opts . serverAddress , err )
6483 }
6584 defer conn .Close ()
66- log .Printf ("Connected to the gRPC server at %s" , * opts .serverAddress )
85+ log .Infof ("Connected to the gRPC server at %s" , * opts .serverAddress )
6786
6887 // Connect to the MongoDB instance
6988 var di * mgo.DialInfo
7089 if * opts .dsn == "" {
7190 di = & mgo.DialInfo {
72- Addrs : []string {opts .mongodbConnOptions .Host + ":" + opts .mongodbConnOptions .Port },
91+ Addrs : []string {opts .mongodbConnOptions .Host + ":" + opts .mongodbConnOptions .Port },
92+ Username : opts .mongodbConnOptions .User ,
93+ Password : opts .mongodbConnOptions .Password ,
94+ ReplicaSetName : opts .mongodbConnOptions .ReplicasetName ,
95+ FailFast : true ,
96+ Source : "admin" ,
7397 }
7498 } else {
7599 di , err = mgo .ParseURL (* opts .dsn )
100+ di .FailFast = true
76101 if err != nil {
77- log .Fatalf ("Cannot parse MongoDB dsn %q, %s" , * opts .dsn , err )
102+ log .Fatalf ("Cannot parse MongoDB DSN %q, %s" , * opts .dsn , err )
78103 }
79104 }
105+
80106 mdbSession , err := mgo .DialWithInfo (di )
81107 if err != nil {
82- log .Fatalf ("Cannot connect to MongoDB on %s: %s" , di .Addrs [0 ], err )
108+ log .Fatalf ("Cannot connect to MongoDB at %s: %s" , di .Addrs [0 ], err )
83109 }
84110 defer mdbSession .Close ()
85- log .Printf ("Connected to MongoDB at %s" , di .Addrs [0 ])
111+ log .Infof ("Connected to MongoDB at %s" , di .Addrs [0 ])
112+
113+ client , err := client .NewClient (context .Background (), * opts .backupDir , opts .mongodbConnOptions , opts .mongodbSslOptions , conn , log )
114+ if err != nil {
115+ log .Fatal (err )
116+ }
86117
87- client , err := client .NewClient (context .Background (), opts .mongodbConnOptions , opts .mongodbSslOptions , conn )
88118 c := make (chan os.Signal , 1 )
89119 signal .Notify (c , os .Interrupt )
90120
@@ -97,22 +127,34 @@ func processCliArgs() (*cliOptios, error) {
97127 opts := & cliOptios {
98128 app : app ,
99129 dsn : app .Flag ("dsn" , "MongoDB connection string" ).String (),
100- serverAddress : app .Flag ("server-address" , "MongoDB backup server address" ).String (),
130+ serverAddress : app .Flag ("server-address" , "MongoDB backup server address" ).Default ("127.0.0.1:10000" ).String (),
131+ backupDir : app .Flag ("backup-dir" , "Directory where to store the backups" ).Default ("/tmp" ).String (),
101132 tls : app .Flag ("tls" , "Use TLS" ).Bool (),
133+ pidFile : app .Flag ("pid-file" , "pid file" ).String (),
102134 caFile : app .Flag ("ca-file" , "CA file" ).String (),
135+ debug : app .Flag ("debug" , "Enable debug log level" ).Bool (),
136+ quiet : app .Flag ("quiet" , "Quiet mode. Log only errors" ).Bool (),
103137 }
104138
105- app .Flag ("mongodb-host" , "MongoDB host" ).StringVar (& opts .mongodbConnOptions .Host )
106- app .Flag ("mongodb-port" , "MongoDB port" ).StringVar (& opts .mongodbConnOptions .Port )
107- app .Flag ("mongodb-user" , "MongoDB username" ).StringVar (& opts .mongodbConnOptions .User )
108- app .Flag ("mongodb-password" , "MongoDB password" ).StringVar (& opts .mongodbConnOptions .Password )
109- app .Flag ("replicaset" , "Replicaset name" ).StringVar (& opts .mongodbConnOptions .ReplicasetName )
139+ //TODO: Remove defaults. These values are only here to test during development
140+ // These params should be Required()
141+ app .Flag ("mongodb-host" , "MongoDB host" ).Default ("127.0.0.1" ).StringVar (& opts .mongodbConnOptions .Host )
142+ app .Flag ("mongodb-port" , "MongoDB port" ).Default (os .Getenv ("TEST_MONGODB_S1_PRIMARY_PORT" )).StringVar (& opts .mongodbConnOptions .Port )
143+ app .Flag ("mongodb-user" , "MongoDB username" ).Default (os .Getenv ("TEST_MONGODB_USERNAME" )).StringVar (& opts .mongodbConnOptions .User )
144+ app .Flag ("mongodb-password" , "MongoDB password" ).Default (os .Getenv ("TEST_MONGODB_PASSWORD" )).StringVar (& opts .mongodbConnOptions .Password )
145+ app .Flag ("replicaset" , "Replicaset name" ).Default (os .Getenv ("TEST_MONGODB_S1_RS" )).StringVar (& opts .mongodbConnOptions .ReplicasetName )
110146
111147 _ , err := app .Parse (os .Args [1 :])
112148 if err != nil {
113149 return nil , err
114150 }
115151
152+ if * opts .pidFile != "" {
153+ if err := writePidFile (* opts .pidFile ); err != nil {
154+ return nil , errors .Wrapf (err , "cannot write pid file %q" , * opts .pidFile )
155+ }
156+ }
157+
116158 if * opts .dsn != "" {
117159 di , err := mgo .ParseURL (* opts .dsn )
118160 if err != nil {
@@ -128,6 +170,15 @@ func processCliArgs() (*cliOptios, error) {
128170 opts .mongodbConnOptions .Password = di .Password
129171 opts .mongodbConnOptions .ReplicasetName = di .ReplicaSetName
130172 }
173+
174+ fi , err := os .Stat (* opts .backupDir )
175+ if err != nil {
176+ return nil , errors .Wrap (err , "invalid backup destination dir" )
177+ }
178+ if ! fi .IsDir () {
179+ return nil , fmt .Errorf ("%q is not a directory" , * opts .backupDir )
180+ }
181+
131182 return opts , nil
132183}
133184
@@ -144,3 +195,24 @@ func getgRPCOptions(opts *cliOptios) []grpc.DialOption {
144195 }
145196 return grpcOpts
146197}
198+
199+ // Write a pid file, but first make sure it doesn't exist with a running pid.
200+ func writePidFile (pidFile string ) error {
201+ // Read in the pid file as a slice of bytes.
202+ if piddata , err := ioutil .ReadFile (pidFile ); err == nil {
203+ // Convert the file contents to an integer.
204+ if pid , err := strconv .Atoi (string (piddata )); err == nil {
205+ // Look for the pid in the process list.
206+ if process , err := os .FindProcess (pid ); err == nil {
207+ // Send the process a signal zero kill.
208+ if err := process .Signal (syscall .Signal (0 )); err == nil {
209+ // We only get an error if the pid isn't running, or it's not ours.
210+ return fmt .Errorf ("pid already running: %d" , pid )
211+ }
212+ }
213+ }
214+ }
215+ // If we get here, then the pidfile didn't exist,
216+ // or the pid in it doesn't belong to the user running this app.
217+ return ioutil .WriteFile (pidFile , []byte (fmt .Sprintf ("%d" , os .Getpid ())), 0664 )
218+ }
0 commit comments