Skip to content

Commit

Permalink
fix(backup): modify backup volume and volume APIs
Browse files Browse the repository at this point in the history
Add an API to update the field `BackupTargetname` of a volume.
Modify backup volume list API to get information by
backup volume name.

ref: longhorn/longhorn 5411

Signed-off-by: James Lu <[email protected]>
  • Loading branch information
mantissahz committed Dec 18, 2024
1 parent 675d0b7 commit be9c91b
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 35 deletions.
52 changes: 26 additions & 26 deletions api/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,11 @@ func (s *Server) backupVolumeList(apiContext *api.ApiContext) (*client.GenericCo
func (s *Server) BackupVolumeGet(w http.ResponseWriter, req *http.Request) error {
apiContext := api.GetApiContext(req)

volName := mux.Vars(req)["volName"]
backupVolumeName := mux.Vars(req)["name"]

bv, err := s.m.GetBackupVolume(volName)
bv, err := s.m.GetBackupVolume(backupVolumeName)
if err != nil {
return errors.Wrapf(err, "failed to get backup volume '%s'", volName)
return errors.Wrapf(err, "failed to get backup volume '%s'", backupVolumeName)
}
apiContext.Write(toBackupVolumeResource(bv, apiContext))
return nil
Expand Down Expand Up @@ -243,20 +243,20 @@ func (s *Server) SyncBackupVolume(w http.ResponseWriter, req *http.Request) erro
return err
}

volName := mux.Vars(req)["volName"]
if volName == "" {
backupVolumeName := mux.Vars(req)["name"]
if backupVolumeName == "" {
return fmt.Errorf("backup volume name is required")
}

bv, err := s.m.GetBackupVolume(volName)
bv, err := s.m.GetBackupVolume(backupVolumeName)
if err != nil {
return errors.Wrapf(err, "failed to get backup volume '%s'", volName)
return errors.Wrapf(err, "failed to get backup volume '%s'", backupVolumeName)
}

if input.SyncBackupVolume {
bv, err = s.m.SyncBackupVolume(bv)
if err != nil {
return errors.Wrapf(err, "failed to synchronize backup volume %v", volName)
return errors.Wrapf(err, "failed to synchronize backup volume %v", backupVolumeName)
}
}

Expand All @@ -265,9 +265,9 @@ func (s *Server) SyncBackupVolume(w http.ResponseWriter, req *http.Request) erro
}

func (s *Server) BackupVolumeDelete(w http.ResponseWriter, req *http.Request) error {
volName := mux.Vars(req)["volName"]
if err := s.m.DeleteBackupVolume(volName); err != nil {
return errors.Wrapf(err, "failed to delete backup volume '%s'", volName)
backupVolumeName := mux.Vars(req)["name"]
if err := s.m.DeleteBackupVolume(backupVolumeName); err != nil {
return errors.Wrapf(err, "failed to delete backup volume '%s'", backupVolumeName)
}
return nil
}
Expand All @@ -283,14 +283,14 @@ func (s *Server) BackupList(w http.ResponseWriter, req *http.Request) error {
return nil
}

func (s *Server) BackupListByVolumeName(w http.ResponseWriter, req *http.Request) error {
func (s *Server) BackupListByBackupVolume(w http.ResponseWriter, req *http.Request) error {
apiContext := api.GetApiContext(req)

volName := mux.Vars(req)["volName"]
backupVolumeName := mux.Vars(req)["name"]

bs, err := s.m.ListBackupsForVolumeSorted(volName)
bs, err := s.m.ListBackupsForBackupVolumeSorted(backupVolumeName)
if err != nil {
return errors.Wrapf(err, "failed to list backups for volume '%s'", volName)
return errors.Wrapf(err, "failed to list backups for volume '%s'", backupVolumeName)
}
apiContext.Write(toBackupCollection(bs))
return nil
Expand All @@ -315,14 +315,14 @@ func (s *Server) BackupGet(w http.ResponseWriter, req *http.Request) error {
if input.Name == "" {
return errors.New("empty backup name is not allowed")
}
volName := mux.Vars(req)["volName"]
backupVolumeName := mux.Vars(req)["name"]

backup, err := s.m.GetBackup(input.Name, volName)
backup, err := s.m.GetBackup(input.Name, backupVolumeName)
if err != nil {
return errors.Wrapf(err, "failed to get backup '%v' of volume '%v'", input.Name, volName)
return errors.Wrapf(err, "failed to get backup '%v' of volume '%v'", input.Name, backupVolumeName)
}
if backup == nil {
logrus.Warnf("cannot find backup '%v' of volume '%v'", input.Name, volName)
logrus.Warnf("cannot find backup '%v' of volume '%v'", input.Name, backupVolumeName)
w.WriteHeader(http.StatusNotFound)
return nil
}
Expand All @@ -342,22 +342,22 @@ func (s *Server) BackupDelete(w http.ResponseWriter, req *http.Request) error {
return errors.New("empty backup name is not allowed")
}

volName := mux.Vars(req)["volName"]
backupVolumeName := mux.Vars(req)["name"]

backup, err := s.m.GetBackup(input.Name, volName)
backup, err := s.m.GetBackup(input.Name, backupVolumeName)
if err != nil {
logrus.WithError(err).Warnf("failed to get backup '%v' of volume '%v'", input.Name, volName)
logrus.WithError(err).Warnf("failed to get backup '%v' of volume '%v'", input.Name, backupVolumeName)
}

if backup != nil {
if err := s.m.DeleteBackup(input.Name, volName); err != nil {
return errors.Wrapf(err, "failed to delete backup '%v' of volume '%v'", input.Name, volName)
if err := s.m.DeleteBackup(input.Name, backupVolumeName); err != nil {
return errors.Wrapf(err, "failed to delete backup '%v' of volume '%v'", input.Name, backupVolumeName)
}
}

bv, err := s.m.GetBackupVolume(volName)
bv, err := s.m.GetBackupVolume(backupVolumeName)
if err != nil {
return errors.Wrapf(err, "failed to get backup volume '%s'", volName)
return errors.Wrapf(err, "failed to get backup volume '%s'", backupVolumeName)
}
apiContext.Write(toBackupVolumeResource(bv, apiContext))
return nil
Expand Down
11 changes: 11 additions & 0 deletions api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ type UpdateFreezeFilesystemForSnapshotInput struct {
FreezeFilesystemForSnapshot string `json:"freezeFilesystemForSnapshot"`
}

type UpdateBackupTargetInput struct {
BackupTargetName string `json:"backupTargetName"`
}

type PVCreateInput struct {
PVName string `json:"pvName"`
FSType string `json:"fsType"`
Expand Down Expand Up @@ -657,6 +661,7 @@ func NewSchema() *client.Schemas {
schemas.AddType("UpdateReplicaZoneSoftAntiAffinityInput", UpdateReplicaZoneSoftAntiAffinityInput{})
schemas.AddType("UpdateReplicaDiskSoftAntiAffinityInput", UpdateReplicaDiskSoftAntiAffinityInput{})
schemas.AddType("UpdateFreezeFilesystemForSnapshotInput", UpdateFreezeFilesystemForSnapshotInput{})
schemas.AddType("UpdateBackupTargetInput", UpdateBackupTargetInput{})
schemas.AddType("workloadStatus", longhorn.WorkloadStatus{})
schemas.AddType("cloneStatus", longhorn.VolumeCloneStatus{})
schemas.AddType("empty", Empty{})
Expand Down Expand Up @@ -1097,6 +1102,10 @@ func volumeSchema(volume *client.Schema) {
Input: "UpdateFreezeFilesystemForSnapshotInput",
},

"updateBackupTarget": {
Input: "UpdateBackupTargetInput",
},

"pvCreate": {
Input: "PVCreateInput",
Output: "volume",
Expand Down Expand Up @@ -1670,6 +1679,7 @@ func toVolumeResource(v *longhorn.Volume, ves []*longhorn.Engine, vrs []*longhor
actions["updateReplicaZoneSoftAntiAffinity"] = struct{}{}
actions["updateReplicaDiskSoftAntiAffinity"] = struct{}{}
actions["updateFreezeFilesystemForSnapshot"] = struct{}{}
actions["updateBackupTarget"] = struct{}{}
actions["recurringJobAdd"] = struct{}{}
actions["recurringJobDelete"] = struct{}{}
actions["recurringJobList"] = struct{}{}
Expand Down Expand Up @@ -1701,6 +1711,7 @@ func toVolumeResource(v *longhorn.Volume, ves []*longhorn.Engine, vrs []*longhor
actions["updateReplicaZoneSoftAntiAffinity"] = struct{}{}
actions["updateReplicaDiskSoftAntiAffinity"] = struct{}{}
actions["updateFreezeFilesystemForSnapshot"] = struct{}{}
actions["updateBackupTarget"] = struct{}{}
actions["pvCreate"] = struct{}{}
actions["pvcCreate"] = struct{}{}
actions["cancelExpansion"] = struct{}{}
Expand Down
9 changes: 5 additions & 4 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func NewRouter(s *Server) *mux.Router {
"updateSnapshotDataIntegrity": s.VolumeUpdateSnapshotDataIntegrity,
"updateBackupCompressionMethod": s.VolumeUpdateBackupCompressionMethod,
"updateFreezeFilesystemForSnapshot": s.VolumeUpdateFreezeFilesystemForSnapshot,
"updateBackupTargetName": s.VolumeUpdateBackupTargetName,
"replicaRemove": s.ReplicaRemove,

"engineUpgrade": s.EngineUpgrade,
Expand Down Expand Up @@ -133,16 +134,16 @@ func NewRouter(s *Server) *mux.Router {
r.Methods("DELETE").Path("/v1/backuptargets/{name}").Handler(f(schemas, s.BackupTargetDelete))
r.Methods("GET").Path("/v1/backupvolumes").Handler(f(schemas, s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupVolumeList)))
r.Methods("PUT").Path("/v1/backupvolumes").Handler(f(schemas, s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupVolumeSyncAll)))
r.Methods("GET").Path("/v1/backupvolumes/{volName}").Handler(f(schemas, s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupVolumeGet)))
r.Methods("DELETE").Path("/v1/backupvolumes/{volName}").Handler(f(schemas, s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupVolumeDelete)))
r.Methods("GET").Path("/v1/backupvolumes/{name}").Handler(f(schemas, s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupVolumeGet)))
r.Methods("DELETE").Path("/v1/backupvolumes/{name}").Handler(f(schemas, s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupVolumeDelete)))
backupActions := map[string]func(http.ResponseWriter, *http.Request) error{
"backupList": s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupList),
"backupList": s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupListByBackupVolume),
"backupGet": s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupGet),
"backupDelete": s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.BackupDelete),
"backupVolumeSync": s.fwd.Handler(s.fwd.HandleProxyRequestByNodeID, s.fwd.GetHTTPAddressByNodeID(NodeHasDefaultEngineImage(s.m)), s.SyncBackupVolume),
}
for name, action := range backupActions {
r.Methods("POST").Path("/v1/backupvolumes/{volName}").Queries("action", name).Handler(f(schemas, action))
r.Methods("POST").Path("/v1/backupvolumes/{name}").Queries("action", name).Handler(f(schemas, action))
}

r.Methods("GET").Path("/v1/nodes").Handler(f(schemas, s.NodeList))
Expand Down
22 changes: 22 additions & 0 deletions api/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,3 +835,25 @@ func (s *Server) VolumeUpdateFreezeFilesystemForSnapshot(rw http.ResponseWriter,
}
return s.responseWithVolume(rw, req, "", v)
}

func (s *Server) VolumeUpdateBackupTargetName(rw http.ResponseWriter, req *http.Request) error {
var input UpdateBackupTargetInput
id := mux.Vars(req)["name"]

apiContext := api.GetApiContext(req)
if err := apiContext.Read(&input); err != nil {
return errors.Wrap(err, "failed to read BackupTarget input")
}

obj, err := util.RetryOnConflictCause(func() (interface{}, error) {
return s.m.UpdateVolumeBackupTarget(id, input.BackupTargetName)
})
if err != nil {
return err
}
v, ok := obj.(*longhorn.Volume)
if !ok {
return fmt.Errorf("failed to convert to volume %v object", id)
}
return s.responseWithVolume(rw, req, "", v)
}
19 changes: 14 additions & 5 deletions manager/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,14 +434,14 @@ func (m *VolumeManager) ListBackupVolumesSorted() ([]*longhorn.BackupVolume, err
return backupVolumes, nil
}

func (m *VolumeManager) GetBackupVolume(volumeName string) (*longhorn.BackupVolume, error) {
backupVolume, err := m.ds.GetBackupVolumeRO(volumeName)
func (m *VolumeManager) GetBackupVolume(backupVolumeName string) (*longhorn.BackupVolume, error) {
backupVolume, err := m.ds.GetBackupVolumeRO(backupVolumeName)
if err != nil {
if apierrors.IsNotFound(err) {
// If the BackupVolume CR is not found, return succeeded result
// This is to compatible with the Longhorn CSI plugin
// https://github.com/longhorn/longhorn-manager/blob/v1.1.2/csi/controller_server.go#L446-L455
return &longhorn.BackupVolume{ObjectMeta: metav1.ObjectMeta{Name: volumeName}}, nil
return &longhorn.BackupVolume{ObjectMeta: metav1.ObjectMeta{Name: backupVolumeName}}, nil
}
return nil, err
}
Expand All @@ -458,8 +458,8 @@ func (m *VolumeManager) SyncBackupVolume(backupVolume *longhorn.BackupVolume) (*
return m.ds.UpdateBackupVolume(backupVolume)
}

func (m *VolumeManager) DeleteBackupVolume(volumeName string) error {
return m.ds.DeleteBackupVolume(volumeName)
func (m *VolumeManager) DeleteBackupVolume(backupVolumeName string) error {
return m.ds.DeleteBackupVolume(backupVolumeName)
}

func (m *VolumeManager) ListAllBackupsSorted() ([]*longhorn.Backup, error) {
Expand Down Expand Up @@ -502,6 +502,15 @@ func (m *VolumeManager) ListBackupsForVolumeSorted(volumeName string) ([]*longho
return backups, nil
}

func (m *VolumeManager) ListBackupsForBackupVolumeSorted(backupVolumeName string) ([]*longhorn.Backup, error) {
bv, err := m.ds.GetBackupVolumeRO(backupVolumeName)
if err != nil {
return []*longhorn.Backup{}, err
}

return m.ListBackupsForVolumeSorted(bv.Spec.VolumeName)
}

func (m *VolumeManager) GetBackup(backupName, volumeName string) (*longhorn.Backup, error) {
return m.ds.GetBackupRO(backupName)
}
Expand Down
26 changes: 26 additions & 0 deletions manager/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -1273,3 +1273,29 @@ func (m *VolumeManager) UpdateFreezeFilesystemForSnapshot(name string,
oldFreezeFilesystemForSnapshot, freezeFilesystemForSnapshot)
return v, nil
}

func (m *VolumeManager) UpdateVolumeBackupTarget(name string, backupTargetName string) (v *longhorn.Volume, err error) {
defer func() {
err = errors.Wrapf(err, "unable to update field BackupTargetName for volume %v", name)
}()

v, err = m.ds.GetVolume(name)
if err != nil {
return nil, err
}

if v.Spec.BackupTargetName == backupTargetName {
logrus.Debugf("Volume %v already set field BackupTargetName to %v", v.Name, backupTargetName)
return v, nil
}

oldBackupTargetName := v.Spec.BackupTargetName
v.Spec.BackupTargetName = backupTargetName
v, err = m.ds.UpdateVolume(v)
if err != nil {
return nil, err
}

logrus.Infof("Updated volume %v field BackupTargetName from %v to %v", v.Name, oldBackupTargetName, backupTargetName)
return v, nil
}

0 comments on commit be9c91b

Please sign in to comment.