diff --git a/cmd/sansshell-server/default-policy.rego b/cmd/sansshell-server/default-policy.rego index d9956f1f..473a679e 100644 --- a/cmd/sansshell-server/default-policy.rego +++ b/cmd/sansshell-server/default-policy.rego @@ -121,3 +121,14 @@ allow { input.message.zero = true input.message.remove = true } + +# Allow fdbbackup commands +allow { + input.type = "Exec.ExecRequest" + input.message.command = "/usr/sbin/fdbbackup" +} + +# Allow all FDBBackup service methods +allow { + startswith(input.method, "/Fdb.FDBBackup/") +} diff --git a/cmd/sansshell-server/main.go b/cmd/sansshell-server/main.go index df8b5285..deb30ebf 100644 --- a/cmd/sansshell-server/main.go +++ b/cmd/sansshell-server/main.go @@ -109,6 +109,7 @@ func init() { *fdbCLIEnvList.Target = append(*fdbCLIEnvList.Target, "") // To set a default flag.Var(&fdbCLIEnvList, "fdbcli-env-list", "List of environment variable names (separated by comma) to retain before fork/exec'ing fdbcli") flag.StringVar(&fdbserver.FDBMoveOrchestrator, "fdb-move-orchestrator", "/usr/bin/fdb_move_orchestrator.py", "Path to python data movement script.") + flag.StringVar(&fdbserver.FDBBackup, "fdbbackup", "/usr/sbin/fdbbackup", "Path to fdbbackup binary.") flag.StringVar(&mtlsFlags.ClientCertFile, "client-cert", mtlsFlags.ClientCertFile, "Path to this client's x509 cert, PEM format") flag.StringVar(&mtlsFlags.ClientKeyFile, "client-key", mtlsFlags.ClientKeyFile, "Path to this client's key") diff --git a/services/fdb/client/client.go b/services/fdb/client/client.go index 14250dd8..f19ff22c 100644 --- a/services/fdb/client/client.go +++ b/services/fdb/client/client.go @@ -102,6 +102,7 @@ func (*fdbCmd) GetSubpackage(f *flag.FlagSet) *subcommands.Commander { c.Register(&fdbConfCmd{}, "") c.Register(&fdbServerCmd{}, "") c.Register(&fdbMoveDataCmd{}, "") + c.Register(&fdbBackupCmd{}, "") return c } @@ -3769,3 +3770,381 @@ func (r *fdbMoveDataWaitCmd) Execute(ctx context.Context, f *flag.FlagSet, args } } } + +const fdbBackupCLIPackage = "fdbbackup" + +func (*fdbBackupCmd) GetSubpackage(f *flag.FlagSet) *subcommands.Commander { + c := client.SetupSubpackage(fdbBackupCLIPackage, f) + c.Register(&fdbBackupStatusCmd{}, "") + c.Register(&fdbBackupAbortCmd{}, "") + c.Register(&fdbBackupStartCmd{}, "") + c.Register(&fdbBackupDescribeCmd{}, "") + c.Register(&fdbBackupExpireCmd{}, "") + c.Register(&fdbBackupPauseCmd{}, "") + c.Register(&fdbBackupResumeCmd{}, "") + return c +} + +type fdbBackupCmd struct{} + +func (*fdbBackupCmd) Name() string { return fdbBackupCLIPackage } +func (*fdbBackupCmd) SetFlags(_ *flag.FlagSet) {} +func (r *fdbBackupCmd) Synopsis() string { + return "Run fdbbackup commands to manage backups.\n" + client.GenerateSynopsis(r.GetSubpackage(flag.NewFlagSet("", flag.ContinueOnError)), 4) +} +func (r *fdbBackupCmd) Usage() string { + return client.GenerateUsage(fdbBackupCLIPackage, r.Synopsis()) +} + +func (r *fdbBackupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + c := r.GetSubpackage(f) + return c.Execute(ctx, args...) +} + +type fdbBackupStatusCmd struct { + req *pb.FDBBackupStatusRequest +} + +func (*fdbBackupStatusCmd) Name() string { return "status" } +func (*fdbBackupStatusCmd) Synopsis() string { + return "Get the status of a backup." +} +func (r *fdbBackupStatusCmd) Usage() string { + return "fdbbackup status [--cluster-file ] [--blob_url ]" +} + +func (r *fdbBackupStatusCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupStatusRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") +} + +func (r *fdbBackupStatusCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupStatusOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup status error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup status error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} + +type fdbBackupAbortCmd struct { + req *pb.FDBBackupAbortRequest +} + +func (*fdbBackupAbortCmd) Name() string { return "abort" } +func (*fdbBackupAbortCmd) Synopsis() string { + return "Abort a backup." +} +func (r *fdbBackupAbortCmd) Usage() string { + return "fdbbackup abort [--cluster-file ] [--blob_url ]" +} + +func (r *fdbBackupAbortCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupAbortRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") +} + +func (r *fdbBackupAbortCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupAbortOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup abort error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup abort error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} + +type fdbBackupStartCmd struct { + req *pb.FDBBackupStartRequest +} + +func (*fdbBackupStartCmd) Name() string { return "start" } +func (*fdbBackupStartCmd) Synopsis() string { + return "Start a backup." +} +func (r *fdbBackupStartCmd) Usage() string { + return "fdbbackup start [--cluster-file ] [--blob_url ] [--snapshot] [--tag ]" +} + +func (r *fdbBackupStartCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupStartRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") + f.BoolVar(&r.req.Snapshot, "snapshot", false, "Create a snapshot backup.") + f.StringVar(&r.req.Tag, "tag", "", "Tag for the backup.") +} + +func (r *fdbBackupStartCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupStartOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup start error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup start error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} + +type fdbBackupDescribeCmd struct { + req *pb.FDBBackupDescribeRequest +} + +func (*fdbBackupDescribeCmd) Name() string { return "describe" } +func (*fdbBackupDescribeCmd) Synopsis() string { + return "Describe a backup." +} +func (r *fdbBackupDescribeCmd) Usage() string { + return "fdbbackup describe [--cluster-file ] [--blob_url ]" +} + +func (r *fdbBackupDescribeCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupDescribeRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") +} + +func (r *fdbBackupDescribeCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupDescribeOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup describe error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup describe error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} + +type fdbBackupExpireCmd struct { + req *pb.FDBBackupExpireRequest +} + +func (*fdbBackupExpireCmd) Name() string { return "expire" } +func (*fdbBackupExpireCmd) Synopsis() string { + return "Expire backups before a specified version." +} +func (r *fdbBackupExpireCmd) Usage() string { + return "fdbbackup expire [--cluster-file ] [--blob_url ] [--version ]" +} + +func (r *fdbBackupExpireCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupExpireRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") + f.StringVar(&r.req.Version, "version", "", "Version before which to expire backups.") +} + +func (r *fdbBackupExpireCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupExpireOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup expire error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup expire error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} + +type fdbBackupPauseCmd struct { + req *pb.FDBBackupPauseRequest +} + +func (*fdbBackupPauseCmd) Name() string { return "pause" } +func (*fdbBackupPauseCmd) Synopsis() string { + return "Pause a backup." +} +func (r *fdbBackupPauseCmd) Usage() string { + return "fdbbackup pause [--cluster-file ] [--blob_url ] [--tag ]" +} + +func (r *fdbBackupPauseCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupPauseRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") + f.StringVar(&r.req.Tag, "tag", "", "Tag for the backup.") +} + +func (r *fdbBackupPauseCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupPauseOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup pause error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup pause error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} + +type fdbBackupResumeCmd struct { + req *pb.FDBBackupResumeRequest +} + +func (*fdbBackupResumeCmd) Name() string { return "resume" } +func (*fdbBackupResumeCmd) Synopsis() string { + return "Resume a backup." +} +func (r *fdbBackupResumeCmd) Usage() string { + return "fdbbackup resume [--cluster-file ] [--blob_url ] [--tag ]" +} + +func (r *fdbBackupResumeCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBBackupResumeRequest{} + f.StringVar(&r.req.ClusterFile, "cluster-file", "", "Path to the cluster file.") + f.StringVar(&r.req.BackupUrl, "blob_url", "", "Backup URL.") + f.StringVar(&r.req.Tag, "tag", "", "Tag for the backup.") +} + +func (r *fdbBackupResumeCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + state := args[0].(*util.ExecuteState) + c := pb.NewFDBBackupClientProxy(state.Conn) + + resp, err := c.FDBBackupResumeOneMany(ctx, r.req) + if err != nil { + for _, e := range state.Err { + fmt.Fprintf(e, "fdbbackup resume error: %v\n", err) + } + return subcommands.ExitFailure + } + + retCode := subcommands.ExitSuccess + for r := range resp { + if r.Error != nil { + fmt.Fprintf(state.Err[r.Index], "fdbbackup resume error: %v\n", r.Error) + retCode = subcommands.ExitFailure + continue + } + fmt.Fprintf(state.Out[r.Index], "%s", r.Resp.Stdout) + if len(r.Resp.Stderr) > 0 { + fmt.Fprintf(state.Err[r.Index], "%s", r.Resp.Stderr) + } + if r.Resp.RetCode != 0 { + retCode = subcommands.ExitFailure + } + } + + return retCode +} diff --git a/services/fdb/client/test/fdbbackup_test.go b/services/fdb/client/test/fdbbackup_test.go new file mode 100644 index 00000000..f2378579 --- /dev/null +++ b/services/fdb/client/test/fdbbackup_test.go @@ -0,0 +1,132 @@ +/* Copyright (c) 2023 Snowflake Inc. All rights reserved. + 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 test + +import ( + "context" + "log" + "net" + "os" + "testing" + + "github.com/Snowflake-Labs/sansshell/services" + pb "github.com/Snowflake-Labs/sansshell/services/fdb" + _ "github.com/Snowflake-Labs/sansshell/services/fdb/server" // Initialize the server to register services + "github.com/Snowflake-Labs/sansshell/testing/testutil" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" +) + +var ( + bufSize = 1024 * 1024 + lis *bufconn.Listener +) + +func bufDialer(context.Context, string) (net.Conn, error) { + return lis.Dial() +} + +func TestMain(m *testing.M) { + lis = bufconn.Listen(bufSize) + s := grpc.NewServer() + for _, svc := range services.ListServices() { + svc.Register(s) + } + go func() { + if err := s.Serve(lis); err != nil { + log.Fatalf("Server exited with error: %v", err) + } + }() + defer s.GracefulStop() + + os.Exit(m.Run()) +} + +// TestFDBBackupCommands tests the basic functionality of fdbbackup commands +func TestFDBBackupCommands(t *testing.T) { + ctx := context.Background() + + // Create a test connection + conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { conn.Close() }) + + // Get a client + client := pb.NewBackupClient(conn) + + // Test cases + testCases := []struct { + name string + request *pb.FDBBackupRequest + wantErr bool + }{ + { + name: "status", + request: &pb.FDBBackupRequest{ + Commands: []*pb.FDBBackupCommand{ + { + Command: &pb.FDBBackupCommand_Status{ + Status: &pb.FDBBackupStatus{}, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "start backup", + request: &pb.FDBBackupRequest{ + Commands: []*pb.FDBBackupCommand{ + { + Command: &pb.FDBBackupCommand_Start{ + Start: &pb.FDBBackupStart{ + DestinationUrl: "s3://test-bucket", + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "abort backup", + request: &pb.FDBBackupRequest{ + Commands: []*pb.FDBBackupCommand{ + { + Command: &pb.FDBBackupCommand_Abort{ + Abort: &pb.FDBBackupAbort{}, + }, + }, + }, + }, + wantErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := client.FDBBackup(ctx, tc.request) + if tc.wantErr { + if err == nil { + t.Errorf("Expected error for %s, but got success", tc.name) + } + return + } + testutil.FatalOnErr(tc.name, err, t) + }) + } +} diff --git a/services/fdb/fdb.proto b/services/fdb/fdb.proto index a0e53364..72539ef3 100644 --- a/services/fdb/fdb.proto +++ b/services/fdb/fdb.proto @@ -718,3 +718,60 @@ message FDBServerResponse { bytes stderr = 2; int32 retCode = 3; } + +// fdbbackup +service FDBBackup { + rpc FDBBackupStatus(FDBBackupStatusRequest) returns (FDBBackupResponse) {} + rpc FDBBackupAbort(FDBBackupAbortRequest) returns (FDBBackupResponse) {} + rpc FDBBackupStart(FDBBackupStartRequest) returns (FDBBackupResponse) {} + rpc FDBBackupDescribe(FDBBackupDescribeRequest) returns (FDBBackupResponse) {} + rpc FDBBackupExpire(FDBBackupExpireRequest) returns (FDBBackupResponse) {} + rpc FDBBackupPause(FDBBackupPauseRequest) returns (FDBBackupResponse) {} + rpc FDBBackupResume(FDBBackupResumeRequest) returns (FDBBackupResponse) {} +} + +message FDBBackupStatusRequest { + string cluster_file = 1; + string backup_url = 2; +} + +message FDBBackupAbortRequest { + string cluster_file = 1; + string backup_url = 2; +} + +message FDBBackupStartRequest { + string cluster_file = 1; + string backup_url = 2; + bool snapshot = 3; + string tag = 4; +} + +message FDBBackupDescribeRequest { + string cluster_file = 1; + string backup_url = 2; +} + +message FDBBackupExpireRequest { + string cluster_file = 1; + string backup_url = 2; + string version = 3; +} + +message FDBBackupPauseRequest { + string cluster_file = 1; + string backup_url = 2; + string tag = 3; +} + +message FDBBackupResumeRequest { + string cluster_file = 1; + string backup_url = 2; + string tag = 3; +} + +message FDBBackupResponse { + bytes stdout = 1; + bytes stderr = 2; + int32 retCode = 3; +} diff --git a/services/fdb/server/fdbbackup.go b/services/fdb/server/fdbbackup.go new file mode 100644 index 00000000..eb4efab3 --- /dev/null +++ b/services/fdb/server/fdbbackup.go @@ -0,0 +1,222 @@ +/* Copyright (c) 2023 Snowflake Inc. All rights reserved. + 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 server + +import ( + "context" + "errors" + "os" + "strings" + + "github.com/Snowflake-Labs/sansshell/services" + pb "github.com/Snowflake-Labs/sansshell/services/fdb" + "github.com/Snowflake-Labs/sansshell/services/util" + "github.com/Snowflake-Labs/sansshell/telemetry/metrics" + "go.opentelemetry.io/otel/attribute" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + // FDBBackup binary location + FDBBackup string = "/usr/sbin/fdbbackup" + + // FDBBackupEnvFile is a file with newline-separated environment variables to set before running fdbbackup + FDBBackupEnvFile string = "/etc/fdb.env" +) + +// Metrics +var ( + fdbbackupFailureCounter = metrics.MetricDefinition{Name: "actions_fdbbackup_failure", + Description: "number of failures when performing fdbbackup"} +) + +type fdbbackup struct{} + +func runFDBBackupCommand(ctx context.Context, args []string) (*pb.FDBBackupResponse, error) { + recorder := metrics.RecorderFromContextOrNoop(ctx) + + command := []string{FDBBackup} + command = append(command, args...) + + var opts []util.Option + // Add env vars from file if it exists + if _, err := os.Stat(FDBBackupEnvFile); !errors.Is(err, os.ErrNotExist) { + content, err := os.ReadFile(FDBBackupEnvFile) + if err != nil { + return nil, err + } + for _, l := range strings.Split(string(content), "\n") { + if l != "" { + opts = append(opts, util.EnvVar(l)) + } + } + } + + run, err := util.RunCommand(ctx, command[0], command[1:], opts...) + if err != nil { + recorder.CounterOrLog(ctx, fdbbackupFailureCounter, 1, attribute.String("reason", "run_err")) + return nil, status.Errorf(codes.Internal, "error running fdbbackup cmd (%+v): %v", command, err) + } + + resp := &pb.FDBBackupResponse{ + RetCode: int32(run.ExitCode), + Stderr: run.Stderr.Bytes(), + Stdout: run.Stdout.Bytes(), + } + + return resp, nil +} + +func (s *fdbbackup) FDBBackupStatus(ctx context.Context, req *pb.FDBBackupStatusRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "status") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + return runFDBBackupCommand(ctx, args) +} + +func (s *fdbbackup) FDBBackupAbort(ctx context.Context, req *pb.FDBBackupAbortRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "abort") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + return runFDBBackupCommand(ctx, args) +} + +func (s *fdbbackup) FDBBackupStart(ctx context.Context, req *pb.FDBBackupStartRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "start") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + if req.Snapshot { + args = append(args, "--snapshot") + } + + if req.Tag != "" { + args = append(args, "--tag", req.Tag) + } + + return runFDBBackupCommand(ctx, args) +} + +func (s *fdbbackup) FDBBackupDescribe(ctx context.Context, req *pb.FDBBackupDescribeRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "describe") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + return runFDBBackupCommand(ctx, args) +} + +func (s *fdbbackup) FDBBackupExpire(ctx context.Context, req *pb.FDBBackupExpireRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "expire") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + if req.Version != "" { + args = append(args, "--version", req.Version) + } + + return runFDBBackupCommand(ctx, args) +} + +func (s *fdbbackup) FDBBackupPause(ctx context.Context, req *pb.FDBBackupPauseRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "pause") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + if req.Tag != "" { + args = append(args, "--tag", req.Tag) + } + + return runFDBBackupCommand(ctx, args) +} + +func (s *fdbbackup) FDBBackupResume(ctx context.Context, req *pb.FDBBackupResumeRequest) (*pb.FDBBackupResponse, error) { + var args []string + + args = append(args, "resume") + + if req.ClusterFile != "" { + args = append(args, "-C", req.ClusterFile) + } + + if req.BackupUrl != "" { + args = append(args, "--blob_url", req.BackupUrl) + } + + if req.Tag != "" { + args = append(args, "--tag", req.Tag) + } + + return runFDBBackupCommand(ctx, args) +} + +// Register is called to expose this handler to the gRPC server +func (s *fdbbackup) Register(gs *grpc.Server) { + pb.RegisterFDBBackupServer(gs, s) +} + +func init() { + services.RegisterSansShellService(&fdbbackup{}) +} diff --git a/services/fdb/server/server_test.go b/services/fdb/server/server_test.go index 38127e28..69a77c46 100644 --- a/services/fdb/server/server_test.go +++ b/services/fdb/server/server_test.go @@ -55,6 +55,9 @@ func TestMain(m *testing.M) { fdbm := &fdbmovedata{operations: make(map[int64]*moveOperation)} fdbm.Register(s) + fbdb := &fdbbackup{} + fbdb.Register(s) + go func() { if err := s.Serve(lis); err != nil { log.Fatalf("Server exited with error: %v", err)