Convert DownloadRequest resource/controller to kubebuilder (#3004)
* Migrate DownloadRequest types to kubebuilder Signed-off-by: Carlisia <carlisia@vmware.com> * Migrate controller to kubebuilder Signed-off-by: Carlisia <carlisia@vmware.com> * Migrate download request cli to kubebuilder Signed-off-by: Carlisia <carlisia@vmware.com> * Format w make update Signed-off-by: Carlisia <carlisia@vmware.com> * Remove download file Signed-off-by: Carlisia <carlisia@vmware.com> * Remove kubebuilder from backup/restore apis Signed-off-by: Carlisia <carlisia@vmware.com> * Fix test description Signed-off-by: Carlisia <carlisia@vmware.com> * Import cleanups Signed-off-by: Carlisia <carlisia@vmware.com> * Refactor for controller runtime version update Signed-off-by: Carlisia <carlisia@vmware.com> * Remove year from the copyright Signed-off-by: Carlisia <carlisia@vmware.com> * Check for expiration regardless of phase Signed-off-by: Carlisia <carlisia@vmware.com> * Fix typos and godoc Signed-off-by: Carlisia <carlisia@vmware.com> * Fix test setup and fix a test case Signed-off-by: Carlisia <carlisia@vmware.com>pull/3442/head
parent
c80ad61bbc
commit
11bfe82342
|
@ -32,7 +32,8 @@ spec:
|
|||
singular: downloadrequest
|
||||
preserveUnknownFields: false
|
||||
scope: Namespaced
|
||||
subresources: {}
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: DownloadRequest is a request to download an artifact from backup
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -26,6 +26,26 @@ rules:
|
|||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
- downloadrequests
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
- downloadrequests/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO(2.0) After converting all controllers to runttime-controller,
|
||||
// TODO(2.0) After converting all controllers to runtime-controller,
|
||||
// the functions in this file will no longer be needed and should be removed.
|
||||
package managercontroller
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ type BackupStorageLocationStatus struct {
|
|||
AccessMode BackupStorageLocationAccessMode `json:"accessMode,omitempty"`
|
||||
}
|
||||
|
||||
// TODO(2.0) After converting all resources to use the runttime-controller client,
|
||||
// TODO(2.0) After converting all resources to use the runtime-controller client,
|
||||
// the genclient and k8s:deepcopy markers will no longer be needed and should be removed.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
@ -116,7 +116,7 @@ type BackupStorageLocation struct {
|
|||
Status BackupStorageLocationStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// TODO(2.0) After converting all resources to use the runttime-controller client,
|
||||
// TODO(2.0) After converting all resources to use the runtime-controller client,
|
||||
// the k8s:deepcopy marker will no longer be needed and should be removed.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -77,8 +77,14 @@ type DownloadRequestStatus struct {
|
|||
Expiration *metav1.Time `json:"expiration,omitempty"`
|
||||
}
|
||||
|
||||
// TODO(2.0) After converting all resources to use the runtime-controller client,
|
||||
// the k8s:deepcopy marker will no longer be needed and should be removed.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:object:generate=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.phase",description="DownloadRequest status such as New/Processed"
|
||||
// +kubebuilder:printcolumn:name="Target Name",type="string",JSONPath=".spec.target.name",description="Name of the associated Kubernetes resource"
|
||||
// +kubebuilder:printcolumn:name="Target Kind",type="string",JSONPath=".spec.target.kind",description="Type of file to download"
|
||||
|
@ -99,7 +105,12 @@ type DownloadRequest struct {
|
|||
Status DownloadRequestStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// TODO(2.0) After converting all resources to use the runtime-controller client,
|
||||
// the k8s:deepcopy marker will no longer be needed and should be removed.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=downloadrequests,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=downloadrequests/status,verbs=get;update;patch
|
||||
|
||||
// DownloadRequestList is a list of DownloadRequests.
|
||||
type DownloadRequestList struct {
|
|
@ -20,7 +20,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TODO(2.0) After converting all resources to use the runttime-controller client,
|
||||
// TODO(2.0) After converting all resources to use the runtime-controller client,
|
||||
// the genclient and k8s:deepcopy markers will no longer be needed and should be removed.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
@ -89,7 +89,7 @@ type ServerStatusRequestStatus struct {
|
|||
Plugins []PluginInfo `json:"plugins,omitempty"`
|
||||
}
|
||||
|
||||
// TODO(2.0) After converting all resources to use the runttime-controller client,
|
||||
// TODO(2.0) After converting all resources to use the runtime-controller client,
|
||||
// the k8s:deepcopy marker will no longer be needed and should be removed.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 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 builder
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
// DownloadRequestBuilder builds DownloadRequest objects.
|
||||
type DownloadRequestBuilder struct {
|
||||
object *velerov1api.DownloadRequest
|
||||
}
|
||||
|
||||
// ForDownloadRequest is the constructor for a DownloadRequestBuilder.
|
||||
func ForDownloadRequest(ns, name string) *DownloadRequestBuilder {
|
||||
return &DownloadRequestBuilder{
|
||||
object: &velerov1api.DownloadRequest{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: velerov1api.SchemeGroupVersion.String(),
|
||||
Kind: "DownloadRequest",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Result returns the built DownloadRequest.
|
||||
func (b *DownloadRequestBuilder) Result() *velerov1api.DownloadRequest {
|
||||
return b.object
|
||||
}
|
||||
|
||||
// Phase sets the DownloadRequest's status phase.
|
||||
func (b *DownloadRequestBuilder) Phase(phase velerov1api.DownloadRequestPhase) *DownloadRequestBuilder {
|
||||
b.object.Status.Phase = phase
|
||||
return b
|
||||
}
|
||||
|
||||
// Target sets the DownloadRequest's target kind and target name.
|
||||
func (b *DownloadRequestBuilder) Target(targetKind velerov1api.DownloadTargetKind, targetName string) *DownloadRequestBuilder {
|
||||
b.object.Spec.Target.Kind = targetKind
|
||||
b.object.Spec.Target.Name = targetName
|
||||
return b
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -56,6 +56,9 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
|||
veleroClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var backups *velerov1api.BackupList
|
||||
if len(args) > 0 {
|
||||
backups = new(velerov1api.BackupList)
|
||||
|
@ -99,7 +102,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, vscList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
s := output.DescribeBackup(context.Background(), kbClient, &backup, deleteRequestList.Items, podVolumeBackupList.Items, vscList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
if first {
|
||||
first = false
|
||||
fmt.Print(s)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
|
||||
|
@ -115,7 +115,7 @@ func (o *DownloadOptions) Complete(args []string) error {
|
|||
}
|
||||
|
||||
func (o *DownloadOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
veleroClient, err := f.Client()
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
backupDest, err := os.OpenFile(o.Output, o.writeOptions, 0600)
|
||||
|
@ -124,7 +124,7 @@ func (o *DownloadOptions) Run(c *cobra.Command, f client.Factory) error {
|
|||
}
|
||||
defer backupDest.Close()
|
||||
|
||||
err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), o.Name, v1.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify, o.caCertFile)
|
||||
err = downloadrequest.Stream(context.Background(), kbClient, f.Namespace(), o.Name, velerov1api.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify, o.caCertFile)
|
||||
if err != nil {
|
||||
os.Remove(o.Output)
|
||||
cmd.CheckError(err)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -26,7 +26,7 @@ import (
|
|||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
|
||||
|
@ -52,6 +52,9 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
|
|||
veleroClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
backup, err := veleroClient.VeleroV1().Backups(f.Namespace()).Get(context.TODO(), backupName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
cmd.Exit("Backup %q does not exist.", backupName)
|
||||
|
@ -60,14 +63,14 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
|
|||
}
|
||||
|
||||
switch backup.Status.Phase {
|
||||
case v1.BackupPhaseCompleted, v1.BackupPhasePartiallyFailed, v1.BackupPhaseFailed:
|
||||
case velerov1api.BackupPhaseCompleted, velerov1api.BackupPhasePartiallyFailed, velerov1api.BackupPhaseFailed:
|
||||
// terminal phases, do nothing.
|
||||
default:
|
||||
cmd.Exit("Logs for backup %q are not available until it's finished processing. Please wait "+
|
||||
"until the backup has a phase of Completed or Failed and try again.", backupName)
|
||||
}
|
||||
|
||||
err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), backupName, v1.DownloadTargetKindBackupLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
|
||||
err = downloadrequest.Stream(context.Background(), kbClient, f.Namespace(), backupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -24,7 +24,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
|
||||
|
@ -51,9 +51,12 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
|||
veleroClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var restores *api.RestoreList
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var restores *velerov1api.RestoreList
|
||||
if len(args) > 0 {
|
||||
restores = new(api.RestoreList)
|
||||
restores = new(velerov1api.RestoreList)
|
||||
for _, name := range args {
|
||||
restore, err := veleroClient.VeleroV1().Restores(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
cmd.CheckError(err)
|
||||
|
@ -72,7 +75,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
|||
fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err)
|
||||
}
|
||||
|
||||
s := output.DescribeRestore(&restore, podvolumeRestoreList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
s := output.DescribeRestore(context.Background(), kbClient, &restore, podvolumeRestoreList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
if first {
|
||||
first = false
|
||||
fmt.Print(s)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -26,7 +26,7 @@ import (
|
|||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
|
||||
|
@ -52,6 +52,9 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
|
|||
veleroClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
restore, err := veleroClient.VeleroV1().Restores(f.Namespace()).Get(context.TODO(), restoreName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
cmd.Exit("Restore %q does not exist.", restoreName)
|
||||
|
@ -60,14 +63,14 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
|
|||
}
|
||||
|
||||
switch restore.Status.Phase {
|
||||
case v1.RestorePhaseCompleted, v1.RestorePhaseFailed, v1.RestorePhasePartiallyFailed:
|
||||
case velerov1api.RestorePhaseCompleted, velerov1api.RestorePhaseFailed, velerov1api.RestorePhasePartiallyFailed:
|
||||
// terminal phases, don't exit.
|
||||
default:
|
||||
cmd.Exit("Logs for restore %q are not available until it's finished processing. Please wait "+
|
||||
"until the restore has a phase of Completed or Failed and try again.", restoreName)
|
||||
}
|
||||
|
||||
err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), restoreName, v1.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
|
||||
err = downloadrequest.Stream(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -731,24 +731,6 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
}
|
||||
}
|
||||
|
||||
downloadrequestControllerRunInfo := func() controllerRunInfo {
|
||||
downloadRequestController := controller.NewDownloadRequestController(
|
||||
s.veleroClient.VeleroV1(),
|
||||
s.sharedInformerFactory.Velero().V1().DownloadRequests(),
|
||||
s.sharedInformerFactory.Velero().V1().Restores().Lister(),
|
||||
s.mgr.GetClient(),
|
||||
s.sharedInformerFactory.Velero().V1().Backups().Lister(),
|
||||
newPluginManager,
|
||||
persistence.NewObjectBackupStoreGetter(),
|
||||
s.logger,
|
||||
)
|
||||
|
||||
return controllerRunInfo{
|
||||
controller: downloadRequestController,
|
||||
numWorkers: defaultControllerWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
enabledControllers := map[string]func() controllerRunInfo{
|
||||
controller.BackupSync: backupSyncControllerRunInfo,
|
||||
controller.Backup: backupControllerRunInfo,
|
||||
|
@ -757,12 +739,11 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
controller.BackupDeletion: deletionControllerRunInfo,
|
||||
controller.Restore: restoreControllerRunInfo,
|
||||
controller.ResticRepo: resticRepoControllerRunInfo,
|
||||
controller.DownloadRequest: downloadrequestControllerRunInfo,
|
||||
}
|
||||
// Note: all runtime type controllers that can be disabled are grouped separately, below:
|
||||
enabledRuntimeControllers := map[string]struct{}{
|
||||
controller.ServerStatusRequest: {},
|
||||
}
|
||||
enabledRuntimeControllers := make(map[string]struct{})
|
||||
enabledRuntimeControllers[controller.ServerStatusRequest] = struct{}{}
|
||||
enabledRuntimeControllers[controller.DownloadRequest] = struct{}{}
|
||||
|
||||
if s.config.restoreOnly {
|
||||
s.logger.Info("Restore only mode - not starting the backup, schedule, delete-backup, or GC controllers")
|
||||
|
@ -839,6 +820,20 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
}
|
||||
}
|
||||
|
||||
if _, ok := enabledRuntimeControllers[controller.DownloadRequest]; ok {
|
||||
r := controller.DownloadRequestReconciler{
|
||||
Scheme: s.mgr.GetScheme(),
|
||||
Client: s.mgr.GetClient(),
|
||||
Clock: clock.RealClock{},
|
||||
NewPluginManager: newPluginManager,
|
||||
BackupStoreGetter: persistence.NewObjectBackupStoreGetter(),
|
||||
Log: s.logger,
|
||||
}
|
||||
if err := r.SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.Fatal(err, "unable to create controller", "controller", controller.DownloadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
|
||||
// by v2.0, the block from this line and including the `s.mgr.Start() will be
|
||||
// deprecated, since the manager auto-starts all the caches. Until then, we need to start the
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -29,74 +29,50 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
)
|
||||
|
||||
// ErrNotFound is exported for external packages to check for when a file is
|
||||
// not found
|
||||
var ErrNotFound = errors.New("file not found")
|
||||
|
||||
func Stream(client velerov1client.DownloadRequestsGetter, namespace, name string, kind v1.DownloadTargetKind, w io.Writer, timeout time.Duration, insecureSkipTLSVerify bool, caCertFile string) error {
|
||||
req := &v1.DownloadRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: fmt.Sprintf("%s-%s", name, time.Now().Format("20060102150405")),
|
||||
},
|
||||
Spec: v1.DownloadRequestSpec{
|
||||
Target: v1.DownloadTarget{
|
||||
Kind: kind,
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
func Stream(ctx context.Context, kbClient kbclient.Client, namespace, name string, kind velerov1api.DownloadTargetKind, w io.Writer, timeout time.Duration, insecureSkipTLSVerify bool, caCertFile string) error {
|
||||
reqName := fmt.Sprintf("%s-%s", name, time.Now().Format("20060102150405"))
|
||||
created := builder.ForDownloadRequest(namespace, reqName).Target(kind, name).Result()
|
||||
|
||||
req, err := client.DownloadRequests(namespace).Create(context.TODO(), req, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
if err := kbClient.Create(context.Background(), created, &kbclient.CreateOptions{}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer client.DownloadRequests(namespace).Delete(context.TODO(), req.Name, metav1.DeleteOptions{})
|
||||
|
||||
listOptions := metav1.ListOptions{
|
||||
FieldSelector: "metadata.name=" + req.Name,
|
||||
ResourceVersion: req.ResourceVersion,
|
||||
}
|
||||
watcher, err := client.DownloadRequests(namespace).Watch(context.TODO(), listOptions)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer watcher.Stop()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
expired := time.NewTimer(timeout)
|
||||
defer expired.Stop()
|
||||
key := kbclient.ObjectKey{Name: created.Name, Namespace: namespace}
|
||||
checkFunc := func() {
|
||||
updated := &velerov1api.DownloadRequest{}
|
||||
if err := kbClient.Get(ctx, key, updated); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-expired.C:
|
||||
return errors.New("timed out waiting for download URL")
|
||||
case e := <-watcher.ResultChan():
|
||||
updated, ok := e.Object.(*v1.DownloadRequest)
|
||||
if !ok {
|
||||
return errors.Errorf("unexpected type %T", e.Object)
|
||||
}
|
||||
// TODO: once the minimum supported Kubernetes version is v1.9.0, remove the following check.
|
||||
// See http://issue.k8s.io/51046 for details.
|
||||
if updated.Name != created.Name {
|
||||
return
|
||||
}
|
||||
|
||||
switch e.Type {
|
||||
case watch.Deleted:
|
||||
errors.New("download request was unexpectedly deleted")
|
||||
case watch.Modified:
|
||||
if updated.Status.DownloadURL != "" {
|
||||
req = updated
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
if updated.Status.DownloadURL != "" {
|
||||
created = updated
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
if req.Status.DownloadURL == "" {
|
||||
wait.Until(checkFunc, 25*time.Millisecond, ctx.Done())
|
||||
|
||||
if created.Status.DownloadURL == "" {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
|
@ -134,7 +110,7 @@ Loop:
|
|||
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("GET", req.Status.DownloadURL, nil)
|
||||
httpReq, err := http.NewRequest("GET", created.Status.DownloadURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -164,7 +140,7 @@ Loop:
|
|||
}
|
||||
|
||||
reader := resp.Body
|
||||
if kind != v1.DownloadTargetKindBackupContents {
|
||||
if kind != velerov1api.DownloadTargetKindBackupContents {
|
||||
// need to decompress logs
|
||||
gzipReader, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 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 downloadrequest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
core "k8s.io/client-go/testing"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kind v1.DownloadTargetKind
|
||||
timeout time.Duration
|
||||
createError error
|
||||
watchError error
|
||||
watchAdds []runtime.Object
|
||||
watchModifies []runtime.Object
|
||||
watchDeletes []runtime.Object
|
||||
updateWithURL bool
|
||||
statusCode int
|
||||
body string
|
||||
deleteError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "error creating req",
|
||||
createError: errors.New("foo"),
|
||||
kind: v1.DownloadTargetKindBackupLog,
|
||||
expectedError: "foo",
|
||||
},
|
||||
{
|
||||
name: "error creating watch",
|
||||
watchError: errors.New("bar"),
|
||||
kind: v1.DownloadTargetKindBackupLog,
|
||||
expectedError: "bar",
|
||||
},
|
||||
{
|
||||
name: "timed out",
|
||||
kind: v1.DownloadTargetKindBackupLog,
|
||||
timeout: time.Millisecond,
|
||||
expectedError: "timed out waiting for download URL",
|
||||
},
|
||||
{
|
||||
name: "unexpected watch type",
|
||||
kind: v1.DownloadTargetKindBackupLog,
|
||||
watchAdds: []runtime.Object{&v1.Backup{}},
|
||||
expectedError: "unexpected type *v1.Backup",
|
||||
},
|
||||
{
|
||||
name: "other requests added/updated/deleted first",
|
||||
kind: v1.DownloadTargetKindBackupLog,
|
||||
watchAdds: []runtime.Object{
|
||||
newDownloadRequest("foo").DownloadRequest,
|
||||
},
|
||||
watchModifies: []runtime.Object{
|
||||
newDownloadRequest("foo").DownloadRequest,
|
||||
},
|
||||
watchDeletes: []runtime.Object{
|
||||
newDownloadRequest("foo").DownloadRequest,
|
||||
},
|
||||
updateWithURL: true,
|
||||
statusCode: http.StatusOK,
|
||||
body: "download body",
|
||||
},
|
||||
{
|
||||
name: "http error",
|
||||
kind: v1.DownloadTargetKindBackupLog,
|
||||
updateWithURL: true,
|
||||
statusCode: http.StatusInternalServerError,
|
||||
body: "some error",
|
||||
expectedError: "request failed: some error",
|
||||
},
|
||||
}
|
||||
|
||||
const testTimeout = 30 * time.Second
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
client := fake.NewSimpleClientset()
|
||||
|
||||
created := make(chan *v1.DownloadRequest, 1)
|
||||
client.PrependReactor("create", "downloadrequests", func(action core.Action) (bool, runtime.Object, error) {
|
||||
createAction := action.(core.CreateAction)
|
||||
created <- createAction.GetObject().(*v1.DownloadRequest)
|
||||
return true, createAction.GetObject(), test.createError
|
||||
})
|
||||
|
||||
fakeWatch := watch.NewFake()
|
||||
client.PrependWatchReactor("downloadrequests", core.DefaultWatchReactor(fakeWatch, test.watchError))
|
||||
|
||||
deleted := make(chan string, 1)
|
||||
client.PrependReactor("delete", "downloadrequests", func(action core.Action) (bool, runtime.Object, error) {
|
||||
deleteAction := action.(core.DeleteAction)
|
||||
deleted <- deleteAction.GetName()
|
||||
return true, nil, test.deleteError
|
||||
})
|
||||
|
||||
timeout := test.timeout
|
||||
if timeout == 0 {
|
||||
timeout = testTimeout
|
||||
}
|
||||
|
||||
var server *httptest.Server
|
||||
var url string
|
||||
if test.updateWithURL {
|
||||
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(test.statusCode)
|
||||
if test.statusCode == http.StatusOK {
|
||||
gzipWriter := gzip.NewWriter(w)
|
||||
fmt.Fprintf(gzipWriter, test.body)
|
||||
gzipWriter.Close()
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, test.body)
|
||||
}))
|
||||
defer server.Close()
|
||||
url = server.URL
|
||||
}
|
||||
|
||||
output := new(bytes.Buffer)
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
err := Stream(client.VeleroV1(), "namespace", "name", test.kind, output, timeout, false, "")
|
||||
errCh <- err
|
||||
}()
|
||||
|
||||
for i := range test.watchAdds {
|
||||
fakeWatch.Add(test.watchAdds[i])
|
||||
}
|
||||
for i := range test.watchModifies {
|
||||
fakeWatch.Modify(test.watchModifies[i])
|
||||
}
|
||||
for i := range test.watchDeletes {
|
||||
fakeWatch.Delete(test.watchDeletes[i])
|
||||
}
|
||||
|
||||
var createdName string
|
||||
if test.updateWithURL {
|
||||
select {
|
||||
case r := <-created:
|
||||
createdName = r.Name
|
||||
r.Status.DownloadURL = url
|
||||
fakeWatch.Modify(r)
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatalf("created object not received")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var err error
|
||||
select {
|
||||
case err = <-errCh:
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatal("test timed out")
|
||||
}
|
||||
|
||||
if test.expectedError != "" {
|
||||
require.EqualError(t, err, test.expectedError)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.statusCode != http.StatusOK {
|
||||
assert.EqualError(t, err, "request failed: "+test.body)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, test.body, output.String())
|
||||
|
||||
select {
|
||||
case name := <-deleted:
|
||||
assert.Equal(t, createdName, name)
|
||||
default:
|
||||
t.Fatal("download request was not deleted")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type downloadRequest struct {
|
||||
*v1.DownloadRequest
|
||||
}
|
||||
|
||||
func newDownloadRequest(name string) *downloadRequest {
|
||||
return &downloadRequest{
|
||||
DownloadRequest: &v1.DownloadRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: v1.DefaultNamespace,
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2021 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -18,6 +18,7 @@ package output
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
@ -28,6 +29,7 @@ import (
|
|||
snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1beta1"
|
||||
|
||||
"github.com/fatih/color"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
|
||||
|
@ -38,6 +40,8 @@ import (
|
|||
|
||||
// DescribeBackup describes a backup in human-readable format.
|
||||
func DescribeBackup(
|
||||
ctx context.Context,
|
||||
kbClient kbclient.Client,
|
||||
backup *velerov1api.Backup,
|
||||
deleteRequests []velerov1api.DeleteBackupRequest,
|
||||
podVolumeBackups []velerov1api.PodVolumeBackup,
|
||||
|
@ -90,7 +94,7 @@ func DescribeBackup(
|
|||
DescribeBackupSpec(d, backup.Spec)
|
||||
|
||||
d.Println()
|
||||
DescribeBackupStatus(d, backup, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
DescribeBackupStatus(ctx, kbClient, d, backup, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
|
||||
if len(deleteRequests) > 0 {
|
||||
d.Println()
|
||||
|
@ -241,7 +245,7 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) {
|
|||
}
|
||||
|
||||
// DescribeBackupStatus describes a backup status in human-readable format.
|
||||
func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
func DescribeBackupStatus(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
status := backup.Status
|
||||
|
||||
// Status.Version has been deprecated, use Status.FormatVersion
|
||||
|
@ -280,7 +284,7 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool
|
|||
}
|
||||
|
||||
if details {
|
||||
describeBackupResourceList(d, backup, veleroClient, insecureSkipTLSVerify, caCertPath)
|
||||
describeBackupResourceList(ctx, kbClient, d, backup, insecureSkipTLSVerify, caCertPath)
|
||||
d.Println()
|
||||
}
|
||||
|
||||
|
@ -291,7 +295,7 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool
|
|||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
|
||||
if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
|
||||
d.Printf("Velero-Native Snapshots:\t<error getting snapshot info: %v>\n", err)
|
||||
return
|
||||
}
|
||||
|
@ -312,9 +316,9 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool
|
|||
d.Printf("Velero-Native Snapshots: <none included>\n")
|
||||
}
|
||||
|
||||
func describeBackupResourceList(d *Describer, backup *velerov1api.Backup, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
func describeBackupResourceList(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
|
||||
if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
|
||||
if err == downloadrequest.ErrNotFound {
|
||||
// the backup resource list could be missing if (other reasons may exist as well):
|
||||
// - the backup was taken prior to v1.1; or
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017, 2019 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -18,40 +18,42 @@ package output
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
|
||||
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
|
||||
pkgrestore "github.com/vmware-tanzu/velero/pkg/restore"
|
||||
)
|
||||
|
||||
func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestore, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertFile string) string {
|
||||
func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *velerov1api.Restore, podVolumeRestores []velerov1api.PodVolumeRestore, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertFile string) string {
|
||||
return Describe(func(d *Describer) {
|
||||
d.DescribeMetadata(restore.ObjectMeta)
|
||||
|
||||
d.Println()
|
||||
phase := restore.Status.Phase
|
||||
if phase == "" {
|
||||
phase = v1.RestorePhaseNew
|
||||
phase = velerov1api.RestorePhaseNew
|
||||
}
|
||||
phaseString := string(phase)
|
||||
switch phase {
|
||||
case v1.RestorePhaseCompleted:
|
||||
case velerov1api.RestorePhaseCompleted:
|
||||
phaseString = color.GreenString(phaseString)
|
||||
case v1.RestorePhaseFailedValidation, v1.RestorePhasePartiallyFailed, v1.RestorePhaseFailed:
|
||||
case velerov1api.RestorePhaseFailedValidation, velerov1api.RestorePhasePartiallyFailed, velerov1api.RestorePhaseFailed:
|
||||
phaseString = color.RedString(phaseString)
|
||||
}
|
||||
|
||||
resultsNote := ""
|
||||
if phase == v1.RestorePhaseFailed || phase == v1.RestorePhasePartiallyFailed {
|
||||
if phase == velerov1api.RestorePhaseFailed || phase == velerov1api.RestorePhasePartiallyFailed {
|
||||
resultsNote = fmt.Sprintf(" (run 'velero restore logs %s' for more information)", restore.Name)
|
||||
}
|
||||
|
||||
|
@ -79,7 +81,7 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor
|
|||
}
|
||||
}
|
||||
|
||||
describeRestoreResults(d, restore, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
describeRestoreResults(ctx, kbClient, d, restore, insecureSkipTLSVerify, caCertFile)
|
||||
|
||||
d.Println()
|
||||
d.Printf("Backup:\t%s\n", restore.Spec.BackupName)
|
||||
|
@ -143,7 +145,7 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor
|
|||
})
|
||||
}
|
||||
|
||||
func describeRestoreResults(d *Describer, restore *v1.Restore, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
func describeRestoreResults(ctx context.Context, kbClient kbclient.Client, d *Describer, restore *velerov1api.Restore, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
if restore.Status.Warnings == 0 && restore.Status.Errors == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -151,7 +153,7 @@ func describeRestoreResults(d *Describer, restore *v1.Restore, veleroClient clie
|
|||
var buf bytes.Buffer
|
||||
var resultMap map[string]pkgrestore.Result
|
||||
|
||||
if err := downloadrequest.Stream(veleroClient.VeleroV1(), restore.Namespace, restore.Name, v1.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
|
||||
if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
|
||||
d.Printf("Warnings:\t<error getting warnings: %v>\n\nErrors:\t<error getting errors: %v>\n", err, err)
|
||||
return
|
||||
}
|
||||
|
@ -186,7 +188,7 @@ func describeRestoreResult(d *Describer, name string, result pkgrestore.Result)
|
|||
}
|
||||
|
||||
// describePodVolumeRestores describes pod volume restores in human-readable format.
|
||||
func describePodVolumeRestores(d *Describer, restores []v1.PodVolumeRestore, details bool) {
|
||||
func describePodVolumeRestores(d *Describer, restores []velerov1api.PodVolumeRestore, details bool) {
|
||||
if details {
|
||||
d.Printf("Restic Restores:\n")
|
||||
} else {
|
||||
|
@ -198,10 +200,10 @@ func describePodVolumeRestores(d *Describer, restores []v1.PodVolumeRestore, det
|
|||
|
||||
// go through phases in a specific order
|
||||
for _, phase := range []string{
|
||||
string(v1.PodVolumeRestorePhaseCompleted),
|
||||
string(v1.PodVolumeRestorePhaseFailed),
|
||||
string(velerov1api.PodVolumeRestorePhaseCompleted),
|
||||
string(velerov1api.PodVolumeRestorePhaseFailed),
|
||||
"In Progress",
|
||||
string(v1.PodVolumeRestorePhaseNew),
|
||||
string(velerov1api.PodVolumeRestorePhaseNew),
|
||||
} {
|
||||
if len(restoresByPhase[phase]) == 0 {
|
||||
continue
|
||||
|
@ -230,15 +232,15 @@ func describePodVolumeRestores(d *Describer, restores []v1.PodVolumeRestore, det
|
|||
}
|
||||
}
|
||||
|
||||
func groupRestoresByPhase(restores []v1.PodVolumeRestore) map[string][]v1.PodVolumeRestore {
|
||||
restoresByPhase := make(map[string][]v1.PodVolumeRestore)
|
||||
func groupRestoresByPhase(restores []velerov1api.PodVolumeRestore) map[string][]velerov1api.PodVolumeRestore {
|
||||
restoresByPhase := make(map[string][]velerov1api.PodVolumeRestore)
|
||||
|
||||
phaseToGroup := map[v1.PodVolumeRestorePhase]string{
|
||||
v1.PodVolumeRestorePhaseCompleted: string(v1.PodVolumeRestorePhaseCompleted),
|
||||
v1.PodVolumeRestorePhaseFailed: string(v1.PodVolumeRestorePhaseFailed),
|
||||
v1.PodVolumeRestorePhaseInProgress: "In Progress",
|
||||
v1.PodVolumeRestorePhaseNew: string(v1.PodVolumeRestorePhaseNew),
|
||||
"": string(v1.PodVolumeRestorePhaseNew),
|
||||
phaseToGroup := map[velerov1api.PodVolumeRestorePhase]string{
|
||||
velerov1api.PodVolumeRestorePhaseCompleted: string(velerov1api.PodVolumeRestorePhaseCompleted),
|
||||
velerov1api.PodVolumeRestorePhaseFailed: string(velerov1api.PodVolumeRestorePhaseFailed),
|
||||
velerov1api.PodVolumeRestorePhaseInProgress: "In Progress",
|
||||
velerov1api.PodVolumeRestorePhaseNew: string(velerov1api.PodVolumeRestorePhaseNew),
|
||||
"": string(velerov1api.PodVolumeRestorePhaseNew),
|
||||
}
|
||||
|
||||
for _, restore := range restores {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -60,7 +60,7 @@ func (r *BackupStorageLocationReconciler) Reconcile(ctx context.Context, req ctr
|
|||
locationList, err := storage.ListBackupStorageLocations(r.Ctx, r.Client, req.Namespace)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("No backup storage locations found, at least one is required")
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
pluginManager := r.NewPluginManager(log)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -18,230 +18,154 @@ package controller
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
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/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/cluster-api/util/patch"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
|
||||
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
type downloadRequestController struct {
|
||||
*genericController
|
||||
// DownloadRequestReconciler reconciles a DownloadRequest object
|
||||
type DownloadRequestReconciler struct {
|
||||
Scheme *runtime.Scheme
|
||||
Client kbclient.Client
|
||||
Clock clock.Clock
|
||||
// use variables to refer to these functions so they can be
|
||||
// replaced with fakes for testing.
|
||||
NewPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
BackupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
|
||||
downloadRequestClient velerov1client.DownloadRequestsGetter
|
||||
downloadRequestLister velerov1listers.DownloadRequestLister
|
||||
restoreLister velerov1listers.RestoreLister
|
||||
clock clock.Clock
|
||||
kbClient client.Client
|
||||
backupLister velerov1listers.BackupLister
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
Log logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewDownloadRequestController creates a new DownloadRequestController.
|
||||
func NewDownloadRequestController(
|
||||
downloadRequestClient velerov1client.DownloadRequestsGetter,
|
||||
downloadRequestInformer velerov1informers.DownloadRequestInformer,
|
||||
restoreLister velerov1listers.RestoreLister,
|
||||
kbClient client.Client,
|
||||
backupLister velerov1listers.BackupLister,
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter,
|
||||
logger logrus.FieldLogger,
|
||||
) Interface {
|
||||
c := &downloadRequestController{
|
||||
genericController: newGenericController(DownloadRequest, logger),
|
||||
downloadRequestClient: downloadRequestClient,
|
||||
downloadRequestLister: downloadRequestInformer.Lister(),
|
||||
restoreLister: restoreLister,
|
||||
kbClient: kbClient,
|
||||
backupLister: backupLister,
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=downloadrequests,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=downloadrequests/status,verbs=get;update;patch
|
||||
func (r *DownloadRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.Log.WithFields(logrus.Fields{
|
||||
"controller": "download-request",
|
||||
"downloadRequest": req.NamespacedName,
|
||||
})
|
||||
|
||||
// use variables to refer to these functions so they can be
|
||||
// replaced with fakes for testing.
|
||||
newPluginManager: newPluginManager,
|
||||
backupStoreGetter: backupStoreGetter,
|
||||
|
||||
clock: &clock.RealClock{},
|
||||
}
|
||||
|
||||
c.syncHandler = c.processDownloadRequest
|
||||
|
||||
downloadRequestInformer.Informer().AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
downloadRequest := obj.(*velerov1api.DownloadRequest)
|
||||
c.logger.WithError(errors.WithStack(err)).
|
||||
WithField(DownloadRequest, downloadRequest.Name).
|
||||
Error("Error creating queue key, item not added to queue")
|
||||
return
|
||||
}
|
||||
c.queue.Add(key)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// processDownloadRequest is the default per-item sync handler. It generates a pre-signed URL for
|
||||
// a new DownloadRequest or deletes the DownloadRequest if it has expired.
|
||||
func (c *downloadRequestController) processDownloadRequest(key string) error {
|
||||
log := c.logger.WithField("key", key)
|
||||
|
||||
log.Debug("Running processDownloadRequest")
|
||||
ns, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("error splitting queue key")
|
||||
return nil
|
||||
}
|
||||
|
||||
downloadRequest, err := c.downloadRequestLister.DownloadRequests(ns).Get(name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.Debug("Unable to find DownloadRequest")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting DownloadRequest")
|
||||
}
|
||||
|
||||
switch downloadRequest.Status.Phase {
|
||||
case "", velerov1api.DownloadRequestPhaseNew:
|
||||
return c.generatePreSignedURL(downloadRequest, log)
|
||||
case velerov1api.DownloadRequestPhaseProcessed:
|
||||
return c.deleteIfExpired(downloadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const signedURLTTL = 10 * time.Minute
|
||||
|
||||
// generatePreSignedURL generates a pre-signed URL for downloadRequest, changes the phase to
|
||||
// Processed, and persists the changes to storage.
|
||||
func (c *downloadRequestController) generatePreSignedURL(downloadRequest *velerov1api.DownloadRequest, log logrus.FieldLogger) error {
|
||||
update := downloadRequest.DeepCopy()
|
||||
|
||||
var (
|
||||
backupName string
|
||||
err error
|
||||
)
|
||||
|
||||
switch downloadRequest.Spec.Target.Kind {
|
||||
case velerov1api.DownloadTargetKindRestoreLog, velerov1api.DownloadTargetKindRestoreResults:
|
||||
restore, err := c.restoreLister.Restores(downloadRequest.Namespace).Get(downloadRequest.Spec.Target.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting Restore")
|
||||
// Fetch the DownloadRequest instance.
|
||||
log.Debug("Getting DownloadRequest")
|
||||
downloadRequest := &velerov1api.DownloadRequest{}
|
||||
if err := r.Client.Get(ctx, req.NamespacedName, downloadRequest); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.Debug("Unable to find DownloadRequest")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
backupName = restore.Spec.BackupName
|
||||
default:
|
||||
backupName = downloadRequest.Spec.Target.Name
|
||||
log.WithError(err).Error("Error getting DownloadRequest")
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
backup, err := c.backupLister.Backups(downloadRequest.Namespace).Get(backupName)
|
||||
// Initialize the patch helper.
|
||||
patchHelper, err := patch.NewHelper(downloadRequest, r.Client)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
log.WithError(err).Error("Error getting a patch helper to update this resource")
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
backupLocation := &velerov1api.BackupStorageLocation{}
|
||||
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
|
||||
Namespace: backup.Namespace,
|
||||
Name: backup.Spec.StorageLocation,
|
||||
}, backupLocation); err != nil {
|
||||
return errors.WithStack(err)
|
||||
defer func() {
|
||||
// Always attempt to Patch the downloadRequest object and status after each reconciliation.
|
||||
if err := patchHelper.Patch(ctx, downloadRequest); err != nil {
|
||||
log.WithError(err).Error("Error updating download request")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if downloadRequest.Status != (velerov1api.DownloadRequestStatus{}) && downloadRequest.Status.Expiration != nil {
|
||||
if downloadRequest.Status.Expiration.Time.Before(r.Clock.Now()) {
|
||||
|
||||
// Delete any request that is expired, regardless of the phase: it is not
|
||||
// worth proceeding and trying/retrying to find it.
|
||||
log.Debug("DownloadRequest has expired - deleting")
|
||||
if err := r.Client.Delete(ctx, downloadRequest); err != nil {
|
||||
log.WithError(err).Error("Error deleting an expired download request")
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
return ctrl.Result{Requeue: false}, nil
|
||||
|
||||
} else if downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseProcessed {
|
||||
|
||||
// Requeue the request if is not yet expired and has already been processed before,
|
||||
// since it might still be in use by the logs streaming and shouldn't
|
||||
// be deleted until after its expiration.
|
||||
log.Debug("DownloadRequest has not yet expired - requeueing")
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
pluginManager := c.newPluginManager(log)
|
||||
defer pluginManager.CleanupClients()
|
||||
// Process a brand new request.
|
||||
backupName := downloadRequest.Spec.Target.Name
|
||||
if downloadRequest.Status.Phase == "" || downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseNew {
|
||||
|
||||
backupStore, err := c.backupStoreGetter.Get(backupLocation, pluginManager, log)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
// Update the expiration.
|
||||
downloadRequest.Status.Expiration = &metav1.Time{Time: r.Clock.Now().Add(persistence.DownloadURLTTL)}
|
||||
|
||||
if update.Status.DownloadURL, err = backupStore.GetDownloadURL(downloadRequest.Spec.Target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
update.Status.Phase = velerov1api.DownloadRequestPhaseProcessed
|
||||
update.Status.Expiration = &metav1.Time{Time: c.clock.Now().Add(persistence.DownloadURLTTL)}
|
||||
|
||||
_, err = patchDownloadRequest(downloadRequest, update, c.downloadRequestClient)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// deleteIfExpired deletes downloadRequest if it has expired.
|
||||
func (c *downloadRequestController) deleteIfExpired(downloadRequest *velerov1api.DownloadRequest) error {
|
||||
log := c.logger.WithField("key", kube.NamespaceAndName(downloadRequest))
|
||||
log.Info("checking for expiration of DownloadRequest")
|
||||
if downloadRequest.Status.Expiration.Time.After(c.clock.Now()) {
|
||||
log.Debug("DownloadRequest has not expired")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("DownloadRequest has expired - deleting")
|
||||
return errors.WithStack(c.downloadRequestClient.DownloadRequests(downloadRequest.Namespace).Delete(context.TODO(), downloadRequest.Name, metav1.DeleteOptions{}))
|
||||
}
|
||||
|
||||
// resync requeues all the DownloadRequests in the lister's cache. This is mostly to handle deleting
|
||||
// any expired requests that were not deleted as part of the normal client flow for whatever reason.
|
||||
func (c *downloadRequestController) resync() {
|
||||
list, err := c.downloadRequestLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
c.logger.WithError(errors.WithStack(err)).Error("error listing download requests")
|
||||
return
|
||||
}
|
||||
|
||||
for _, dr := range list {
|
||||
key, err := cache.MetaNamespaceKeyFunc(dr)
|
||||
if err != nil {
|
||||
c.logger.WithError(errors.WithStack(err)).WithField(DownloadRequest, dr.Name).Error("error generating key for download request")
|
||||
continue
|
||||
if downloadRequest.Spec.Target.Kind == velerov1api.DownloadTargetKindRestoreLog ||
|
||||
downloadRequest.Spec.Target.Kind == velerov1api.DownloadTargetKindRestoreResults {
|
||||
restore := &velerov1api.Restore{}
|
||||
if err := r.Client.Get(ctx, kbclient.ObjectKey{
|
||||
Namespace: downloadRequest.Namespace,
|
||||
Name: downloadRequest.Spec.Target.Name,
|
||||
}, restore); err != nil {
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
backupName = restore.Spec.BackupName
|
||||
}
|
||||
|
||||
c.queue.Add(key)
|
||||
backup := &velerov1api.Backup{}
|
||||
if err := r.Client.Get(ctx, kbclient.ObjectKey{
|
||||
Namespace: downloadRequest.Namespace,
|
||||
Name: backupName,
|
||||
}, backup); err != nil {
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
location := &velerov1api.BackupStorageLocation{}
|
||||
if err := r.Client.Get(ctx, kbclient.ObjectKey{
|
||||
Namespace: backup.Namespace,
|
||||
Name: backup.Spec.StorageLocation,
|
||||
}, location); err != nil {
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pluginManager := r.NewPluginManager(log)
|
||||
defer pluginManager.CleanupClients()
|
||||
|
||||
backupStore, err := r.BackupStoreGetter.Get(location, pluginManager, log)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error getting a backup store")
|
||||
return ctrl.Result{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if downloadRequest.Status.DownloadURL, err = backupStore.GetDownloadURL(downloadRequest.Spec.Target); err != nil {
|
||||
return ctrl.Result{Requeue: true}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
downloadRequest.Status.Phase = velerov1api.DownloadRequestPhaseProcessed
|
||||
|
||||
// Update the expiration again to extend the time we wait (the TTL) to start after successfully processing the URL.
|
||||
downloadRequest.Status.Expiration = &metav1.Time{Time: r.Clock.Now().Add(persistence.DownloadURLTTL)}
|
||||
}
|
||||
|
||||
// Requeue is mostly to handle deleting any expired requests that were not
|
||||
// deleted as part of the normal client flow for whatever reason.
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
func patchDownloadRequest(original, updated *velerov1api.DownloadRequest, client velerov1client.DownloadRequestsGetter) (*velerov1api.DownloadRequest, error) {
|
||||
origBytes, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshalling original download request")
|
||||
}
|
||||
|
||||
updatedBytes, err := json.Marshal(updated)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshalling updated download request")
|
||||
}
|
||||
|
||||
patchBytes, err := jsonpatch.CreateMergePatch(origBytes, updatedBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating json merge patch for download request")
|
||||
}
|
||||
|
||||
res, err := client.DownloadRequests(original.Namespace).Patch(context.TODO(), original.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error patching download request")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
func (r *DownloadRequestReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&velerov1api.DownloadRequest{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
|
|
@ -18,306 +18,249 @@ package controller
|
|||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/fake"
|
||||
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
|
||||
persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
type downloadRequestTestHarness struct {
|
||||
client *fake.Clientset
|
||||
informerFactory informers.SharedInformerFactory
|
||||
pluginManager *pluginmocks.Manager
|
||||
backupStore *persistencemocks.BackupStore
|
||||
|
||||
controller *downloadRequestController
|
||||
}
|
||||
|
||||
func newDownloadRequestTestHarness(t *testing.T, fakeClient client.Client) *downloadRequestTestHarness {
|
||||
var (
|
||||
client = fake.NewSimpleClientset()
|
||||
informerFactory = informers.NewSharedInformerFactory(client, 0)
|
||||
pluginManager = new(pluginmocks.Manager)
|
||||
backupStore = new(persistencemocks.BackupStore)
|
||||
controller = NewDownloadRequestController(
|
||||
client.VeleroV1(),
|
||||
informerFactory.Velero().V1().DownloadRequests(),
|
||||
informerFactory.Velero().V1().Restores().Lister(),
|
||||
fakeClient,
|
||||
informerFactory.Velero().V1().Backups().Lister(),
|
||||
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
|
||||
NewFakeSingleObjectBackupStoreGetter(backupStore),
|
||||
velerotest.NewLogger(),
|
||||
).(*downloadRequestController)
|
||||
)
|
||||
|
||||
clockTime, err := time.Parse(time.RFC1123, time.RFC1123)
|
||||
require.NoError(t, err)
|
||||
controller.clock = clock.NewFakeClock(clockTime)
|
||||
|
||||
pluginManager.On("CleanupClients").Return()
|
||||
|
||||
return &downloadRequestTestHarness{
|
||||
client: client,
|
||||
informerFactory: informerFactory,
|
||||
pluginManager: pluginManager,
|
||||
backupStore: backupStore,
|
||||
controller: controller,
|
||||
var _ = Describe("Download Request Reconciler", func() {
|
||||
type request struct {
|
||||
downloadRequest *velerov1api.DownloadRequest
|
||||
backup *velerov1api.Backup
|
||||
restore *velerov1api.Restore
|
||||
backupLocation *velerov1api.BackupStorageLocation
|
||||
expired bool
|
||||
expectedReconcileErr string
|
||||
expectGetsURL bool
|
||||
expectedRequeue ctrl.Result
|
||||
}
|
||||
}
|
||||
|
||||
func newDownloadRequest(phase velerov1api.DownloadRequestPhase, targetKind velerov1api.DownloadTargetKind, targetName string) *velerov1api.DownloadRequest {
|
||||
return &velerov1api.DownloadRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a-download-request",
|
||||
Namespace: velerov1api.DefaultNamespace,
|
||||
},
|
||||
Spec: velerov1api.DownloadRequestSpec{
|
||||
Target: velerov1api.DownloadTarget{
|
||||
Kind: targetKind,
|
||||
Name: targetName,
|
||||
},
|
||||
},
|
||||
Status: velerov1api.DownloadRequestStatus{
|
||||
Phase: phase,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newBackupLocation(name, provider, bucket string) *velerov1api.BackupStorageLocation {
|
||||
return &velerov1api.BackupStorageLocation{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: velerov1api.DefaultNamespace,
|
||||
},
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: provider,
|
||||
StorageType: velerov1api.StorageType{
|
||||
ObjectStorage: &velerov1api.ObjectStorageLocation{
|
||||
Bucket: bucket,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessDownloadRequest(t *testing.T) {
|
||||
|
||||
defaultBackup := func() *velerov1api.Backup {
|
||||
return builder.ForBackup(velerov1api.DefaultNamespace, "a-backup").StorageLocation("a-location").Result()
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
downloadRequest *velerov1api.DownloadRequest
|
||||
backup *velerov1api.Backup
|
||||
restore *velerov1api.Restore
|
||||
backupLocation *velerov1api.BackupStorageLocation
|
||||
expired bool
|
||||
expectedErr string
|
||||
expectGetsURL bool
|
||||
}{
|
||||
{
|
||||
name: "empty key returns without error",
|
||||
key: "",
|
||||
DescribeTable("a Download request",
|
||||
func(test request) {
|
||||
// now will be used to set the fake clock's time; capture
|
||||
// it here so it can be referenced in the test case defs.
|
||||
now, err := time.Parse(time.RFC1123, time.RFC1123)
|
||||
Expect(err).To(BeNil())
|
||||
now = now.Local()
|
||||
|
||||
rClock := clock.NewFakeClock(now)
|
||||
|
||||
const signedURLTTL = 10 * time.Minute
|
||||
|
||||
var (
|
||||
pluginManager = &pluginmocks.Manager{}
|
||||
backupStores = make(map[string]*persistencemocks.BackupStore)
|
||||
)
|
||||
pluginManager.On("CleanupClients").Return(nil)
|
||||
|
||||
Expect(test.downloadRequest).ToNot(BeNil())
|
||||
|
||||
// Set .status.expiration properly for all requests test cases that are
|
||||
// meant to be expired. Since "expired" is relative to the controller's
|
||||
// clock time, it's easier to do this here than as part of the test case definitions.
|
||||
if test.expired {
|
||||
test.downloadRequest.Status.Expiration = &metav1.Time{Time: rClock.Now().Add(-1 * time.Minute)}
|
||||
}
|
||||
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme)
|
||||
err = fakeClient.Create(context.TODO(), test.downloadRequest)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
if test.backup != nil {
|
||||
err := fakeClient.Create(context.TODO(), test.backup)
|
||||
Expect(err).To(BeNil())
|
||||
}
|
||||
|
||||
if test.backupLocation != nil {
|
||||
err := fakeClient.Create(context.TODO(), test.backupLocation)
|
||||
Expect(err).To(BeNil())
|
||||
backupStores[test.backupLocation.Name] = &persistencemocks.BackupStore{}
|
||||
}
|
||||
|
||||
if test.restore != nil {
|
||||
err := fakeClient.Create(context.TODO(), test.restore)
|
||||
Expect(err).To(BeNil())
|
||||
}
|
||||
|
||||
// Setup reconciler
|
||||
Expect(velerov1api.AddToScheme(scheme.Scheme)).To(Succeed())
|
||||
r := DownloadRequestReconciler{
|
||||
Client: fakeClient,
|
||||
Clock: rClock,
|
||||
NewPluginManager: func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
|
||||
BackupStoreGetter: NewFakeObjectBackupStoreGetter(backupStores),
|
||||
Log: velerotest.NewLogger(),
|
||||
}
|
||||
|
||||
if test.backupLocation != nil && test.expectGetsURL {
|
||||
backupStores[test.backupLocation.Name].On("GetDownloadURL", test.downloadRequest.Spec.Target).Return("a-url", nil)
|
||||
}
|
||||
|
||||
actualResult, err := r.Reconcile(context.Background(), ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: velerov1api.DefaultNamespace,
|
||||
Name: test.downloadRequest.Name,
|
||||
},
|
||||
})
|
||||
|
||||
Expect(actualResult).To(BeEquivalentTo(test.expectedRequeue))
|
||||
if test.expectedReconcileErr == "" {
|
||||
Expect(err).To(BeNil())
|
||||
} else {
|
||||
Expect(err.Error()).To(Equal(test.expectedReconcileErr))
|
||||
}
|
||||
|
||||
instance := &velerov1api.DownloadRequest{}
|
||||
err = r.Client.Get(ctx, kbclient.ObjectKey{Name: test.downloadRequest.Name, Namespace: test.downloadRequest.Namespace}, instance)
|
||||
|
||||
if test.expired {
|
||||
Expect(instance).ToNot(Equal(test.downloadRequest))
|
||||
Expect(apierrors.IsNotFound(err)).To(BeTrue())
|
||||
} else {
|
||||
if test.downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseProcessed {
|
||||
Expect(instance).To(Equal(test.downloadRequest))
|
||||
} else {
|
||||
Expect(instance).ToNot(Equal(test.downloadRequest))
|
||||
}
|
||||
Expect(err).To(BeNil())
|
||||
}
|
||||
|
||||
if test.expectGetsURL {
|
||||
Expect(string(instance.Status.Phase)).To(Equal(string(velerov1api.DownloadRequestPhaseProcessed)))
|
||||
Expect(instance.Status.DownloadURL).To(Equal("a-url"))
|
||||
Expect(velerotest.TimesAreEqual(instance.Status.Expiration.Time, r.Clock.Now().Add(signedURLTTL))).To(BeTrue())
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "bad key format returns without error",
|
||||
key: "a/b/c",
|
||||
},
|
||||
{
|
||||
name: "no download request for key returns without error",
|
||||
key: "nonexistent/key",
|
||||
},
|
||||
{
|
||||
name: "backup contents request for nonexistent backup returns an error",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupContents, "a-backup"),
|
||||
backup: builder.ForBackup(velerov1api.DefaultNamespace, "non-matching-backup").StorageLocation("a-location").Result(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
expectedErr: "backup.velero.io \"a-backup\" not found",
|
||||
},
|
||||
{
|
||||
name: "restore log request for nonexistent restore returns an error",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "non-matching-restore").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
|
||||
Entry("backup contents request for nonexistent backup returns an error", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindBackupContents, "a1-backup").Result(),
|
||||
backup: builder.ForBackup(velerov1api.DefaultNamespace, "non-matching-backup").StorageLocation("a-location").Result(),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectedReconcileErr: "backups.velero.io \"a1-backup\" not found",
|
||||
expectedRequeue: ctrl.Result{Requeue: false},
|
||||
}),
|
||||
Entry("restore log request for nonexistent restore returns an error", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214").Result(),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "non-matching-restore").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectedReconcileErr: "restores.velero.io \"a-backup-20170912150214\" not found",
|
||||
expectedRequeue: ctrl.Result{Requeue: false},
|
||||
}),
|
||||
Entry("backup contents request for backup with nonexistent location returns an error", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindBackupContents, "a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "non-matching-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectedReconcileErr: "backupstoragelocations.velero.io \"a-location\" not found",
|
||||
expectedRequeue: ctrl.Result{Requeue: false},
|
||||
}),
|
||||
Entry("backup contents request with phase '' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindBackupContents, "a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
expectedErr: "error getting Restore: restore.velero.io \"a-backup-20170912150214\" not found",
|
||||
},
|
||||
{
|
||||
name: "backup contents request for backup with nonexistent location returns an error",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupContents, "a-backup"),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("non-matching-location", "a-provider", "a-bucket"),
|
||||
expectedErr: "backupstoragelocations.velero.io \"a-location\" not found",
|
||||
},
|
||||
{
|
||||
name: "backup contents request with phase '' gets a url",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupContents, "a-backup"),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "backup contents request with phase 'New' gets a url",
|
||||
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindBackupContents, "a-backup"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("backup contents request with phase 'New' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseNew).Target(velerov1api.DownloadTargetKindBackupContents, "a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "backup log request with phase '' gets a url",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupLog, "a-backup"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("backup log request with phase '' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindBackupLog, "a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "backup log request with phase 'New' gets a url",
|
||||
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindBackupLog, "a-backup"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("backup log request with phase 'New' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseNew).Target(velerov1api.DownloadTargetKindBackupLog, "a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "restore log request with phase '' gets a url",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("restore log request with phase '' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214").Result(),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "restore log request with phase 'New' gets a url",
|
||||
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("restore log request with phase 'New' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseNew).Target(velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214").Result(),
|
||||
backup: defaultBackup(),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("restore results request with phase '' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindRestoreResults, "a-backup-20170912150214").Result(),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "restore results request with phase '' gets a url",
|
||||
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindRestoreResults, "a-backup-20170912150214"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("restore results request with phase 'New' gets a url", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseNew).Target(velerov1api.DownloadTargetKindRestoreResults, "a-backup-20170912150214").Result(),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
backupLocation: builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "a-location").Provider("a-provider").Bucket("a-bucket").Result(),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "restore results request with phase 'New' gets a url",
|
||||
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindRestoreResults, "a-backup-20170912150214"),
|
||||
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("request with phase 'Processed' and not expired is not deleted", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseProcessed).Target(velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214").Result(),
|
||||
backup: defaultBackup(),
|
||||
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
|
||||
expectGetsURL: true,
|
||||
},
|
||||
{
|
||||
name: "request with phase 'Processed' is not deleted if not expired",
|
||||
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseProcessed, velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214"),
|
||||
backup: defaultBackup(),
|
||||
},
|
||||
{
|
||||
name: "request with phase 'Processed' is deleted if expired",
|
||||
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseProcessed, velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214"),
|
||||
expectedRequeue: ctrl.Result{Requeue: true},
|
||||
}),
|
||||
Entry("request with phase 'Processed' and expired is deleted", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseProcessed).Target(velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214").Result(),
|
||||
backup: defaultBackup(),
|
||||
expired: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var fakeClient client.Client
|
||||
if tc.backupLocation != nil {
|
||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, tc.backupLocation)
|
||||
} else {
|
||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||
}
|
||||
|
||||
harness := newDownloadRequestTestHarness(t, fakeClient)
|
||||
|
||||
// set up test case data
|
||||
|
||||
// Set .status.expiration properly for processed requests. Since "expired" is relative to the controller's
|
||||
// clock time, it's easier to do this here than as part of the test case definitions.
|
||||
if tc.downloadRequest != nil && tc.downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseProcessed {
|
||||
if tc.expired {
|
||||
tc.downloadRequest.Status.Expiration = &metav1.Time{Time: harness.controller.clock.Now().Add(-1 * time.Minute)}
|
||||
} else {
|
||||
tc.downloadRequest.Status.Expiration = &metav1.Time{Time: harness.controller.clock.Now().Add(time.Minute)}
|
||||
}
|
||||
}
|
||||
|
||||
if tc.downloadRequest != nil {
|
||||
require.NoError(t, harness.informerFactory.Velero().V1().DownloadRequests().Informer().GetStore().Add(tc.downloadRequest))
|
||||
|
||||
_, err := harness.client.VeleroV1().DownloadRequests(tc.downloadRequest.Namespace).Create(context.TODO(), tc.downloadRequest, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if tc.restore != nil {
|
||||
require.NoError(t, harness.informerFactory.Velero().V1().Restores().Informer().GetStore().Add(tc.restore))
|
||||
}
|
||||
|
||||
if tc.backup != nil {
|
||||
require.NoError(t, harness.informerFactory.Velero().V1().Backups().Informer().GetStore().Add(tc.backup))
|
||||
}
|
||||
|
||||
if tc.expectGetsURL {
|
||||
harness.backupStore.On("GetDownloadURL", tc.downloadRequest.Spec.Target).Return("a-url", nil)
|
||||
}
|
||||
|
||||
// exercise method under test
|
||||
key := tc.key
|
||||
if key == "" && tc.downloadRequest != nil {
|
||||
key = kubeutil.NamespaceAndName(tc.downloadRequest)
|
||||
}
|
||||
|
||||
err := harness.controller.processDownloadRequest(key)
|
||||
|
||||
// verify results
|
||||
if tc.expectedErr != "" {
|
||||
require.Equal(t, tc.expectedErr, err.Error())
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
if tc.expectGetsURL {
|
||||
output, err := harness.client.VeleroV1().DownloadRequests(tc.downloadRequest.Namespace).Get(context.TODO(), tc.downloadRequest.Name, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, string(velerov1api.DownloadRequestPhaseProcessed), string(output.Status.Phase))
|
||||
assert.Equal(t, "a-url", output.Status.DownloadURL)
|
||||
assert.True(t, velerotest.TimesAreEqual(harness.controller.clock.Now().Add(signedURLTTL), output.Status.Expiration.Time), "expiration does not match")
|
||||
}
|
||||
|
||||
if tc.downloadRequest != nil && tc.downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseProcessed {
|
||||
res, err := harness.client.VeleroV1().DownloadRequests(tc.downloadRequest.Namespace).Get(context.TODO(), tc.downloadRequest.Name, metav1.GetOptions{})
|
||||
|
||||
if tc.expired {
|
||||
assert.True(t, apierrors.IsNotFound(err))
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.downloadRequest, res)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
expectedRequeue: ctrl.Result{Requeue: false},
|
||||
}),
|
||||
Entry("request with phase '' and expired is deleted", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase("").Target(velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214").Result(),
|
||||
backup: defaultBackup(),
|
||||
expired: true,
|
||||
expectedRequeue: ctrl.Result{Requeue: false},
|
||||
}),
|
||||
Entry("request with phase 'New' and expired is deleted", request{
|
||||
downloadRequest: builder.ForDownloadRequest(velerov1api.DefaultNamespace, "a-download-request").Phase(velerov1api.DownloadRequestPhaseNew).Target(velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214").Result(),
|
||||
backup: defaultBackup(),
|
||||
expired: true,
|
||||
expectedRequeue: ctrl.Result{Requeue: false},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2018 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
@ -71,12 +71,11 @@ func (r *ServerStatusRequestReconciler) Reconcile(ctx context.Context, req ctrl.
|
|||
statusRequest := &velerov1api.ServerStatusRequest{}
|
||||
if err := r.Client.Get(r.Ctx, req.NamespacedName, statusRequest); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.WithError(err).Error("ServerStatusRequest not found")
|
||||
log.Debug("Unable to find ServerStatusRequest")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
log.WithError(err).Error("Error getting ServerStatusRequest")
|
||||
// Error reading the object - requeue the request.
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue