Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 80 additions & 17 deletions cli/mongodb-backup-admin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,28 @@ type cliOptions struct {

listClients *kingpin.CmdClause

startBackup *kingpin.CmdClause
backup *kingpin.CmdClause
backupType *string
destinationType *string
compressionAlgorithm *string
encryptionAlgorithm *string
description *string

restore *kingpin.CmdClause
restoreMetadata *string
restore *kingpin.CmdClause
restoreMetadataFile *string
restoreSkipUsersAndRoles *bool

listBackups *kingpin.CmdClause
}

var (
conn *grpc.ClientConn
)

const (
defaultServerAddr = "127.0.0.1:10001"
)

func main() {
cmd, opts, err := processCliArgs(os.Args[1:])
if err != nil {
Expand All @@ -62,7 +71,7 @@ func main() {
grpcOpts = append(grpcOpts, grpc.WithInsecure())
}

conn, err := grpc.Dial(*opts.serverAddr, grpcOpts...)
conn, err = grpc.Dial(*opts.serverAddr, grpcOpts...)
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
Expand Down Expand Up @@ -92,12 +101,19 @@ func main() {
return
}
fmt.Println("No backups found")
case "start-backup":
case "backup":
err := startBackup(apiClient, opts)
if err != nil {
log.Fatal(err)
log.Fatalf("Cannot send the StartBackup command to the gRPC server: %s", err)
}
case "restore":
fmt.Println("restoring")
err := restoreBackup(apiClient, opts)
if err != nil {
log.Fatal(err)
log.Fatalf("Cannot send the RestoreBackup command to the gRPC server: %s", err)
}
}

}
Expand Down Expand Up @@ -145,10 +161,36 @@ func getAvailableBackups(conn *grpc.ClientConn) (map[string]*pb.BackupMetadata,
return mds, nil
}

// This function is used by autocompletion. Currently, when it is called, the gRPC connection is nil
// because command line parameters havent been processed yet.
// Maybe in the future, we could read the defaults from a config file. For now, just try to connect
// to a server running on the local host
func listAvailableBackups() (backups []string) {
var err error
if conn == nil {
conn, err = grpc.Dial(defaultServerAddr, []grpc.DialOption{grpc.WithInsecure()}...)
if err != nil {
return
}
defer conn.Close()
}

mds, err := getAvailableBackups(conn)
if err != nil {
return
}

for name, md := range mds {
backup := fmt.Sprintf("%s -> %s", name, md.Description)
backups = append(backups, backup)
}
return
}

