mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 11:46:06 +00:00
Merge pull request #6410 from blackpiglet/6117_fix
Retrieve DataUpload into result ConfigMap during volume snapshot restore.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Retrieve DataUpload into backup result ConfigMap during volume snapshot restore.
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user