Files
velero/pkg/datamover/dataupload_delete_action.go

134 lines
4.9 KiB
Go

package datamover
import (
"context"
"encoding/json"
"fmt"
"github.com/cockroachdb/errors"
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
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/label"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
repotypes "github.com/vmware-tanzu/velero/pkg/repository/types"
datamoverutil "github.com/vmware-tanzu/velero/pkg/util/datamover"
)
type DataUploadDeleteAction struct {
logger logrus.FieldLogger
client client.Client
}
func (d *DataUploadDeleteAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"datauploads.velero.io"},
}, nil
}
func (d *DataUploadDeleteAction) Execute(input *velero.DeleteItemActionExecuteInput) error {
d.logger.Infof("Executing DataUploadDeleteAction")
du := &velerov2alpha1.DataUpload{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), &du); err != nil {
return errors.WithStack(errors.Wrapf(err, "failed to convert input.Item from unstructured"))
}
// Only create a snapshot-info ConfigMap when the DataUpload's owning
// backup (its velero.io/backup-name label) matches the backup currently
// being deleted. Two other cases reach this code path and must be
// skipped, because the resulting CM would be unmatchable and only adds
// etcd churn:
//
// 1. The label is missing. We have no verifiable owner, so a CM created
// with the executing backup's label is a guess that deleteMovedSnapshots
// cannot rely on.
// 2. The label names a different backup. Velero does not support
// self-protection, so this almost always means the velero namespace
// was captured in a backup tarball and the DataUpload CR belongs to
// an unrelated backup. Creating a CM labeled with the executing
// backup mislabels the snapshot and causes the real owning backup's
// deleteMovedSnapshots query to miss it, leaking the Kopia snapshot
// in the object store.
//
// Both cases warn so misconfigured installs surface in logs.
owner := du.Labels[velerov1.BackupNameLabel]
switch {
case owner == "":
d.logger.Warnf(
"DataUpload %q has no %q label, so its owning backup cannot be verified; "+
"skipping snapshot-info ConfigMap creation because a CM without a verifiable owner "+
"cannot be matched back to its snapshot at backup deletion time.",
du.Name, velerov1.BackupNameLabel,
)
return nil
case owner != label.GetValidName(input.Backup.Name):
d.logger.Warnf(
"DataUpload %q belongs to backup %q but is being deleted under backup %q; "+
"this almost always means the velero namespace was included in a backup tarball. "+
"Velero does not support self-protection — exclude the velero namespace from your schedules. "+
"Skipping snapshot-info ConfigMap creation to avoid mislabeling.",
du.Name, owner, input.Backup.Name,
)
return nil
}
cm := genConfigmap(input.Backup, *du)
if cm == nil {
// will not fail the backup deletion
return nil
}
err := d.client.Create(context.Background(), cm)
if err != nil {
return errors.WithStack(errors.Wrapf(err, "failed to create the configmap for DataUpload %s/%s", du.Namespace, du.Name))
}
return nil
}
// generate the configmap which is to be created and used as a way to communicate the snapshot info to the backup deletion controller
func genConfigmap(bak *velerov1.Backup, du velerov2alpha1.DataUpload) *corev1api.ConfigMap {
if !datamoverutil.IsBuiltInDataMover(du.Spec.DataMover) || du.Status.SnapshotID == "" {
return nil
}
snapshot := repotypes.SnapshotIdentifier{
VolumeNamespace: du.Spec.SourceNamespace,
BackupStorageLocation: bak.Spec.StorageLocation,
SnapshotID: du.Status.SnapshotID,
RepositoryType: velerov1.BackupRepositoryTypeKopia,
UploaderType: GetUploaderType(du.Spec.DataMover),
Source: GetRealSource(du.Spec.SourceNamespace, du.Spec.SourcePVC),
}
b, err := json.Marshal(snapshot)
if err != nil {
return nil
}
data := make(map[string]string)
if err := json.Unmarshal(b, &data); err != nil {
return nil
}
return &corev1api.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1api.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: bak.Namespace,
Name: fmt.Sprintf("%s-info", du.Name),
Labels: map[string]string{
velerov1.BackupNameLabel: bak.Name,
velerov1.DataUploadSnapshotInfoLabel: "true",
},
},
Data: data,
}
}
func NewDataUploadDeleteAction(logger logrus.FieldLogger, client client.Client) *DataUploadDeleteAction {
return &DataUploadDeleteAction{
logger: logger,
client: client,
}
}