func printAvailableBackups(md map[string]*pb.BackupMetadata) {
fmt.Println(" Metadata file name - Description")
for name, md := range md {
fmt.Printf("%30s - %s\n", name, md.Description)
for name, metadata := range md {
fmt.Printf("%30s - %s\n", name, metadata.Description)
}
fmt.Println("")
}
Expand Down Expand Up @@ -205,26 +247,47 @@ func startBackup(apiClient pbapi.ApiClient, opts *cliOptions) error {
return nil
}

func restoreBackup(apiClient pbapi.ApiClient, opts *cliOptions) error {
msg := &pbapi.RunRestoreParams{
MetadataFile: *opts.restoreMetadataFile,
SkipUsersAndRoles: *opts.restoreSkipUsersAndRoles,
}

_, err := apiClient.RunRestore(context.Background(), msg)
if err != nil {
return err
}

return nil
}

func processCliArgs(args []string) (string, *cliOptions, error) {
app := kingpin.New("mongodb-backup-admin", "MongoDB backup admin")
listClientsCmd := app.Command("list-agents", "List all agents connected to the server")
listBackups := app.Command("list-backups", "List all backups (metadata files) stored in the server working directory")
startBackupCmd := app.Command("start-backup", "Start a backup")
listBackupsCmd := app.Command("list-backups", "List all backups (metadata files) stored in the server working directory")
backupCmd := app.Command("start-backup", "Start a backup")
restoreCmd := app.Command("restore", "Restore a backup given a metadata file name")

opts := &cliOptions{
tls: app.Flag("tls", "Connection uses TLS if true, else plain TCP").Default("false").Bool(),
caFile: app.Flag("ca-file", "The file containning the CA root cert file").String(),
serverAddr: app.Flag("server-addr", "The server address in the format of host:port").Default("127.0.0.1:10001").String(),
serverAddr: app.Flag("server-addr", "The server address in the format of host:port").Default(defaultServerAddr).String(),

listClients: listClientsCmd,

startBackup: startBackupCmd,
backupType: startBackupCmd.Flag("backup-type", "Backup type").Enum("logical", "hot"),
destinationType: startBackupCmd.Flag("destination-type", "Backup destination type").Enum("file", "aws"),
compressionAlgorithm: startBackupCmd.Flag("compression-algorithm", "Compression algorithm used for the backup").String(),
encryptionAlgorithm: startBackupCmd.Flag("encryption-algorithm", "Encryption algorithm used for the backup").String(),
description: startBackupCmd.Flag("description", "Backup description").Required().String(),
listBackups: listBackups,
backup: backupCmd,
backupType: backupCmd.Flag("backup-type", "Backup type").Enum("logical", "hot"),
destinationType: backupCmd.Flag("destination-type", "Backup destination type").Enum("file", "aws"),
compressionAlgorithm: backupCmd.Flag("compression-algorithm", "Compression algorithm used for the backup").String(),
encryptionAlgorithm: backupCmd.Flag("encryption-algorithm", "Encryption algorithm used for the backup").String(),
description: backupCmd.Flag("description", "Backup description").Required().String(),

listBackups: listBackupsCmd,

restore: restoreCmd,
restoreMetadataFile: restoreCmd.Arg("metadata-file", "Metadata file having the backup info for restore").
HintAction(listAvailableBackups).Required().String(),
restoreSkipUsersAndRoles: restoreCmd.Flag("skip-users-and-roles", "Do not restore users and roles").Default("true").Bool(),
}

cmd, err := app.Parse(args)
Expand Down
9 changes: 9 additions & 0 deletions grpc/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,12 @@ func (a *ApiServer) RunBackup(ctx context.Context, opts *pbapi.RunBackupParams)
logger.Debug("Balancer started")
return &pbapi.Error{}, nil
}

func (a *ApiServer) RunRestore(ctx context.Context, opts *pbapi.RunRestoreParams) (*pbapi.Error, error) {
err := a.messagesServer.RestoreBackupFromMetadataFile(opts.MetadataFile, opts.SkipUsersAndRoles)
if err != nil {
return &pbapi.Error{Message: err.Error()}, err
}

return &pbapi.Error{}, nil
}
2 changes: 2 additions & 0 deletions grpc/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ func (c *Client) processIncommingServerMessages() {
//var response *pb.ClientMessage

c.logger.Debugf("Client %s -> incoming message: %+v", c.nodeName, msg)
c.logger.Infof("Client %s -> incoming message: %+v", c.nodeName, msg)
switch msg.Payload.(type) {
case *pb.ServerMessage_GetStatusMsg:
c.processStatus()
Expand Down Expand Up @@ -397,6 +398,7 @@ func (c *Client) processRestore(msg *pb.RestoreBackup) error {
func (c *Client) sendRestoreComplete(err error) error {
msg := &pb.RestoreComplete{
ClientID: c.id,
Err: &pb.Error{},
}
if err != nil {
msg.Err = &pb.Error{
Expand Down
18 changes: 15 additions & 3 deletions grpc/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,21 @@ func (s *MessagesServer) ReplicasetsRunningRestore() map[string]*Client {
return replicasets
}

// RestoreBackupFromMetadataFile is just a wrappwe around RestoreBackUp that receives a metadata filename
// loads and parse it and then call RestoreBackUp
func (s *MessagesServer) RestoreBackupFromMetadataFile(filename string, skipUsersAndRoles bool) error {
filename = filepath.Join(s.workDir, filename)
bm, err := LoadMetadataFromFile(filename)
if err != nil {
return fmt.Errorf("Invalid backup metadata file %s: %s", filename, err)
}

return s.RestoreBackUp(bm.Metadata(), skipUsersAndRoles)
}

// RestoreBackUp will run a restore on each client, using the provided backup metadata to choose the source for each
// replicaset.
func (s *MessagesServer) RestoreBackUp(bm *pb.BackupMetadata, SkipUsersAndRoles bool) error {
func (s *MessagesServer) RestoreBackUp(bm *pb.BackupMetadata, skipUsersAndRoles bool) error {
clients, err := s.BackupSourceByReplicaset()
if err != nil {
return errors.Wrapf(err, "Cannot start backup restore. Cannot find backup source for replicas")
Expand All @@ -226,7 +238,7 @@ func (s *MessagesServer) RestoreBackUp(bm *pb.BackupMetadata, SkipUsersAndRoles
s.setRestoreRunning(true)

for replName, client := range clients {
s.logger.Printf("Starting backup for replicaset %q on client %s %s %s", replName, client.ID, client.NodeName, client.NodeType)
s.logger.Printf("Starting restore for replicaset %q on client %s %s %s", replName, client.ID, client.NodeName, client.NodeType)
s.replicasRunningBackup[replName] = true
for bmReplName, metadata := range bm.Replicasets {
if bmReplName == replName {
Expand All @@ -239,7 +251,7 @@ func (s *MessagesServer) RestoreBackUp(bm *pb.BackupMetadata, SkipUsersAndRoles
OplogSourceName: metadata.OplogBackupName,
CompressionType: bm.CompressionType,
Cypher: bm.Cypher,
SkipUsersAndRoles: SkipUsersAndRoles,
SkipUsersAndRoles: skipUsersAndRoles,
})
}
}
Expand Down
Loading