/
pvc_action.go
144 lines (121 loc) · 5.58 KB
/
pvc_action.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
Copyright 2019, 2020 the Velero contributors.
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 restore
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
corev1api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/vmware-tanzu/velero-plugin-for-csi/internal/util"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// PVCRestoreItemAction is a restore item action plugin for Velero
type PVCRestoreItemAction struct {
Log logrus.FieldLogger
}
// AppliesTo returns information indicating that the PVCRestoreItemAction should be run while restoring PVCs.
func (p *PVCRestoreItemAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"persistentvolumeclaims"},
//TODO: add label selector volumeSnapshotLabel
}, nil
}
func resetPVCAnnotations(pvc *corev1api.PersistentVolumeClaim, preserve []string) {
if pvc.Annotations == nil {
pvc.Annotations = make(map[string]string)
return
}
for k := range pvc.Annotations {
if !util.Contains(preserve, k) {
delete(pvc.Annotations, k)
}
}
}
func resetPVCSpec(pvc *corev1api.PersistentVolumeClaim, vsName string) {
// Restore operation for the PVC will use the volumesnapshot as the data source.
// So clear out the volume name, which is a ref to the PV
pvc.Spec.VolumeName = ""
pvc.Spec.DataSource = &corev1api.TypedLocalObjectReference{
APIGroup: &snapshotv1beta1api.SchemeGroupVersion.Group,
Kind: "VolumeSnapshot",
Name: vsName,
}
}
func setPVCStorageResourceRequest(pvc *corev1api.PersistentVolumeClaim, restoreSize resource.Quantity, log logrus.FieldLogger) {
{
if pvc.Spec.Resources.Requests == nil {
pvc.Spec.Resources.Requests = corev1api.ResourceList{}
}
storageReq, exists := pvc.Spec.Resources.Requests[corev1api.ResourceStorage]
if !exists || storageReq.Cmp(restoreSize) < 0 {
pvc.Spec.Resources.Requests[corev1api.ResourceStorage] = restoreSize
rs := pvc.Spec.Resources.Requests[corev1api.ResourceStorage]
log.Infof("Resetting storage requests for PVC %s/%s to %s", pvc.Namespace, pvc.Name, rs.String())
}
}
}
// Execute modifies the PVC's spec to use the volumesnapshot object as the data source ensuring that the newly provisioned volume
// can be pre-populated with data from the volumesnapshot.
func (p *PVCRestoreItemAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
var pvc corev1api.PersistentVolumeClaim
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), &pvc); err != nil {
return nil, errors.WithStack(err)
}
p.Log.Infof("Starting PVCRestoreItemAction for PVC %s/%s", pvc.Namespace, pvc.Name)
resetPVCAnnotations(&pvc, []string{velerov1api.BackupNameLabel, util.VolumeSnapshotLabel})
volumeSnapshotName, ok := pvc.Annotations[util.VolumeSnapshotLabel]
if !ok {
p.Log.Infof("Skipping PVCRestoreItemAction for PVC %s/%s, PVC does not have a CSI volumesnapshot.", pvc.Namespace, pvc.Name)
return &velero.RestoreItemActionExecuteOutput{
UpdatedItem: input.Item,
}, nil
}
_, snapClient, err := util.GetClients()
if err != nil {
return nil, errors.WithStack(err)
}
vs, err := snapClient.SnapshotV1beta1().VolumeSnapshots(pvc.Namespace).Get(context.TODO(), volumeSnapshotName, metav1.GetOptions{})
if err != nil {
return nil, errors.Wrapf(err, fmt.Sprintf("Failed to get Volumesnapshot %s/%s to restore PVC %s/%s", pvc.Namespace, volumeSnapshotName, pvc.Namespace, pvc.Name))
}
if _, exists := vs.Annotations[util.VolumeSnapshotRestoreSize]; exists {
restoreSize, err := resource.ParseQuantity(vs.Annotations[util.VolumeSnapshotRestoreSize])
if err != nil {
return nil, errors.Wrapf(err, fmt.Sprintf("Failed to parse %s from annotation on Volumesnapshot %s/%s into restore size",
vs.Annotations[util.VolumeSnapshotRestoreSize], vs.Namespace, vs.Name))
}
// It is possible that the volume provider allocated a larger capacity volume than what was requested in the backed up PVC.
// In this scenario the volumesnapshot of the PVC will endup being larger than its requested storage size.
// Such a PVC, on restore as-is, will be stuck attempting to use a Volumesnapshot as a data source for a PVC that
// is not large enough.
// To counter that, here we set the storage request on the PVC to the larger of the PVC's storage request and the size of the
// VolumeSnapshot
setPVCStorageResourceRequest(&pvc, restoreSize, p.Log)
}
resetPVCSpec(&pvc, volumeSnapshotName)
pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&pvc)
if err != nil {
return nil, errors.WithStack(err)
}
p.Log.Infof("Returning from PVCRestoreItemAction for PVC %s/%s", pvc.Namespace, pvc.Name)
return &velero.RestoreItemActionExecuteOutput{
UpdatedItem: &unstructured.Unstructured{Object: pvcMap},
}, nil
}