Merge pull request #6410 from blackpiglet/6117_fix

Retrieve DataUpload into result ConfigMap during volume snapshot restore.
This commit is contained in:
Xun Jiang/Bruce Jiang
2023-06-30 14:30:30 +08:00
committed by GitHub
12 changed files with 328 additions and 9 deletions
+1
View File
@@ -0,0 +1 @@
Retrieve DataUpload into backup result ConfigMap during volume snapshot restore.
+23
View File
@@ -78,4 +78,27 @@ const (
// AsyncOperationIDLabel is the label key used to identify the async operation ID
AsyncOperationIDLabel = "velero.io/async-operation-id"
// PVCNameLabel is the label key used to identify the the PVC's namespace and name.
// The format is <namespace>/<name>.
PVCNamespaceNameLabel = "velero.io/pvc-namespace-name"
// DynamicPVRestoreLabel is the label key for dynamic PV restore
DynamicPVRestoreLabel = "velero.io/dynamic-pv-restore"
// ResourceUsageLabel is the label key to explain the Velero resource usage.
ResourceUsageLabel = "velero.io/resource-usage"
)
type AsyncOperationIDPrefix string
const (
AsyncOperationIDPrefixDataDownload AsyncOperationIDPrefix = "dd-"
AsyncOperationIDPrefixDataUpload AsyncOperationIDPrefix = "du-"
)
type VeleroResourceUsage string
const (
VeleroResourceUsageDataUploadResult VeleroResourceUsage = "DataUpload"
)
+15 -6
View File
@@ -5,7 +5,7 @@ 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
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,
@@ -22,22 +22,22 @@ import (
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
)
// DataDownloadBuilder builds DataDownload objects
// DataDownloadBuilder builds DataDownload objects.
type DataDownloadBuilder struct {
object *velerov2alpha1api.DataDownload
}
// ForDataDownload is the constructor for a DataDownloadBuilder.
func ForDataDownload(ns, name string) *DataDownloadBuilder {
// ForDataDownload is the constructor of DataDownloadBuilder
func ForDataDownload(namespace, name string) *DataDownloadBuilder {
return &DataDownloadBuilder{
object: &velerov2alpha1api.DataDownload{
TypeMeta: metav1.TypeMeta{
Kind: "DataDownload",
APIVersion: velerov2alpha1api.SchemeGroupVersion.String(),
Kind: "DataDownloadload",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
Namespace: namespace,
},
},
}
@@ -101,3 +101,12 @@ func (d *DataDownloadBuilder) DataMoverConfig(config *map[string]string) *DataDo
d.object.Spec.DataMoverConfig = *config
return d
}
// ObjectMeta applies functional options to the DataDownload's ObjectMeta.
func (d *DataDownloadBuilder) ObjectMeta(opts ...ObjectMetaOpt) *DataDownloadBuilder {
for _, opt := range opts {
opt(d.object)
}
return d
}
+7
View File
@@ -160,3 +160,10 @@ func WithCreationTimestamp(t time.Time) func(obj metav1.Object) {
obj.SetCreationTimestamp(metav1.Time{Time: t})
}
}
// WithOwnerReference is a functional option that applies the specified OwnerReference to an object.
func WithOwnerReference(val []metav1.OwnerReference) func(obj metav1.Object) {
return func(obj metav1.Object) {
obj.SetOwnerReferences(val)
}
}
@@ -18,6 +18,7 @@ package builder
import (
corev1api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -73,3 +74,39 @@ func (b *PersistentVolumeClaimBuilder) Phase(phase corev1api.PersistentVolumeCla
b.object.Status.Phase = phase
return b
}
// RequestResource sets the PersistentVolumeClaim's spec.Resources.Requests.
func (b *PersistentVolumeClaimBuilder) RequestResource(requests corev1api.ResourceList) *PersistentVolumeClaimBuilder {
if b.object.Spec.Resources.Requests == nil {
b.object.Spec.Resources.Requests = make(map[corev1api.ResourceName]resource.Quantity)
}
b.object.Spec.Resources.Requests = requests
return b
}
// LimitResource sets the PersistentVolumeClaim's spec.Resources.Limits.
func (b *PersistentVolumeClaimBuilder) LimitResource(limits corev1api.ResourceList) *PersistentVolumeClaimBuilder {
if b.object.Spec.Resources.Limits == nil {
b.object.Spec.Resources.Limits = make(map[corev1api.ResourceName]resource.Quantity)
}
b.object.Spec.Resources.Limits = limits
return b
}
// DataSource sets the PersistentVolumeClaim's spec.DataSource.
func (b *PersistentVolumeClaimBuilder) DataSource(dataSource *corev1api.TypedLocalObjectReference) *PersistentVolumeClaimBuilder {
b.object.Spec.DataSource = dataSource
return b
}
// DataSourceRef sets the PersistentVolumeClaim's spec.DataSourceRef.
func (b *PersistentVolumeClaimBuilder) DataSourceRef(dataSourceRef *corev1api.TypedLocalObjectReference) *PersistentVolumeClaimBuilder {
b.object.Spec.DataSourceRef = dataSourceRef
return b
}
// Selector sets the PersistentVolumeClaim's spec.Selector.
func (b *PersistentVolumeClaimBuilder) Selector(labelSelector *metav1.LabelSelector) *PersistentVolumeClaimBuilder {
b.object.Spec.Selector = labelSelector
return b
}
+13 -3
View File
@@ -22,11 +22,10 @@ import (
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/features"
"github.com/vmware-tanzu/velero/pkg/backup"
"github.com/vmware-tanzu/velero/pkg/client"
velerodiscovery "github.com/vmware-tanzu/velero/pkg/discovery"
"github.com/vmware-tanzu/velero/pkg/features"
veleroplugin "github.com/vmware-tanzu/velero/pkg/plugin/framework"
plugincommon "github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/restore"
@@ -59,7 +58,8 @@ func NewCommand(f client.Factory) *cobra.Command {
RegisterRestoreItemAction("velero.io/change-pvc-node-selector", newChangePVCNodeSelectorItemAction(f)).
RegisterRestoreItemAction("velero.io/apiservice", newAPIServiceRestoreItemAction).
RegisterRestoreItemAction("velero.io/admission-webhook-configuration", newAdmissionWebhookConfigurationAction).
RegisterRestoreItemAction("velero.io/secret", newSecretRestoreItemAction(f))
RegisterRestoreItemAction("velero.io/secret", newSecretRestoreItemAction(f)).
RegisterRestoreItemAction("velero.io/dataupload", newDataUploadRetrieveAction(f))
if !features.IsEnabled(velerov1api.APIGroupVersionsFeatureFlag) {
// Do not register crd-remap-version BIA if the API Group feature flag is enabled, so that the v1 CRD can be backed up
pluginServer = pluginServer.RegisterBackupItemAction("velero.io/crd-remap-version", newRemapCRDVersionAction(f))
@@ -245,3 +245,13 @@ func newSecretRestoreItemAction(f client.Factory) plugincommon.HandlerInitialize
return restore.NewSecretAction(logger, client), nil
}
}
func newDataUploadRetrieveAction(f client.Factory) plugincommon.HandlerInitializer {
return func(logger logrus.FieldLogger) (interface{}, error) {
client, err := f.KubeClient()
if err != nil {
return nil, err
}
return restore.NewDataUploadRetrieveAction(logger, client.CoreV1().ConfigMaps(f.Namespace())), nil
}
}
+2
View File
@@ -521,6 +521,7 @@ High priorities:
- VolumeSnapshotContents are needed as they contain the handle to the volume snapshot in the
storage provider
- VolumeSnapshots are needed to create PVCs using the VolumeSnapshot as their data source.
- DataUploads need to restore before PVC for Snapshot DataMover to work, because PVC needs the DataUploadResults to create DataDownloads.
- PVs go before PVCs because PVCs depend on them.
- PVCs go before pods or controllers so they can be mounted as volumes.
- Service accounts go before secrets so service account token secrets can be filled automatically.
@@ -551,6 +552,7 @@ var defaultRestorePriorities = restore.Priorities{
"volumesnapshotclass.snapshot.storage.k8s.io",
"volumesnapshotcontents.snapshot.storage.k8s.io",
"volumesnapshots.snapshot.storage.k8s.io",
"datauploads.velero.io",
"persistentvolumes",
"persistentvolumeclaims",
"serviceaccounts",
+105
View File
@@ -0,0 +1,105 @@
/*
Copyright 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"
"encoding/json"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
"github.com/vmware-tanzu/velero/pkg/label"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
type DataUploadRetrieveAction struct {
logger logrus.FieldLogger
configMapClient corev1client.ConfigMapInterface
}
func NewDataUploadRetrieveAction(logger logrus.FieldLogger, configMapClient corev1client.ConfigMapInterface) *DataUploadRetrieveAction {
return &DataUploadRetrieveAction{
logger: logger,
configMapClient: configMapClient,
}
}
func (d *DataUploadRetrieveAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"datauploads.velero.io"},
}, nil
}
func (d *DataUploadRetrieveAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
d.logger.Info("Executing DataUploadRetrieveAction")
dataUpload := velerov2alpha1.DataUpload{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.ItemFromBackup.UnstructuredContent(), &dataUpload); err != nil {
d.logger.Errorf("unable to convert unstructured item to DataUpload: %s", err.Error())
return nil, errors.Wrap(err, "unable to convert unstructured item to DataUpload.")
}
dataUploadResult := velerov2alpha1.DataUploadResult{
BackupStorageLocation: dataUpload.Spec.BackupStorageLocation,
DataMover: dataUpload.Spec.DataMover,
SnapshotID: dataUpload.Status.SnapshotID,
SourceNamespace: dataUpload.Spec.SourceNamespace,
DataMoverResult: dataUpload.Status.DataMoverResult,
}
jsonBytes, err := json.Marshal(dataUploadResult)
if err != nil {
d.logger.Errorf("fail to convert DataUploadResult to JSON: %s", err.Error())
return nil, errors.Wrap(err, "fail to convert DataUploadResult to JSON")
}
cm := corev1api.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: corev1api.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: dataUpload.Name + "-",
Namespace: dataUpload.Namespace,
Labels: map[string]string{
velerov1api.RestoreUIDLabel: label.GetValidName(string(input.Restore.UID)),
velerov1api.PVCNamespaceNameLabel: dataUpload.Spec.SourceNamespace + "." + dataUpload.Spec.SourcePVC,
velerov1api.ResourceUsageLabel: string(velerov1api.VeleroResourceUsageDataUploadResult),
},
},
Data: map[string]string{
string(input.Restore.UID): string(jsonBytes),
},
}
_, err = d.configMapClient.Create(context.Background(), &cm, metav1.CreateOptions{})
if err != nil {
d.logger.Errorf("fail to create DataUploadResult ConfigMap %s/%s: %s", cm.Namespace, cm.Name, err.Error())
return nil, errors.Wrap(err, "fail to create DataUploadResult ConfigMap")
}
return &velero.RestoreItemActionExecuteOutput{
SkipRestore: true,
}, nil
}
@@ -0,0 +1,88 @@
/*
Copyright 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"
"testing"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
func TestDataUploadRetrieveActionExectue(t *testing.T) {
tests := []struct {
name string
dataUpload *velerov2alpha1.DataUpload
restore *velerov1.Restore
expectedDataUploadResult *corev1.ConfigMap
expectedErr string
}{
{
name: "DataUploadRetrieve Action test",
dataUpload: builder.ForDataUpload("velero", "testDU").SourceNamespace("testNamespace").SourcePVC("testPVC").Result(),
restore: builder.ForRestore("velero", "testRestore").ObjectMeta(builder.WithUID("testingUID")).Result(),
expectedDataUploadResult: builder.ForConfigMap("velero", "").ObjectMeta(builder.WithGenerateName("testDU-"), builder.WithLabels(velerov1.PVCNamespaceNameLabel, "testNamespace.testPVC", velerov1.RestoreUIDLabel, "testingUID", velerov1.ResourceUsageLabel, string(velerov1.VeleroResourceUsageDataUploadResult))).Data("testingUID", `{"backupStorageLocation":"","sourceNamespace":"testNamespace"}`).Result(),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
logger := velerotest.NewLogger()
cmClient := fake.NewSimpleClientset()
var unstructuredDataUpload map[string]interface{}
if tc.dataUpload != nil {
var err error
unstructuredDataUpload, err = runtime.DefaultUnstructuredConverter.ToUnstructured(tc.dataUpload)
require.NoError(t, err)
}
input := velero.RestoreItemActionExecuteInput{
Restore: tc.restore,
ItemFromBackup: &unstructured.Unstructured{Object: unstructuredDataUpload},
}
action := NewDataUploadRetrieveAction(logger, cmClient.CoreV1().ConfigMaps("velero"))
_, err := action.Execute(&input)
if tc.expectedErr != "" {
require.Equal(t, tc.expectedErr, err.Error())
}
require.NoError(t, err)
if tc.expectedDataUploadResult != nil {
cmList, err := cmClient.CoreV1().ConfigMaps("velero").List(context.Background(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s,%s=%s", velerov1.RestoreUIDLabel, "testingUID", velerov1.PVCNamespaceNameLabel, tc.dataUpload.Spec.SourceNamespace+"."+tc.dataUpload.Spec.SourcePVC),
})
require.NoError(t, err)
// debug
fmt.Printf("CM: %s\n", &cmList.Items[0])
require.Equal(t, *tc.expectedDataUploadResult, cmList.Items[0])
}
})
}
}
+15
View File
@@ -535,6 +535,21 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
// Close the progress update channel.
quit <- struct{}{}
// Clean the DataUploadResult ConfigMaps
defer func() {
opts := []crclient.DeleteAllOfOption{
crclient.InNamespace(ctx.restore.Namespace),
crclient.MatchingLabels{
velerov1api.RestoreUIDLabel: string(ctx.restore.UID),
velerov1api.ResourceUsageLabel: string(velerov1api.VeleroResourceUsageDataUploadResult),
},
}
err := ctx.kbClient.DeleteAllOf(go_context.Background(), &v1.ConfigMap{}, opts...)
if err != nil {
ctx.log.Errorf("Fail to batch delete DataUploadResult ConfigMaps for restore %s: %s", ctx.restore.Name, err.Error())
}
}()
// Do a final progress update as stopping the ticker might have left last few
// updates from taking place.
updated := ctx.restore.DeepCopy()
+12
View File
@@ -1397,6 +1397,18 @@ func TestRestoreActionsRunForCorrectItems(t *testing.T) {
new(recordResourcesAction).ForNamespace("ns-2").ForResource("pods"): nil,
},
},
{
name: "actions run for datauploads resource",
restore: defaultRestore().Result(),
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).
AddItems("datauploads.velero.io", builder.ForDataUpload("velero", "du").Result()).
Done(),
apiResources: []*test.APIResource{test.DataUploads()},
actions: map[*recordResourcesAction][]string{
new(recordResourcesAction).ForNamespace("velero").ForResource("datauploads.velero.io"): {"velero/du"},
},
},
}
for _, tc := range tests {
+10
View File
@@ -183,3 +183,13 @@ func Services(items ...metav1.Object) *APIResource {
Items: items,
}
}
func DataUploads(items ...metav1.Object) *APIResource {
return &APIResource{
Group: "velero.io",
Version: "v2alpha1",
Name: "datauploads",
Namespaced: true,
Items: items,
}
}