Skip to content

Commit

Permalink
feat(local-snapshot-restore, velero) : support to restore local snaps…
Browse files Browse the repository at this point in the history
…hot to different namespace using velero (#1575)

Changes:
- Added support to restore local snapshot to different namespace using velero-plugin. Restored PV (and cstorVolume) will have `cstor-clone-` prefix.

Sample output:
```
kubectl get pv 
NAME                                               CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS           REASON   AGE
cstor-clone-37458540-3bc4-42ae-b0ba-57ba116bb72f   5G         RWO            Delete           Bound    ns2/demo-cstor-vol-claim-1    openebs-cstor-sparse            5m55s
cstor-clone-7db69671-1243-4d96-99f7-7041ad9f3c2b   5G         RWO            Delete           Bound    ns1/demo-cstor-vol-claim-1    openebs-cstor-sparse            10s
pvc-109d7f73-699c-11ea-9326-42010a9a0064           5G         RWO            Delete           Bound    test/demo-cstor-vol-claim-1   openebs-cstor-sparse            3h52m
```
In above output, first two PVs are created from `pvc-109d7f73-699c-11ea-9326-42010a9a0064` PV.

PVC output:
```
kubectl get pvc -A 
NAMESPACE   NAME                     STATUS   VOLUME                                             CAPACITY   ACCESS MODES   STORAGECLASS           AGE
ns1         demo-cstor-vol-claim-1   Bound    cstor-clone-7db69671-1243-4d96-99f7-7041ad9f3c2b   5G         RWO            openebs-cstor-sparse   18s
ns2         demo-cstor-vol-claim-1   Bound    cstor-clone-37458540-3bc4-42ae-b0ba-57ba116bb72f   5G         RWO            openebs-cstor-sparse   6m3s
test        demo-cstor-vol-claim-1   Bound    pvc-109d7f73-699c-11ea-9326-42010a9a0064           5G         RWO            openebs-cstor-sparse   3h52m
```

CStorVolume output:
```
kubectl get cstorvolume -n openebs
NAME                                               STATUS    AGE     CAPACITY
cstor-clone-37458540-3bc4-42ae-b0ba-57ba116bb72f   Healthy   13m     5G
cstor-clone-7db69671-1243-4d96-99f7-7041ad9f3c2b   Healthy   8m13s   5G
pvc-109d7f73-699c-11ea-9326-42010a9a0064           Healthy   4h      5G
```

_For documentation:_
- _For local snapshot(or remote backup) CV, related to PV, must be in healthy state._
- _If local snapshot( or remote backup) failed then check the backup logs for the error:_
 _`error taking snapshot of volume: rpc error: code = Unknown desc = Failed to send backup request: Error calling REST api: Status error{Bad Request}`_


Signed-off-by: mayank <[email protected]>
  • Loading branch information
mynktl authored Mar 26, 2020
1 parent 064f1d5 commit 7daa3c6
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 13 deletions.
8 changes: 6 additions & 2 deletions cmd/maya-apiserver/app/server/backup_endpoint_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func (bOps *backupAPIOps) create() (interface{}, error) {
return nil, CodedError(400, fmt.Sprintf("Failed to create backup '%v': missing volume name", bkp.Spec.BackupName))
}

// backupIP is expected
if len(strings.TrimSpace(bkp.Spec.BackupDest)) == 0 {
// backupIP is expected for remote snapshot
if !bkp.Spec.LocalSnap && len(strings.TrimSpace(bkp.Spec.BackupDest)) == 0 {
return nil, CodedError(400, fmt.Sprintf("Failed to create backup '%v': missing backupIP", bkp.Spec.BackupName))
}

Expand All @@ -107,6 +107,10 @@ func (bOps *backupAPIOps) create() (interface{}, error) {
return nil, CodedError(400, fmt.Sprintf("Failed to find healthy replica"))
}

if bkp.Spec.LocalSnap {
return "", nil
}

bkp.ObjectMeta.Labels = map[string]string{
"cstorpool.openebs.io/uid": cvr.ObjectMeta.Labels["cstorpool.openebs.io/uid"],
"openebs.io/persistent-volume": cvr.ObjectMeta.Labels["openebs.io/persistent-volume"],
Expand Down
68 changes: 64 additions & 4 deletions cmd/maya-apiserver/app/server/restore_endpoint_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (

"github.com/openebs/maya/pkg/apis/openebs.io/v1alpha1"
"github.com/openebs/maya/pkg/client/generated/clientset/versioned"
"github.com/openebs/maya/pkg/volume"
"github.com/pkg/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -62,7 +64,7 @@ func (rOps *restoreAPIOps) create() (interface{}, error) {
}

// namespace is expected
if len(strings.TrimSpace(restore.Namespace)) == 0 {
if !restore.Spec.Local && len(strings.TrimSpace(restore.Namespace)) == 0 {
return nil, CodedError(400, fmt.Sprintf("failed to create restore '%v': missing namespace", restore.Name))
}

Expand All @@ -81,16 +83,36 @@ func (rOps *restoreAPIOps) create() (interface{}, error) {
return nil, CodedError(400, fmt.Sprintf("failed to create restore '%v': missing restoreSrc", restore.Name))
}

// storageClass is expected if restore is for local snapshot
if restore.Spec.Local && len(strings.TrimSpace(restore.Spec.StorageClass)) == 0 {
return nil, CodedError(400, fmt.Sprintf("failed to create restore '%v': missing storageClass", restore.Name))
}

// size is expected if restore is for local snapshot
if restore.Spec.Local && len(strings.TrimSpace(restore.Spec.Size.String())) == 0 {
return nil, CodedError(400, fmt.Sprintf("failed to create restore '%v': missing size", restore.Name))
}

openebsClient, _, err = loadClientFromServiceAccount()
if err != nil {
return nil, CodedError(400, fmt.Sprintf("Failed to load openebs client:{%v}", err))
}

return createRestoreResource(openebsClient, restore)
cvol, err := createVolumeForRestore(restore)
if err != nil {
return nil, CodedError(400, fmt.Sprintf("Failed to create resources for volume: {%v}", err))
}
klog.Infof("Restore volume '%v' created successfully ", cvol.Name)

if restore.Spec.Local {
return cvol, nil
}

return createRestoreResource(openebsClient, restore, cvol)
}

// createRestoreResource create restore CR for volume's CVR
func createRestoreResource(openebsClient *versioned.Clientset, rst *v1alpha1.CStorRestore) (interface{}, error) {
func createRestoreResource(openebsClient *versioned.Clientset, rst *v1alpha1.CStorRestore, cvol *v1alpha1.CASVolume) (interface{}, error) {
//Get List of cvr's related to this pvc
listOptions := v1.ListOptions{
LabelSelector: "openebs.io/persistent-volume=" + rst.Spec.VolumeName,
Expand Down Expand Up @@ -137,7 +159,7 @@ func createRestoreResource(openebsClient *versioned.Clientset, rst *v1alpha1.CSt
}
}

return "", nil
return cvol, nil
}

// get is http handler which handles backup get request
Expand Down Expand Up @@ -255,3 +277,41 @@ func updateRestoreStatus(clientset versioned.Interface, rst v1alpha1.CStorRestor
return
}
}

func createVolumeForRestore(r *v1alpha1.CStorRestore) (*v1alpha1.CASVolume, error) {
vol := &v1alpha1.CASVolume{}

vol.Name = r.Spec.VolumeName
vol.Labels = map[string]string{
string(v1alpha1.StorageClassKey): r.Spec.StorageClass,
}
vol.Spec.Capacity = r.Spec.Size.String()

if r.Spec.Local {
vol.CloneSpec.IsClone = true
vol.CloneSpec.SourceVolume = r.Spec.RestoreSrc
vol.CloneSpec.SnapshotName = r.Spec.RestoreName
} else {
vol.Annotations = map[string]string{
v1alpha1.PVCreatedByKey: "restore",
}
}

vOps, err := volume.NewOperation(vol)
if err != nil {
return nil, errors.Wrapf(err, "Failed to create volume operation")
}

// If Restore is from remote backup then volume creation is handled by velero-plugin
// So let's check if volume exist or not
cvol, err := vOps.Read()
if err != nil {
if !isNotFound(err) {
return nil, errors.Wrapf(err, "Failed to get restore volume details")
}
} else {
return cvol, nil
}

return vOps.Create()
}
1 change: 0 additions & 1 deletion cmd/maya-apiserver/app/server/volume_endpoint_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ func (v *volumeAPIOpsV1alpha1) delete(volumeName string) (*v1alpha1.CASVolume, e
if len(vol.Name) == 0 {
return nil, CodedErrorf(400, "failed to delete volume: missing volume name: %s", vol)
}

// use namespace from req headers if volume ns is still not set
if len(vol.Namespace) == 0 {
vol.Namespace = hdrNS
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/openebs.io/v1alpha1/cstor_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type CStorBackupSpec struct {

// BackupDest is the remote address for backup transfer
BackupDest string `json:"backupDest"`

// LocalSnap is flag to enable local snapshot only
LocalSnap bool `json:"localSnap"`
}

// CStorBackupStatus is to hold status of backup
Expand Down
14 changes: 9 additions & 5 deletions pkg/apis/openebs.io/v1alpha1/cstor_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1

import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -35,11 +36,14 @@ type CStorRestore struct {

// CStorRestoreSpec is the spec for a CStorRestore resource
type CStorRestoreSpec struct {
RestoreName string `json:"restoreName"` // set restore name
VolumeName string `json:"volumeName"`
RestoreSrc string `json:"restoreSrc"`
MaxRetryCount int `json:"maxretrycount"`
RetryCount int `json:"retrycount"`
RestoreName string `json:"restoreName"` // set restore name
VolumeName string `json:"volumeName"`
RestoreSrc string `json:"restoreSrc"` // it can be ip:port in case of restore from remote or volumeName in case of local restore
MaxRetryCount int `json:"maxretrycount"`
RetryCount int `json:"retrycount"`
StorageClass string `json:"storageClass,omitempty"`
Size resource.Quantity `json:"size,omitempty"`
Local bool `json:"localRestore,omitempty"` // if restore is from local backup/snapshot
}

// CStorRestoreStatus is to hold result of action.
Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/openebs.io/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7daa3c6

Please sign in to comment.