Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
d2555bfc28 Bump github/codeql-action from 3 to 4
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m19s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-15 13:54:08 +08:00
11 changed files with 230 additions and 173 deletions

View File

@@ -31,6 +31,6 @@ jobs:
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: 'trivy-results.sarif'

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.1.0
- uses: actions/stale@v10.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days. If a Velero team member has requested log or more information, please provide the output of the shared commands."

View File

@@ -1 +0,0 @@
Fix issue #7904, remove the code and doc for PVC node selection

View File

@@ -1 +0,0 @@
Fix issue #9332, add bytesDone for cache files

View File

@@ -275,7 +275,7 @@ func (r *BackupRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request)
log.WithError(err).Warn("Failed to get keepLatestMaintenanceJobs from ConfigMap, using CLI parameter value")
}
if err := maintenance.DeleteOldJobs(r.Client, *backupRepo, keepJobs, log); err != nil {
if err := maintenance.DeleteOldJobs(r.Client, req.Name, keepJobs, log); err != nil {
log.WithError(err).Warn("Failed to delete old maintenance jobs")
}
}

View File

@@ -32,13 +32,11 @@ import (
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerolabel "github.com/vmware-tanzu/velero/pkg/label"
velerotypes "github.com/vmware-tanzu/velero/pkg/types"
"github.com/vmware-tanzu/velero/pkg/util"
"github.com/vmware-tanzu/velero/pkg/util/kube"
@@ -70,22 +68,11 @@ func GenerateJobName(repo string) string {
}
// DeleteOldJobs deletes old maintenance jobs and keeps the latest N jobs
func DeleteOldJobs(cli client.Client, repo velerov1api.BackupRepository, keep int, logger logrus.FieldLogger) error {
func DeleteOldJobs(cli client.Client, repo string, keep int, logger logrus.FieldLogger) error {
logger.Infof("Start to delete old maintenance jobs. %d jobs will be kept.", keep)
// Get the maintenance job list by label
jobList := &batchv1api.JobList{}
err := cli.List(
context.TODO(),
jobList,
&client.ListOptions{
Namespace: repo.Namespace,
LabelSelector: labels.SelectorFromSet(
map[string]string{
RepositoryNameLabel: velerolabel.GetValidName(repo.Name),
},
),
},
)
err := cli.List(context.TODO(), jobList, client.MatchingLabels(map[string]string{RepositoryNameLabel: repo}))
if err != nil {
return err
}
@@ -352,17 +339,10 @@ func WaitJobComplete(cli client.Client, ctx context.Context, jobName, ns string,
// and then return the maintenance jobs' status in the range of limit
func WaitAllJobsComplete(ctx context.Context, cli client.Client, repo *velerov1api.BackupRepository, limit int, log logrus.FieldLogger) ([]velerov1api.BackupRepositoryMaintenanceStatus, error) {
jobList := &batchv1api.JobList{}
err := cli.List(
context.TODO(),
jobList,
&client.ListOptions{
Namespace: repo.Namespace,
LabelSelector: labels.SelectorFromSet(
map[string]string{
RepositoryNameLabel: velerolabel.GetValidName(repo.Name),
},
),
},
err := cli.List(context.TODO(), jobList, &client.ListOptions{
Namespace: repo.Namespace,
},
client.MatchingLabels(map[string]string{RepositoryNameLabel: repo.Name}),
)
if err != nil {
@@ -578,7 +558,7 @@ func buildJob(
}
podLabels := map[string]string{
RepositoryNameLabel: velerolabel.GetValidName(repo.Name),
RepositoryNameLabel: repo.Name,
}
for _, k := range util.ThirdPartyLabels {
@@ -608,7 +588,7 @@ func buildJob(
Name: GenerateJobName(repo.Name),
Namespace: repo.Namespace,
Labels: map[string]string{
RepositoryNameLabel: velerolabel.GetValidName(repo.Name),
RepositoryNameLabel: repo.Name,
},
},
Spec: batchv1api.JobSpec{

View File

@@ -40,7 +40,6 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
velerolabel "github.com/vmware-tanzu/velero/pkg/label"
"github.com/vmware-tanzu/velero/pkg/repository/provider"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
velerotypes "github.com/vmware-tanzu/velero/pkg/types"
@@ -49,7 +48,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/util/logging"
)
func TestGenerateJobName(t *testing.T) {
func TestGenerateJobName1(t *testing.T) {
testCases := []struct {
repo string
expectedStart string
@@ -83,62 +82,59 @@ func TestGenerateJobName(t *testing.T) {
}
func TestDeleteOldJobs(t *testing.T) {
// Set up test repo and keep value
repo := &velerov1api.BackupRepository{
ObjectMeta: metav1.ObjectMeta{
Name: "label with more than 63 characters should be modified",
Namespace: velerov1api.DefaultNamespace,
},
}
keep := 1
repo := "test-repo"
keep := 2
jobArray := []client.Object{
&batchv1api.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job-0",
Namespace: velerov1api.DefaultNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
},
Spec: batchv1api.JobSpec{},
},
&batchv1api.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job-1",
Namespace: velerov1api.DefaultNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
},
Spec: batchv1api.JobSpec{},
},
}
newJob := &batchv1api.Job{
// Create some maintenance jobs for testing
var objs []client.Object
// Create a newer job
newerJob := &batchv1api.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job-new",
Namespace: velerov1api.DefaultNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
Name: "job1",
Namespace: "default",
Labels: map[string]string{RepositoryNameLabel: repo},
},
Spec: batchv1api.JobSpec{},
}
// Create a fake Kubernetes client with 2 jobs.
objs = append(objs, newerJob)
// Create older jobs
for i := 2; i <= 3; i++ {
olderJob := &batchv1api.Job{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("job%d", i),
Namespace: "default",
Labels: map[string]string{RepositoryNameLabel: repo},
CreationTimestamp: metav1.Time{
Time: metav1.Now().Add(time.Duration(-24*i) * time.Hour),
},
},
Spec: batchv1api.JobSpec{},
}
objs = append(objs, olderJob)
}
// Create a fake Kubernetes client
scheme := runtime.NewScheme()
_ = batchv1api.AddToScheme(scheme)
cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(jobArray...).Build()
// Create a new job
require.NoError(t, cli.Create(context.TODO(), newJob))
cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).Build()
// Call the function
require.NoError(t, DeleteOldJobs(cli, *repo, keep, velerotest.NewLogger()))
err := DeleteOldJobs(cli, repo, keep, velerotest.NewLogger())
require.NoError(t, err)
// Get the remaining jobs
jobList := &batchv1api.JobList{}
require.NoError(t, cli.List(t.Context(), jobList, client.MatchingLabels(map[string]string{RepositoryNameLabel: repo.Name})))
err = cli.List(t.Context(), jobList, client.MatchingLabels(map[string]string{RepositoryNameLabel: repo}))
require.NoError(t, err)
// We expect the number of jobs to be equal to 'keep'
assert.Len(t, jobList.Items, keep)
// Only the new created job should be left.
assert.Equal(t, jobList.Items[0].Name, newJob.Name)
// We expect that the oldest jobs were deleted
// Job3 should not be present in the remaining list
assert.NotContains(t, jobList.Items, objs[2])
// Job2 should also not be present in the remaining list
assert.NotContains(t, jobList.Items, objs[1])
}
func TestWaitForJobComplete(t *testing.T) {
@@ -575,7 +571,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
repo := &velerov1api.BackupRepository{
ObjectMeta: metav1.ObjectMeta{
Namespace: veleroNamespace,
Name: "label with more than 63 characters should be modified",
Name: "fake-repo",
},
Spec: velerov1api.BackupRepositorySpec{
BackupStorageLocation: "default",
@@ -599,7 +595,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: veleroNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
Labels: map[string]string{RepositoryNameLabel: "fake-repo"},
CreationTimestamp: metav1.Time{Time: now},
},
}
@@ -608,7 +604,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: veleroNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
Labels: map[string]string{RepositoryNameLabel: "fake-repo"},
CreationTimestamp: metav1.Time{Time: now},
},
Status: batchv1api.JobStatus{
@@ -628,7 +624,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "job2",
Namespace: veleroNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
Labels: map[string]string{RepositoryNameLabel: "fake-repo"},
CreationTimestamp: metav1.Time{Time: now.Add(time.Hour)},
},
Status: batchv1api.JobStatus{
@@ -649,7 +645,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "job3",
Namespace: veleroNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
Labels: map[string]string{RepositoryNameLabel: "fake-repo"},
CreationTimestamp: metav1.Time{Time: now.Add(time.Hour * 2)},
},
Status: batchv1api.JobStatus{
@@ -669,7 +665,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "job4",
Namespace: veleroNamespace,
Labels: map[string]string{RepositoryNameLabel: velerolabel.GetValidName(repo.Name)},
Labels: map[string]string{RepositoryNameLabel: "fake-repo"},
CreationTimestamp: metav1.Time{Time: now.Add(time.Hour * 3)},
},
Status: batchv1api.JobStatus{
@@ -702,7 +698,7 @@ func TestWaitAllJobsComplete(t *testing.T) {
{
name: "list job error",
runtimeScheme: schemeFail,
expectedError: "error listing maintenance job for repo label with more than 63 characters should be modified: no kind is registered for the type v1.JobList in scheme",
expectedError: "error listing maintenance job for repo fake-repo: no kind is registered for the type v1.JobList in scheme",
},
{
name: "job not exist",
@@ -947,7 +943,6 @@ func TestBuildJob(t *testing.T) {
expectedSecurityContext *corev1api.SecurityContext
expectedPodSecurityContext *corev1api.PodSecurityContext
expectedImagePullSecrets []corev1api.LocalObjectReference
backupRepository *velerov1api.BackupRepository
}{
{
name: "Valid maintenance job without third party labels",
@@ -1065,64 +1060,6 @@ func TestBuildJob(t *testing.T) {
expectedJobName: "",
expectedError: true,
},
{
name: "Valid maintenance job with third party labels and BackupRepository name longer than 63",
m: &velerotypes.JobConfigs{
PodResources: &kube.PodResources{
CPURequest: "100m",
MemoryRequest: "128Mi",
CPULimit: "200m",
MemoryLimit: "256Mi",
},
},
deploy: deploy2,
logLevel: logrus.InfoLevel,
logFormat: logging.NewFormatFlag(),
expectedError: false,
expectedEnv: []corev1api.EnvVar{
{
Name: "test-name",
Value: "test-value",
},
},
expectedEnvFrom: []corev1api.EnvFromSource{
{
ConfigMapRef: &corev1api.ConfigMapEnvSource{
LocalObjectReference: corev1api.LocalObjectReference{
Name: "test-configmap",
},
},
},
{
SecretRef: &corev1api.SecretEnvSource{
LocalObjectReference: corev1api.LocalObjectReference{
Name: "test-secret",
},
},
},
},
expectedPodLabel: map[string]string{
RepositoryNameLabel: velerolabel.GetValidName("label with more than 63 characters should be modified"),
"azure.workload.identity/use": "fake-label-value",
},
expectedSecurityContext: nil,
expectedPodSecurityContext: nil,
expectedImagePullSecrets: []corev1api.LocalObjectReference{
{
Name: "imagePullSecret1",
},
},
backupRepository: &velerov1api.BackupRepository{
ObjectMeta: metav1.ObjectMeta{
Namespace: "velero",
Name: "label with more than 63 characters should be modified",
},
Spec: velerov1api.BackupRepositorySpec{
VolumeNamespace: "test-123",
RepositoryType: "kopia",
},
},
},
}
param := provider.RepoParam{
@@ -1146,10 +1083,6 @@ func TestBuildJob(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.backupRepository != nil {
param.BackupRepo = tc.backupRepository
}
// Create a fake clientset with resources
objs := []runtime.Object{param.BackupLocation, param.BackupRepo}

View File

@@ -17,15 +17,20 @@ limitations under the License.
package actions
import (
"context"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/util"
)
@@ -86,13 +91,46 @@ func (p *PVCAction) Execute(input *velero.RestoreItemActionExecuteInput) (*veler
return nil, errors.WithStack(err)
}
if pvc.Annotations == nil {
pvc.Annotations = make(map[string]string)
}
log := p.logger.WithFields(map[string]any{
"kind": pvc.Kind,
"namespace": pvc.Namespace,
"name": pvc.Name,
})
// Remove PVC annotations
// Handle selected node annotation
node, ok := pvc.Annotations[AnnSelectedNode]
if ok {
// fetch node mapping from configMap
newNode, err := getNewNodeFromConfigMap(p.configMapClient, node)
if err != nil {
return nil, err
}
if len(newNode) != 0 {
// Check whether the mapped node exists first.
exists, err := isNodeExist(p.nodeClient, newNode)
if err != nil {
return nil, errors.Wrapf(err, "error checking %s's mapped node %s existence", node, newNode)
}
if !exists {
log.Warnf("Selected-node's mapped node doesn't exist: source: %s, dest: %s. Please check the ConfigMap with label velero.io/change-pvc-node-selector.", node, newNode)
}
// set node selector
// We assume that node exist for node-mapping
pvc.Annotations[AnnSelectedNode] = newNode
log.Infof("Updating selected-node to %s from %s", newNode, node)
} else {
log.Info("Clearing PVC selected-node annotation")
delete(pvc.Annotations, AnnSelectedNode)
}
}
// Remove other annotations
removePVCAnnotations(
&pvc,
[]string{
@@ -100,7 +138,6 @@ func (p *PVCAction) Execute(input *velero.RestoreItemActionExecuteInput) (*veler
AnnBoundByController,
AnnStorageProvisioner,
AnnBetaStorageProvisioner,
AnnSelectedNode,
velerov1api.VolumeSnapshotLabel,
velerov1api.DataUploadNameAnnotation,
},
@@ -130,6 +167,34 @@ func (p *PVCAction) Execute(input *velero.RestoreItemActionExecuteInput) (*veler
return output, nil
}
func getNewNodeFromConfigMap(client corev1client.ConfigMapInterface, node string) (string, error) {
// fetch node mapping from configMap
config, err := common.GetPluginConfig(common.PluginKindRestoreItemAction, "velero.io/change-pvc-node-selector", client)
if err != nil {
return "", err
}
if config == nil {
// there is no node mapping defined for change-pvc-node
// so we will return empty new node
return "", nil
}
return config.Data[node], nil
}
// isNodeExist check if node resource exist or not
func isNodeExist(nodeClient corev1client.NodeInterface, name string) (bool, error) {
_, err := nodeClient.Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
func removePVCAnnotations(pvc *corev1api.PersistentVolumeClaim, remove []string) {
for k := range pvc.Annotations {
if util.Contains(remove, k) {

View File

@@ -17,9 +17,11 @@ limitations under the License.
package actions
import (
"bytes"
"fmt"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
@@ -40,57 +42,105 @@ import (
// desired result.
func TestPVCActionExecute(t *testing.T) {
tests := []struct {
name string
pvc *corev1api.PersistentVolumeClaim
want *corev1api.PersistentVolumeClaim
wantErr error
name string
pvc *corev1api.PersistentVolumeClaim
configMap *corev1api.ConfigMap
node *corev1api.Node
newNode *corev1api.Node
want *corev1api.PersistentVolumeClaim
wantErr error
}{
{
name: "a persistent volume claim with no annotation",
pvc: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
},
{
name: "a persistent volume claim with selected-node annotation",
name: "a valid mapping for a persistent volume claim is applied correctly",
pvc: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").
ObjectMeta(
builder.WithAnnotations("volume.kubernetes.io/selected-node", "source-node"),
).Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").ObjectMeta(builder.WithAnnotationsMap(map[string]string{})).Result(),
configMap: builder.ForConfigMap("velero", "change-pvc-node").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "", "velero.io/change-pvc-node-selector", "RestoreItemAction")).
Data("source-node", "dest-node").
Result(),
newNode: builder.ForNode("dest-node").Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").
ObjectMeta(
builder.WithAnnotations("volume.kubernetes.io/selected-node", "dest-node"),
).Result(),
},
{
name: "a persistent volume claim with other annotation",
name: "when no config map exists for the plugin, the item is returned without node selector",
pvc: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").
ObjectMeta(
builder.WithAnnotations("other-anno-1", "other-value-1", "other-anno-2", "other-value-2"),
builder.WithAnnotations("volume.kubernetes.io/selected-node", "source-node"),
).Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").ObjectMeta(
builder.WithAnnotations("other-anno-1", "other-value-1", "other-anno-2", "other-value-2"),
).Result(),
configMap: builder.ForConfigMap("velero", "change-pvc-node").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "", "velero.io/some-other-plugin", "RestoreItemAction")).
Data("source-node", "dest-node").
Result(),
node: builder.ForNode("source-node").Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
},
{
name: "a persistent volume claim with other annotation and selected-node annotation",
name: "when no node-mappings exist in the plugin config map, the item is returned without node selector",
pvc: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").
ObjectMeta(
builder.WithAnnotations("other-anno", "other-value", "volume.kubernetes.io/selected-node", "source-node"),
builder.WithAnnotations("volume.kubernetes.io/selected-node", "source-node"),
).Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").ObjectMeta(
builder.WithAnnotations("other-anno", "other-value"),
).Result(),
configMap: builder.ForConfigMap("velero", "change-pvc-node").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "", "velero.io/change-pvc-node-selector", "RestoreItemAction")).
Result(),
node: builder.ForNode("source-node").Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
},
{
name: "when persistent volume claim has no node selector, the item is returned as-is",
pvc: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
configMap: builder.ForConfigMap("velero", "change-pvc-node").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "", "velero.io/change-pvc-node-selector", "RestoreItemAction")).
Data("source-node", "dest-node").
Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
},
{
name: "when persistent volume claim's node-selector has no mapping in the config map, the item is returned without node selector",
pvc: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").
ObjectMeta(
builder.WithAnnotations("volume.kubernetes.io/selected-node", "source-node"),
).Result(),
configMap: builder.ForConfigMap("velero", "change-pvc-node").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "", "velero.io/change-pvc-node-selector", "RestoreItemAction")).
Data("source-node-1", "dest-node").
Result(),
node: builder.ForNode("source-node").Result(),
want: builder.ForPersistentVolumeClaim("source-ns", "pvc-1").Result(),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
clientset := fake.NewSimpleClientset()
logger := logrus.StandardLogger()
buf := bytes.Buffer{}
logrus.SetOutput(&buf)
a := NewPVCAction(
velerotest.NewLogger(),
logger,
clientset.CoreV1().ConfigMaps("velero"),
clientset.CoreV1().Nodes(),
)
// set up test data
if tc.configMap != nil {
_, err := clientset.CoreV1().ConfigMaps(tc.configMap.Namespace).Create(t.Context(), tc.configMap, metav1.CreateOptions{})
require.NoError(t, err)
}
if tc.node != nil {
_, err := clientset.CoreV1().Nodes().Create(t.Context(), tc.node, metav1.CreateOptions{})
require.NoError(t, err)
}
if tc.newNode != nil {
_, err := clientset.CoreV1().Nodes().Create(t.Context(), tc.newNode, metav1.CreateOptions{})
require.NoError(t, err)
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvc)
require.NoError(t, err)
@@ -106,6 +156,10 @@ func TestPVCActionExecute(t *testing.T) {
// execute method under test
res, err := a.Execute(input)
// Make sure mapped selected-node exists.
logOutput := buf.String()
assert.NotContains(t, logOutput, "Selected-node's mapped node doesn't exist")
// validate for both error and non-error cases
switch {
case tc.wantErr != nil:

View File

@@ -121,7 +121,6 @@ func (p *Progress) UploadStarted() {}
// CachedFile statistic the total bytes been cached currently
func (p *Progress) CachedFile(fname string, numBytes int64) {
atomic.AddInt64(&p.cachedBytes, numBytes)
atomic.AddInt64(&p.processedBytes, numBytes)
p.UpdateProgress()
}

View File

@@ -215,9 +215,37 @@ data:
### PVC selected-node
Velero removes PVC's `volume.kubernetes.io/selected-node` annotation during restore, so that the restored PVC could be provisioned appropriately according to ```WaitForFirstConsumer``` rules, storage topologies and the restored pod's schedule result, etc.
Velero by default removes PVC's `volume.kubernetes.io/selected-node` annotation during restore, so that the restored PVC could be provisioned appropriately according to ```WaitForFirstConsumer``` rules, storage topologies and the restored pod's schedule result, etc.
For more information of how this selected-node annotation matters to PVC restore, see issue https://github.com/vmware-tanzu/velero/issues/9053.
For more information of how this selected-node annotation matters to PVC restore, see issue https://github.com/vmware-tanzu/velero/issues/9053.
As an expectation, when you provide the selected-node configuration, Velero sets the annotation to the node in the configuration, if the node doesn't exist in the cluster then the annotation will also be removed.
Note: This feature is under deprecation as of Velero 1.15, following Velero deprecation policy. This feature is primarily used to remedy some problems in old Kubernetes versions as described [here](https://github.com/vmware-tanzu/velero/pull/2377). It may not work with the new features of Kubernetes and Velero. For more information, see issue https://github.com/vmware-tanzu/velero/issues/9053 for more information.
To configure a selected-node, create a config map in the Velero namespace like the following:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
# any name can be used; Velero uses the labels (below)
# to identify it rather than the name
name: change-pvc-node-selector-config
# must be in the velero namespace
namespace: velero
# the below labels should be used verbatim in your
# ConfigMap.
labels:
# this value-less label identifies the ConfigMap as
# config for a plugin (i.e. the built-in restore item action plugin)
velero.io/plugin-config: ""
# this label identifies the name and kind of plugin
# that this ConfigMap is for.
velero.io/change-pvc-node-selector: RestoreItemAction
data:
# add 1+ key-value pairs here, where the key is the old
# node name and the value is the new node name.
<old-node-name>: <new-node-name>
```
## Restoring into a different namespace