backup(longhorn): create snapshot before snapshot backup

This commit is contained in:
Brad Stein 2026-04-13 00:35:36 -03:00
parent 42fa848a82
commit e8add01511
3 changed files with 59 additions and 1 deletions

View File

@ -85,11 +85,25 @@ type snapshotInput struct {
BackupMode string `json:"backupMode,omitempty"`
}
type snapshotCreateInput struct {
Name string `json:"name,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
type pvcCreateInput struct {
Namespace string `json:"namespace"`
PVCName string `json:"pvcName"`
}
func (c *Client) CreateSnapshot(ctx context.Context, volume, name string, labels map[string]string) error {
path := fmt.Sprintf("%s/v1/volumes/%s?action=snapshotCreate", c.baseURL, url.PathEscape(volume))
input := snapshotCreateInput{
Name: name,
Labels: labels,
}
return c.doJSON(ctx, http.MethodPost, path, input, nil)
}
func (c *Client) SnapshotBackup(ctx context.Context, volume, name string, labels map[string]string, backupMode string) (*Volume, error) {
path := fmt.Sprintf("%s/v1/volumes/%s?action=snapshotBackup", c.baseURL, url.PathEscape(volume))
input := snapshotInput{

View File

@ -35,6 +35,7 @@ type kubeClient interface {
}
type longhornClient interface {
CreateSnapshot(ctx context.Context, volume, name string, labels map[string]string) error
SnapshotBackup(ctx context.Context, volume, name string, labels map[string]string, backupMode string) (*longhorn.Volume, error)
GetVolume(ctx context.Context, volume string) (*longhorn.Volume, error)
CreateVolumeFromBackup(ctx context.Context, name, size string, replicas int, backupURL string) (*longhorn.Volume, error)
@ -620,6 +621,9 @@ func (s *Server) executeBackup(ctx context.Context, req api.BackupRequest, reque
"soteria.bstein.dev/pvc": req.PVC,
"soteria.bstein.dev/requested-by": requester,
}
if err := s.longhorn.CreateSnapshot(ctx, volumeName, backupID, labels); err != nil {
return api.BackupResponse{}, "backend_error", err
}
if _, err := s.longhorn.SnapshotBackup(ctx, volumeName, backupID, labels, s.cfg.LonghornBackupMode); err != nil {
return api.BackupResponse{}, "backend_error", err
}

View File

@ -68,10 +68,18 @@ func (f *fakeKubeClient) SaveSecretData(_ context.Context, _, _, key string, val
}
type fakeLonghornClient struct {
backups []longhorn.Backup
backups []longhorn.Backup
createSnapshotName string
snapshotBackupName string
}
func (f *fakeLonghornClient) CreateSnapshot(_ context.Context, volume, name string, labels map[string]string) error {
f.createSnapshotName = name
return nil
}
func (f *fakeLonghornClient) SnapshotBackup(_ context.Context, volume, name string, labels map[string]string, backupMode string) (*longhorn.Volume, error) {
f.snapshotBackupName = name
return &longhorn.Volume{Name: volume}, nil
}
@ -222,6 +230,38 @@ func TestRestoreRejectsSameSourceAndTarget(t *testing.T) {
}
}
func TestBackupCreatesSnapshotBeforeBackup(t *testing.T) {
lh := &fakeLonghornClient{}
srv := &Server{
cfg: &config.Config{AuthRequired: false, BackupDriver: "longhorn", LonghornBackupMode: "incremental"},
client: &fakeKubeClient{},
longhorn: lh,
metrics: newTelemetry(),
}
srv.handler = http.HandlerFunc(srv.route)
body := `{"namespace":"apps","pvc":"data","dry_run":false}`
req := httptest.NewRequest(http.MethodPost, "/v1/backup", strings.NewReader(body))
res := httptest.NewRecorder()
srv.Handler().ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", res.Code, res.Body.String())
}
if lh.createSnapshotName == "" {
t.Fatalf("expected snapshot creation call")
}
if lh.snapshotBackupName == "" {
t.Fatalf("expected snapshot backup call")
}
if lh.createSnapshotName != lh.snapshotBackupName {
t.Fatalf("expected same snapshot and backup name, got snapshot=%q backup=%q", lh.createSnapshotName, lh.snapshotBackupName)
}
if !strings.HasPrefix(lh.createSnapshotName, "soteria-backup-apps-data-") {
t.Fatalf("unexpected generated backup name %q", lh.createSnapshotName)
}
}
func TestMetricsStayPublic(t *testing.T) {
srv := &Server{
cfg: &config.Config{AuthRequired: true, AllowedGroups: []string{"admin"}},