Skip to content

Commit

Permalink
feat(backup): bakcup target HTTP apis
Browse files Browse the repository at this point in the history
Add backup target HTTP apis, "backupTargetCreate", "backupTargetGet"
"backupTargetUpdate" and "backupTargetDelete".

Ref: 5411

Signed-off-by: James Lu <james.lu@suse.com>
  • Loading branch information
mantissahz committed Oct 26, 2023
1 parent 3c40dde commit fe815cc
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 7 deletions.
106 changes: 104 additions & 2 deletions api/backup.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package api

import (
"fmt"
"net/http"
"strconv"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/gorilla/mux"
longhorn "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2"
"github.com/longhorn/longhorn-manager/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

Expand All @@ -14,11 +21,106 @@ import (
func (s *Server) BackupTargetList(w http.ResponseWriter, req *http.Request) error {
apiContext := api.GetApiContext(req)

backupTargets, err := s.m.ListBackupTargetsSorted()
bts, err := s.backupTargetList(apiContext)
if err != nil {
return err
}
apiContext.Write(bts)
return nil
}

func (s *Server) backupTargetList(apiContext *api.ApiContext) (*client.GenericCollection, error) {
bts, err := s.m.ListBackupTargetsSorted()
if err != nil {
return nil, errors.Wrap(err, "failed to list backup targets")
}
return toBackupTargetCollection(bts, apiContext), nil
}

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

backupTargetName := mux.Vars(req)["name"]
backupTarget, err := s.m.GetBackupTarget(backupTargetName)
if err != nil {
return errors.Wrap(err, "failed to list backup targets")
}
apiContext.Write(toBackupTargetCollection(backupTargets))
apiContext.Write(toBackupTargetResource(backupTarget, apiContext))
return nil
}

func (s *Server) BackupTargetCreate(rw http.ResponseWriter, req *http.Request) error {
var input BackupTarget
apiContext := api.GetApiContext(req)

if err := apiContext.Read(&input); err != nil {
return err
}

backupTargetSpec, err := newBackupTarget(input)
if err != nil {
return err
}

obj, err := s.m.CreateBackupTarget(input.Name, backupTargetSpec)
if err != nil {
return errors.Wrapf(err, "failed to create backup target %v", input.Name)
}
apiContext.Write(toBackupTargetResource(obj, apiContext))
return nil
}

func (s *Server) BackupTargetUpdate(rw http.ResponseWriter, req *http.Request) error {
var input BackupTarget

apiContext := api.GetApiContext(req)
if err := apiContext.Read(&input); err != nil {
return err
}

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

backupTargetSpec, err := newBackupTarget(input)
if err != nil {
return err
}

obj, err := util.RetryOnConflictCause(func() (interface{}, error) {
return s.m.UpdateBackupTarget(input.Name, backupTargetSpec)
})
if err != nil {
return err
}

backupTarget, ok := obj.(*longhorn.BackupTarget)
if !ok {
return fmt.Errorf("failed to convert %v to backup target", name)
}

apiContext.Write(toBackupTargetResource(backupTarget, apiContext))
return nil
}

func newBackupTarget(input BackupTarget) (*longhorn.BackupTargetSpec, error) {
pollInterval, err := strconv.ParseInt(input.PollInterval, 10, 64)
if err != nil {
return nil, err
}

return &longhorn.BackupTargetSpec{
BackupTargetURL: input.BackupTargetURL,
CredentialSecret: input.CredentialSecret,
Default: input.Default,
PollInterval: metav1.Duration{Duration: time.Duration(pollInterval) * time.Second},
ReadOnly: input.ReadyOnly}, nil
}

func (s *Server) BackupTargetDelete(rw http.ResponseWriter, req *http.Request) error {
backupTargetName := mux.Vars(req)["name"]
if err := s.m.DeleteBackupTarget(backupTargetName); err != nil {
return errors.Wrapf(err, "failed to delete backup target %v", backupTargetName)
}

return nil
}

Expand Down
28 changes: 24 additions & 4 deletions api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ type BackupVolume struct {
BackingImageName string `json:"backingImageName"`
BackingImageChecksum string `json:"backingImageChecksum"`
StorageClassName string `json:"storageClassName"`
BackupTargetName string `json:"backupTargetName"`
VolumeName string `json:"volumeName"`
}

type Backup struct {
Expand All @@ -159,6 +161,7 @@ type Backup struct {
VolumeCreated string `json:"volumeCreated"`
VolumeBackingImageName string `json:"volumeBackingImageName"`
CompressionMethod string `json:"compressionMethod"`
BackupTargetName string `json:"backupTargetName"`
}

type Setting struct {
Expand Down Expand Up @@ -550,7 +553,6 @@ func NewSchema() *client.Schemas {
schemas.AddType("detachInput", DetachInput{})
schemas.AddType("snapshotInput", SnapshotInput{})
schemas.AddType("snapshotCRInput", SnapshotCRInput{})
schemas.AddType("backupTarget", BackupTarget{})
schemas.AddType("backup", Backup{})
schemas.AddType("backupInput", BackupInput{})
schemas.AddType("backupStatus", BackupStatus{})
Expand Down Expand Up @@ -612,6 +614,7 @@ func NewSchema() *client.Schemas {
snapshotSchema(schemas.AddType("snapshot", Snapshot{}))
snapshotCRSchema(schemas.AddType("snapshotCR", SnapshotCR{}))
backupVolumeSchema(schemas.AddType("backupVolume", BackupVolume{}))
backupTargetSchema(schemas.AddType("backupTarget", BackupTarget{}))
settingSchema(schemas.AddType("setting", Setting{}))
recurringJobSchema(schemas.AddType("recurringJob", RecurringJob{}))
engineImageSchema(schemas.AddType("engineImage", EngineImage{}))
Expand Down Expand Up @@ -788,6 +791,17 @@ func backupVolumeSchema(backupVolume *client.Schema) {
}
}

func backupTargetSchema(backupTarget *client.Schema) {
backupTarget.CollectionMethods = []string{"GET", "POST"}
backupTarget.ResourceMethods = []string{"GET", "PUT", "DELETE"}

backupTargetName := backupTarget.ResourceFields["name"]
backupTargetName.Required = true
backupTargetName.Unique = true
backupTargetName.Create = true
backupTarget.ResourceFields["name"] = backupTargetName
}

func settingSchema(setting *client.Schema) {
setting.CollectionMethods = []string{"GET"}
setting.ResourceMethods = []string{"GET", "PUT"}
Expand Down Expand Up @@ -1641,7 +1655,7 @@ func toVolumeRecurringJobCollection(recurringJobs map[string]*longhorn.VolumeRec
return &client.GenericCollection{Data: data, Collection: client.Collection{ResourceType: "volumeRecurringJob"}}
}

func toBackupTargetResource(bt *longhorn.BackupTarget) *BackupTarget {
func toBackupTargetResource(bt *longhorn.BackupTarget, apiContext *api.ApiContext) *BackupTarget {
if bt == nil {
return nil
}
Expand All @@ -1653,11 +1667,14 @@ func toBackupTargetResource(bt *longhorn.BackupTarget) *BackupTarget {
Links: map[string]string{},
},
BackupTarget: engineapi.BackupTarget{
Name: bt.Name,
BackupTargetURL: bt.Spec.BackupTargetURL,
CredentialSecret: bt.Spec.CredentialSecret,
Default: bt.Spec.Default,
PollInterval: bt.Spec.PollInterval.Duration.String(),
Available: bt.Status.Available,
Message: types.GetCondition(bt.Status.Conditions, longhorn.BackupTargetConditionTypeUnavailable).Message,
ReadyOnly: bt.Spec.ReadOnly,
},
}
return res
Expand All @@ -1684,6 +1701,8 @@ func toBackupVolumeResource(bv *longhorn.BackupVolume, apiContext *api.ApiContex
BackingImageName: bv.Status.BackingImageName,
BackingImageChecksum: bv.Status.BackingImageChecksum,
StorageClassName: bv.Status.StorageClassName,
BackupTargetName: bv.Spec.BackupTargetName,
VolumeName: bv.Spec.VolumeName,
}
b.Actions = map[string]string{
"backupList": apiContext.UrlBuilder.ActionLink(b.Resource, "backupList"),
Expand All @@ -1693,10 +1712,10 @@ func toBackupVolumeResource(bv *longhorn.BackupVolume, apiContext *api.ApiContex
return b
}

func toBackupTargetCollection(bts []*longhorn.BackupTarget) *client.GenericCollection {
func toBackupTargetCollection(bts []*longhorn.BackupTarget, apiContext *api.ApiContext) *client.GenericCollection {
data := []interface{}{}
for _, bt := range bts {
data = append(data, toBackupTargetResource(bt))
data = append(data, toBackupTargetResource(bt, apiContext))
}
return &client.GenericCollection{Data: data, Collection: client.Collection{ResourceType: "backupTarget"}}
}
Expand Down Expand Up @@ -1743,6 +1762,7 @@ func toBackupResource(b *longhorn.Backup) *Backup {
VolumeCreated: b.Status.VolumeCreated,
VolumeBackingImageName: b.Status.VolumeBackingImageName,
CompressionMethod: string(b.Status.CompressionMethod),
BackupTargetName: b.Spec.BackupTargetName,
}
// Set the volume name from backup CR's label if it's empty.
// This field is empty probably because the backup state is not Ready
Expand Down
9 changes: 9 additions & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ func NewRouter(s *Server) *mux.Router {
}

r.Methods("GET").Path("/v1/backuptargets").Handler(f(schemas, s.BackupTargetList))
r.Methods("GET").Path("/v1/backuptargets/{name}").Handler(f(schemas, s.BackupTargetGet))
r.Methods("POST").Path("/v1/backuptargets").Handler(f(schemas, s.BackupTargetCreate))
r.Methods("DELETE").Path("/v1/backuptargets/{name}").Handler(f(schemas, s.BackupTargetDelete))
r.Methods("PUT").Path("/v1/backuptargets/{name}").Handler(f(schemas, s.BackupTargetUpdate))

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("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)))
Expand Down Expand Up @@ -225,6 +230,10 @@ func NewRouter(s *Server) *mux.Router {
r.Path("/v1/ws/backingimages").Handler(f(schemas, backingImageStream))
r.Path("/v1/ws/{period}/backingimages").Handler(f(schemas, backingImageStream))

backupTargetStream := NewStreamHandlerFunc("backuptargets", s.wsc.NewWatcher("backupTarget"), s.backupTargetList)
r.Path("/v1/ws/backuptargets").Handler(f(schemas, backupTargetStream))
r.Path("/v1/ws/{period}/backuptargets").Handler(f(schemas, backupTargetStream))

backupVolumeStream := NewStreamHandlerFunc("backupvolumes", s.wsc.NewWatcher("backupVolume"), s.backupVolumeList)
r.Path("/v1/ws/backupvolumes").Handler(f(schemas, backupVolumeStream))
r.Path("/v1/ws/{period}/backupvolumes").Handler(f(schemas, backupVolumeStream))
Expand Down
2 changes: 2 additions & 0 deletions client/generated_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const (
type Backup struct {
Resource `yaml:"-"`

BackupTargetName string `json:"backupTargetName,omitempty" yaml:"backup_target_name,omitempty"`

CompressionMethod string `json:"compressionMethod,omitempty" yaml:"compression_method,omitempty"`

Created string `json:"created,omitempty" yaml:"created,omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions client/generated_backup_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ type BackupTarget struct {

CredentialSecret string `json:"credentialSecret,omitempty" yaml:"credential_secret,omitempty"`

Default bool `json:"default,omitempty" yaml:"default,omitempty"`

Message string `json:"message,omitempty" yaml:"message,omitempty"`

Name string `json:"name,omitempty" yaml:"name,omitempty"`

PollInterval string `json:"pollInterval,omitempty" yaml:"poll_interval,omitempty"`

ReadOnly bool `json:"readOnly,omitempty" yaml:"read_only,omitempty"`
}

type BackupTargetCollection struct {
Expand Down
4 changes: 4 additions & 0 deletions client/generated_backup_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type BackupVolume struct {

BackingImageName string `json:"backingImageName,omitempty" yaml:"backing_image_name,omitempty"`

BackupTargetName string `json:"backupTargetName,omitempty" yaml:"backup_target_name,omitempty"`

Created string `json:"created,omitempty" yaml:"created,omitempty"`

DataStored string `json:"dataStored,omitempty" yaml:"data_stored,omitempty"`
Expand All @@ -28,6 +30,8 @@ type BackupVolume struct {
Size string `json:"size,omitempty" yaml:"size,omitempty"`

StorageClassName string `json:"storageClassName,omitempty" yaml:"storage_class_name,omitempty"`

VolumeName string `json:"volumeName,omitempty" yaml:"volume_name,omitempty"`
}

type BackupVolumeCollection struct {
Expand Down
6 changes: 6 additions & 0 deletions engineapi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,14 @@ type Volume struct {
}

type BackupTarget struct {
Name string `json:"name"`
BackupTargetURL string `json:"backupTargetURL"`
CredentialSecret string `json:"credentialSecret"`
Default bool `json:"default"`
PollInterval string `json:"pollInterval"`
Available bool `json:"available"`
Message string `json:"message"`
ReadyOnly bool `json:"readOnly"`
}

type BackupVolume struct {
Expand All @@ -152,6 +155,8 @@ type BackupVolume struct {
BackingImageName string `json:"backingImageName"`
BackingImageChecksum string `json:"backingImageChecksum"`
StorageClassName string `json:"storageClassName"`
BackupTargetName string `json:"backupTargetName"`
VolumeName string `json:"volumeName"`
}

type Backup struct {
Expand All @@ -169,6 +174,7 @@ type Backup struct {
VolumeBackingImageName string `json:"volumeBackingImageName"`
Messages map[string]string `json:"messages"`
CompressionMethod string `json:"compressionMethod"`
BackupTargetName string `json:"backupTargetName"`
}

type ConfigMetadata struct {
Expand Down
Loading

0 comments on commit fe815cc

Please sign in to comment.