diff --git a/Makefile b/Makefile index 788da9dfb10..2067a2af3b5 100644 --- a/Makefile +++ b/Makefile @@ -333,7 +333,7 @@ ifneq ($(shell $(OPERATOR_SDK) version | cut -d'"' -f2),$(OPERATOR_SDK_VERSION)) set -e; \ mkdir -p $(dir $(OPERATOR_SDK)) ;\ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ + curl --retry 5 --retry-delay 5 -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ chmod +x $(OPERATOR_SDK); endif @if [ -L "$(LOCALBIN)/operator-sdk" ]; then \ @@ -370,7 +370,7 @@ ifneq ($(shell $(OPM) version | cut -d'"' -f2),$(OPM_VERSION)) set -e ;\ mkdir -p $(dir $(OPM)) ;\ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$${OS}-$${ARCH}-opm ;\ + curl --retry 5 --retry-delay 5 -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/$(OPM_VERSION)/$${OS}-$${ARCH}-opm ;\ chmod +x $(OPM) endif @if [ -L "$(LOCALBIN)/opm" ]; then \ @@ -499,7 +499,7 @@ else endif endif submit-coverage: - curl -Os https://uploader.codecov.io/latest/$(OS_String)/codecov + curl --retry 5 --retry-delay 5 -Os https://uploader.codecov.io/latest/$(OS_String)/codecov chmod +x codecov ./codecov -C $(shell git rev-parse HEAD) -r openshift/oadp-operator --nonZero rm -f codecov @@ -965,6 +965,13 @@ ARTIFACT_DIR ?= /tmp HCO_UPSTREAM ?= false TEST_VIRT_GA ?= false TEST_VIRT ?= false +# TEST_VIRT_KDM runs only the kubevirt-datamover-specific specs (ginkgo label +# "kdm", a subset of "virt") -- for CI jobs that build/test against the +# kubevirt-datamover-controller/-plugin repos specifically and don't need the +# full TEST_VIRT suite's runtime. TEST_VIRT=true already covers these specs +# too, since they carry both labels -- this only matters when TEST_VIRT_KDM +# is set WITHOUT TEST_VIRT. +TEST_VIRT_KDM ?= false HCO_INDEX_TAG ?= 1.18.0 # hcp TEST_HCP ?= false @@ -989,9 +996,21 @@ ifeq ($(TEST_VIRT),true) TEST_FILTER += && (virt) else ifeq ($(TEST_VIRT_GA),true) TEST_FILTER += && (virt) +else ifeq ($(TEST_VIRT_KDM),true) + TEST_FILTER += && (kdm) else TEST_FILTER += && (! virt) endif +# kdm specs need the same community-HCO/KubeVirt setup as the rest of the virt +# suite (TEST_VIRT's own -hco_community wiring below) -- without this, +# TEST_VIRT_KDM=true alone (i.e. without TEST_VIRT=true) would leave +# -hco_community=false and skip installing HCO entirely, breaking the kdm-only +# run before any spec even gets a VM to test against. +ifeq ($(TEST_VIRT_KDM),true) +HCO_COMMUNITY := true +else +HCO_COMMUNITY := $(TEST_VIRT) +endif ifeq ($(TEST_UPGRADE),true) TEST_FILTER += && (upgrade) else @@ -1049,7 +1068,7 @@ test-e2e: test-e2e-setup install-ginkgo $(if $(MUST_GATHER_REPO),build-must-gath -artifact_dir=$(ARTIFACT_DIR) \ -kvm_emulation=$(KVM_EMULATION) \ -hco_upstream=$(HCO_UPSTREAM) \ - -hco_community=$(TEST_VIRT) \ + -hco_community=$(HCO_COMMUNITY) \ -hco_index_tag=$(HCO_INDEX_TAG) \ -skipMustGather=$(SKIP_MUST_GATHER) \ $(HCP_EXTERNAL_ARGS) \ diff --git a/api/v1alpha1/dataprotectionapplication_types.go b/api/v1alpha1/dataprotectionapplication_types.go index afc70d9c709..8423d6b2970 100644 --- a/api/v1alpha1/dataprotectionapplication_types.go +++ b/api/v1alpha1/dataprotectionapplication_types.go @@ -683,6 +683,13 @@ type KubevirtDatamoverConfig struct { // +optional MaxIncrementalBackups *int32 `json:"maxIncrementalBackups,omitempty"` + // MaxConcurrentDataMovers is the maximum number of concurrent active + // DataUploads/DataDownloads per direction (DU and DD counted independently). + // 0 means unlimited (default behavior). + // +kubebuilder:validation:Minimum=0 + // +optional + MaxConcurrentDataMovers *int32 `json:"maxConcurrentDataMovers,omitempty"` + // StaleDataUploadThreshold is the duration after which a DataUpload in an // active phase is considered stale and will no longer block younger // DataUploads for the same VM. Default is 2h. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a21392a56fc..02931129263 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -745,6 +745,11 @@ func (in *KubevirtDatamoverConfig) DeepCopyInto(out *KubevirtDatamoverConfig) { *out = new(int32) **out = **in } + if in.MaxConcurrentDataMovers != nil { + in, out := &in.MaxConcurrentDataMovers, &out.MaxConcurrentDataMovers + *out = new(int32) + **out = **in + } if in.StaleDataUploadThreshold != nil { in, out := &in.StaleDataUploadThreshold, &out.StaleDataUploadThreshold *out = new(v1.Duration) diff --git a/build/ci-Dockerfile b/build/ci-Dockerfile index 2a620c0ada8..29d75cd2613 100644 --- a/build/ci-Dockerfile +++ b/build/ci-Dockerfile @@ -10,26 +10,47 @@ RUN chmod +x tests/e2e/scripts/analyze_failures.sh # Install kubectl (multi-arch) ARG TARGETARCH -RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${TARGETARCH}/kubectl" && \ +# --retry: curl's default retry set already covers exit 6 (could not resolve +# host) -- the exact transient DNS failure hit live in CI on this same class +# of fetch (see the virtctl step below) -- along with connect/read/5xx errors. +RUN curl --retry 5 --retry-delay 5 -LO "https://dl.k8s.io/release/$(curl --retry 5 --retry-delay 5 -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${TARGETARCH}/kubectl" && \ chmod +x kubectl && \ mv kubectl /usr/local/bin/ # Install virtctl for KubeVirt VM operations in E2E tests -RUN export KV_VERSION=$(curl -s https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt) && \ - curl -L -o virtctl "https://github.com/kubevirt/kubevirt/releases/download/${KV_VERSION}/virtctl-${KV_VERSION}-linux-${TARGETARCH}" && \ +# --retry: confirmed live in CI -- this exact curl failed with "Could not +# resolve host: github.com" (transient DNS blip in the build environment), +# failing the whole image build over a network hiccup unrelated to any code +# change. curl's default retry-on-error set already covers that failure mode +# (exit 6); no extra flags needed beyond --retry/--retry-delay. +RUN export KV_VERSION=$(curl --retry 5 --retry-delay 5 -s https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt) && \ + curl --retry 5 --retry-delay 5 -L -o virtctl "https://github.com/kubevirt/kubevirt/releases/download/${KV_VERSION}/virtctl-${KV_VERSION}-linux-${TARGETARCH}" && \ chmod +x virtctl && \ mv virtctl /usr/local/bin/ # Install Claude CLI (native binary, no Node.js dependency) -RUN curl -fsSL https://claude.ai/install.sh | bash && \ +RUN curl --retry 5 --retry-delay 5 -fsSL https://claude.ai/install.sh | bash && \ ln -sf ~/.local/bin/claude /usr/local/bin/claude && \ claude --version # Clone openshift/velero source code for failure analysis # Uses oadp-dev branch to match OADP operator development -RUN git clone --depth 1 --branch oadp-dev \ - https://github.com/openshift/velero.git \ - /go/src/github.com/openshift/velero +# Retried like the curl fetches above -- same category of transient +# network/DNS blip, just via git instead of curl (no built-in --retry flag). +# rm -rf before each attempt: a clone that fails partway through can leave a +# non-empty target directory behind, which would make the next attempt fail +# with "destination path already exists" instead of actually retrying. +# The trailing `test -d .../.git` is load-bearing, not decoration: without it, +# `&& break || sleep 5` makes the loop's own exit status the last `sleep`'s +# (always 0), so RUN succeeds even after all 3 clone attempts fail -- silently +# shipping an image with no velero checkout instead of failing the build here. +RUN for i in 1 2 3; do \ + rm -rf /go/src/github.com/openshift/velero && \ + git clone --depth 1 --branch oadp-dev \ + https://github.com/openshift/velero.git \ + /go/src/github.com/openshift/velero && break || sleep 5; \ + done; \ + test -d /go/src/github.com/openshift/velero/.git RUN go mod download && \ mkdir -p $(go env GOCACHE) && \ diff --git a/bundle/manifests/oadp-operator.clusterserviceversion.yaml b/bundle/manifests/oadp-operator.clusterserviceversion.yaml index ef52f7a9017..5b4da490b30 100644 --- a/bundle/manifests/oadp-operator.clusterserviceversion.yaml +++ b/bundle/manifests/oadp-operator.clusterserviceversion.yaml @@ -1249,6 +1249,13 @@ spec: - get - list - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - "" resources: @@ -1330,6 +1337,7 @@ spec: verbs: - get - list + - update - watch - apiGroups: - velero.io diff --git a/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml b/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml index 037754feebd..89566e160da 100644 --- a/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml +++ b/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml @@ -231,6 +231,14 @@ spec: kubevirtDatamover: description: KubevirtDatamover configures the kubevirt-datamover-controller for VM backup/restore. properties: + maxConcurrentDataMovers: + description: |- + MaxConcurrentDataMovers is the maximum number of concurrent active + DataUploads/DataDownloads per direction (DU and DD counted independently). + 0 means unlimited (default behavior). + format: int32 + minimum: 0 + type: integer maxIncrementalBackups: description: |- MaxIncrementalBackups is the maximum number of incremental backups per VM diff --git a/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml b/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml index 26679e419a5..36ef7cfd5ee 100644 --- a/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml +++ b/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml @@ -231,6 +231,14 @@ spec: kubevirtDatamover: description: KubevirtDatamover configures the kubevirt-datamover-controller for VM backup/restore. properties: + maxConcurrentDataMovers: + description: |- + MaxConcurrentDataMovers is the maximum number of concurrent active + DataUploads/DataDownloads per direction (DU and DD counted independently). + 0 means unlimited (default behavior). + format: int32 + minimum: 0 + type: integer maxIncrementalBackups: description: |- MaxIncrementalBackups is the maximum number of incremental backups per VM diff --git a/config/kubevirt-datamover-controller_rbac/role.yaml b/config/kubevirt-datamover-controller_rbac/role.yaml index c8b58d3a534..b00d8341ad0 100644 --- a/config/kubevirt-datamover-controller_rbac/role.yaml +++ b/config/kubevirt-datamover-controller_rbac/role.yaml @@ -4,6 +4,13 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - "" resources: @@ -85,6 +92,7 @@ rules: verbs: - get - list + - update - watch - apiGroups: - velero.io diff --git a/internal/controller/kubevirt_datamover_controller.go b/internal/controller/kubevirt_datamover_controller.go index efc4622623a..d25a500cd5e 100644 --- a/internal/controller/kubevirt_datamover_controller.go +++ b/internal/controller/kubevirt_datamover_controller.go @@ -255,6 +255,10 @@ func ensureKubevirtDatamoverRequiredSpecs( args = append(args, fmt.Sprintf("--max-incremental-backups=%d", *dpa.Spec.Configuration.KubevirtDatamover.MaxIncrementalBackups)) } + if dpa.Spec.Configuration.KubevirtDatamover.MaxConcurrentDataMovers != nil { + args = append(args, fmt.Sprintf("--max-concurrent-data-movers=%d", + *dpa.Spec.Configuration.KubevirtDatamover.MaxConcurrentDataMovers)) + } if dpa.Spec.Configuration.KubevirtDatamover.StaleDataUploadThreshold != nil { args = append(args, fmt.Sprintf("--stale-dataupload-threshold=%s", dpa.Spec.Configuration.KubevirtDatamover.StaleDataUploadThreshold.Duration.String())) diff --git a/internal/controller/kubevirt_datamover_controller_test.go b/internal/controller/kubevirt_datamover_controller_test.go index fce021ddf77..bbc1df90d3d 100644 --- a/internal/controller/kubevirt_datamover_controller_test.go +++ b/internal/controller/kubevirt_datamover_controller_test.go @@ -33,6 +33,7 @@ type ReconcileKubevirtDatamoverControllerScenario struct { eventWords []string kubevirtDatamoverEnabled bool deployment *appsv1.Deployment + kubevirtDatamoverConfig *oadpv1alpha1.KubevirtDatamoverConfig } func createTestKubevirtDatamoverDeployment(namespace string) *appsv1.Deployment { @@ -105,6 +106,10 @@ func runReconcileKubevirtDatamoverControllerTest( ) } + if scenario.kubevirtDatamoverConfig != nil { + dpa.Spec.Configuration.KubevirtDatamover = scenario.kubevirtDatamoverConfig + } + gomega.Expect(k8sClient.Create(ctx, dpa)).To(gomega.Succeed()) if scenario.deployment != nil { @@ -145,6 +150,26 @@ func runReconcileKubevirtDatamoverControllerTest( } else { gomega.Expect(len(event.Events)).To(gomega.Equal(0)) } + + if scenario.kubevirtDatamoverConfig != nil && scenario.kubevirtDatamoverConfig.MaxConcurrentDataMovers != nil { + deployment := &appsv1.Deployment{} + gomega.Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: kubevirtDatamoverObjectName, + Namespace: scenario.namespace, + }, deployment)).To(gomega.Succeed()) + + var container *corev1.Container + for i := range deployment.Spec.Template.Spec.Containers { + if deployment.Spec.Template.Spec.Containers[i].Name == "manager" { + container = &deployment.Spec.Template.Spec.Containers[i] + break + } + } + gomega.Expect(container).ToNot(gomega.BeNil()) + gomega.Expect(container.Args).To(gomega.ContainElement( + fmt.Sprintf("--max-concurrent-data-movers=%d", *scenario.kubevirtDatamoverConfig.MaxConcurrentDataMovers), + )) + } } var _ = ginkgo.Describe("Test ReconcileKubevirtDatamoverController function", func() { @@ -202,6 +227,15 @@ var _ = ginkgo.Describe("Test ReconcileKubevirtDatamoverController function", fu eventWords: []string{"Normal", "KubevirtDatamoverDeploymentReconciled", "created"}, kubevirtDatamoverEnabled: true, }), + ginkgo.Entry("Should create kubevirt-datamover deployment with max-concurrent-data-movers configured", ReconcileKubevirtDatamoverControllerScenario{ + namespace: "kdm-test-1b", + dpa: "kdm-test-1b-dpa", + eventWords: []string{"Normal", "KubevirtDatamoverDeploymentReconciled", "created"}, + kubevirtDatamoverEnabled: true, + kubevirtDatamoverConfig: &oadpv1alpha1.KubevirtDatamoverConfig{ + MaxConcurrentDataMovers: ptr.To(int32(3)), + }, + }), ginkgo.Entry("Should update kubevirt-datamover deployment", ReconcileKubevirtDatamoverControllerScenario{ namespace: "kdm-test-2", dpa: "kdm-test-2-dpa", @@ -749,6 +783,27 @@ func TestEnsureKubevirtDatamoverRequiredSpecs(t *testing.T) { expectedEnvCount: 3, expectError: false, }, + { + name: "Should include --max-concurrent-data-movers arg when configured", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpa", + Namespace: "test-namespace", + ResourceVersion: "12345", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{}, + KubevirtDatamover: &oadpv1alpha1.KubevirtDatamoverConfig{ + MaxConcurrentDataMovers: ptr.To(int32(5)), + }, + }, + }, + }, + existingContainers: nil, + expectedEnvCount: 3, + expectError: false, + }, { name: "Should include --stale-dataupload-threshold arg when configured", dpa: &oadpv1alpha1.DataProtectionApplication{ @@ -770,6 +825,24 @@ func TestEnsureKubevirtDatamoverRequiredSpecs(t *testing.T) { expectedEnvCount: 3, expectError: false, }, + { + name: "Should not include --max-concurrent-data-movers arg when not configured", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpa", + Namespace: "test-namespace", + ResourceVersion: "12345", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{}, + }, + }, + }, + existingContainers: nil, + expectedEnvCount: 3, + expectError: false, + }, { name: "Should not include --stale-dataupload-threshold arg when not configured", dpa: &oadpv1alpha1.DataProtectionApplication{ @@ -788,6 +861,31 @@ func TestEnsureKubevirtDatamoverRequiredSpecs(t *testing.T) { expectedEnvCount: 3, expectError: false, }, + { + name: "Should update --max-concurrent-data-movers arg on existing container", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dpa", + Namespace: "test-namespace", + ResourceVersion: "12345", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{}, + KubevirtDatamover: &oadpv1alpha1.KubevirtDatamoverConfig{ + MaxConcurrentDataMovers: ptr.To(int32(10)), + }, + }, + }, + }, + existingContainers: []corev1.Container{{ + Name: "manager", + Image: "old", + Args: []string{"--leader-elect", "--max-concurrent-data-movers=2", "--old-arg"}, + }}, + expectedEnvCount: 3, + expectError: false, + }, { name: "Should update --stale-dataupload-threshold arg on existing container", dpa: &oadpv1alpha1.DataProtectionApplication{ @@ -953,6 +1051,35 @@ func TestEnsureKubevirtDatamoverRequiredSpecs(t *testing.T) { } } + // Verify --max-concurrent-data-movers arg + if tt.dpa.Spec.Configuration != nil && tt.dpa.Spec.Configuration.KubevirtDatamover != nil && + tt.dpa.Spec.Configuration.KubevirtDatamover.MaxConcurrentDataMovers != nil { + expectedArg := fmt.Sprintf("--max-concurrent-data-movers=%d", + *tt.dpa.Spec.Configuration.KubevirtDatamover.MaxConcurrentDataMovers) + hasArg := false + maxConcurrentArgCount := 0 + for _, arg := range container.Args { + if strings.HasPrefix(arg, "--max-concurrent-data-movers=") { + maxConcurrentArgCount++ + } + if arg == expectedArg { + hasArg = true + } + } + if !hasArg { + t.Errorf("expected arg %s in container args %v", expectedArg, container.Args) + } + if maxConcurrentArgCount != 1 { + t.Errorf("expected exactly one --max-concurrent-data-movers arg, got %d in %v", maxConcurrentArgCount, container.Args) + } + } else { + for _, arg := range container.Args { + if strings.Contains(arg, "--max-concurrent-data-movers") { + t.Errorf("unexpected --max-concurrent-data-movers arg found: %s", arg) + } + } + } + // Verify --stale-dataupload-threshold arg if tt.dpa.Spec.Configuration != nil && tt.dpa.Spec.Configuration.KubevirtDatamover != nil && tt.dpa.Spec.Configuration.KubevirtDatamover.StaleDataUploadThreshold != nil { diff --git a/tests/e2e/lib/backup.go b/tests/e2e/lib/backup.go index 1c931a372d8..98d7bff8f42 100755 --- a/tests/e2e/lib/backup.go +++ b/tests/e2e/lib/backup.go @@ -8,6 +8,7 @@ import ( "time" velero "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" pkgbackup "github.com/vmware-tanzu/velero/pkg/backup" "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" @@ -132,6 +133,44 @@ func CreateBackupWithVolumePolicy(ocClient client.Client, veleroNamespace, backu return ocClient.Create(context.Background(), &backup) } +const ( + // annotationDataUploadName and annotationExpectedBackupType mirror constants from + // migtools/kubevirt-datamover-controller pkg/common/constants.go (AnnotationDataUploadName, + // AnnotationExpectedBackupType). Not imported directly — that module isn't otherwise a + // dependency of oadp-operator, and pulling it in just for two string constants isn't + // worth the cross-repo coupling. + annotationDataUploadName = "velero.io/dataupload-name" + annotationExpectedBackupType = "kubevirt-datamover.io/expected-backup-type" +) + +// GetDataUploadForBackup returns the name and expected-backup-type annotation +// ("full"/"incremental") of the single DataUpload created for a kubevirt-datamover backup. +// Assumes exactly one DataUpload per backup (true for a single-disk VM). +func GetDataUploadForBackup(ocClient client.Client, veleroNamespace, backupName string) (dataUploadName, expectedType string, err error) { + list := velerov2alpha1.DataUploadList{} + err = ocClient.List(context.Background(), &list, client.InNamespace(veleroNamespace), client.MatchingLabels{velero.BackupNameLabel: backupName}) + if err != nil { + return "", "", fmt.Errorf("failed to list DataUploads for backup %s: %w", backupName, err) + } + if len(list.Items) != 1 { + return "", "", fmt.Errorf("expected exactly 1 DataUpload for backup %s in %s, found %d", backupName, veleroNamespace, len(list.Items)) + } + du := list.Items[0] + return du.Name, du.Annotations[annotationExpectedBackupType], nil +} + +// ListDataUploadsForBackup returns every DataUpload created for a kubevirt-datamover +// backup, unlike GetDataUploadForBackup which assumes exactly one -- a multi-disk VM +// backup creates one DataUpload per disk, all sharing the same backup-name label. +func ListDataUploadsForBackup(ocClient client.Client, veleroNamespace, backupName string) ([]velerov2alpha1.DataUpload, error) { + list := velerov2alpha1.DataUploadList{} + err := ocClient.List(context.Background(), &list, client.InNamespace(veleroNamespace), client.MatchingLabels{velero.BackupNameLabel: backupName}) + if err != nil { + return nil, fmt.Errorf("failed to list DataUploads for backup %s: %w", backupName, err) + } + return list.Items, nil +} + func GetBackup(c client.Client, namespace string, name string) (*velero.Backup, error) { backup := velero.Backup{} err := c.Get(context.Background(), client.ObjectKey{ diff --git a/tests/e2e/lib/k8s_common_helpers.go b/tests/e2e/lib/k8s_common_helpers.go index 9aba5778df5..97b9b72140c 100755 --- a/tests/e2e/lib/k8s_common_helpers.go +++ b/tests/e2e/lib/k8s_common_helpers.go @@ -93,29 +93,18 @@ func DeleteSecret(clientset *kubernetes.Clientset, namespace string, credSecretR return err } -// ExecuteCommandInPodsSh executes a command in a Kubernetes pod using the provided parameters. -// -// Parameters: -// - params: ProxyPodParameters - Parameters specifying Connection to the Kubernetes, the pod, namespace, and container details. -// - command: string - The command to be executed in the specified pod. -// -// Returns: -// - string: Standard output of the executed command. -// - string: Standard error output of the executed command. -// - error: An error, if any, that occurred during the execution of the command. -// -// The function logs relevant information, such as the provided command, the pod name, container name, -// and the full command URL before initiating the command execution. It streams the command's standard -// output and error output, logging them if available. In case of errors, it returns an error message -// with details about the issue. -func ExecuteCommandInPodsSh(params ProxyPodParameters, command string) (string, string, error) { - +// execArgsInPod runs an argv-style command (no shell involved) inside the given +// pod/container via the exec subresource, returning combined stdout/stderr. +// Shared by ExecuteCommandInPodsSh (naive space-split argv, kept for existing +// callers whose args never contain spaces) and ExecuteShellCommandInPod (proper +// "sh -c