2017-08-02 17:27:17 +00:00
/ *
2021-02-16 17:36:17 +00:00
Copyright The Velero Contributors .
2017-08-02 17:27:17 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package restore
import (
2018-02-28 01:35:35 +00:00
go_context "context"
2017-08-02 17:27:17 +00:00
"encoding/json"
"fmt"
"io"
2023-03-16 01:25:58 +00:00
"os"
2023-08-21 15:36:46 +00:00
"os/signal"
2021-03-15 22:51:07 +00:00
"path/filepath"
2017-08-02 17:27:17 +00:00
"sort"
2019-06-27 18:57:47 +00:00
"strings"
2020-01-15 17:27:21 +00:00
"sync"
2018-02-28 01:35:35 +00:00
"time"
2017-08-02 17:27:17 +00:00
2022-11-07 07:25:15 +00:00
"github.com/google/uuid"
2017-11-21 17:24:43 +00:00
"github.com/pkg/errors"
2017-09-14 21:27:31 +00:00
"github.com/sirupsen/logrus"
2019-01-25 03:33:07 +00:00
v1 "k8s.io/api/core/v1"
2018-03-29 18:50:30 +00:00
"k8s.io/apimachinery/pkg/api/equality"
2017-08-02 17:27:17 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
2017-11-21 17:24:43 +00:00
"k8s.io/apimachinery/pkg/runtime"
2017-08-02 17:27:17 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2018-02-28 01:35:35 +00:00
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
2017-08-02 17:27:17 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2018-09-07 14:42:57 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2023-08-21 15:36:46 +00:00
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers"
2017-08-02 17:27:17 +00:00
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
2020-09-08 18:33:15 +00:00
"k8s.io/client-go/tools/cache"
2023-02-03 07:57:43 +00:00
crclient "sigs.k8s.io/controller-runtime/pkg/client"
2017-08-02 17:27:17 +00:00
2022-04-01 17:29:52 +00:00
"github.com/vmware-tanzu/velero/internal/credentials"
2020-09-08 18:33:15 +00:00
"github.com/vmware-tanzu/velero/internal/hook"
2023-07-03 08:18:24 +00:00
"github.com/vmware-tanzu/velero/internal/resourcemodifiers"
2019-09-30 21:26:56 +00:00
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/archive"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/discovery"
2021-02-16 17:36:17 +00:00
"github.com/vmware-tanzu/velero/pkg/features"
2023-03-10 17:40:20 +00:00
"github.com/vmware-tanzu/velero/pkg/itemoperation"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/label"
2021-12-10 17:53:47 +00:00
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
2022-11-03 20:21:21 +00:00
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
2022-09-08 22:09:18 +00:00
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
2020-09-08 18:33:15 +00:00
"github.com/vmware-tanzu/velero/pkg/podexec"
2022-08-05 09:15:38 +00:00
"github.com/vmware-tanzu/velero/pkg/podvolume"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
2023-10-17 12:57:11 +00:00
csiutil "github.com/vmware-tanzu/velero/pkg/util/csi"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/kube"
2023-04-25 05:50:52 +00:00
"github.com/vmware-tanzu/velero/pkg/util/results"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/volume"
2017-08-02 17:27:17 +00:00
)
2023-09-01 08:52:24 +00:00
var resourceMustHave = [ ] string {
"datauploads.velero.io" ,
}
2019-03-27 18:22:04 +00:00
type VolumeSnapshotterGetter interface {
2022-09-08 22:09:18 +00:00
GetVolumeSnapshotter ( name string ) ( vsv1 . VolumeSnapshotter , error )
2018-10-16 14:28:05 +00:00
}
2017-08-02 17:27:17 +00:00
// Restorer knows how to restore a backup.
type Restorer interface {
// Restore restores the backup data from backupReader, returning warnings and errors.
2023-02-02 08:51:23 +00:00
Restore ( req * Request ,
2022-11-03 20:21:21 +00:00
actions [ ] riav2 . RestoreItemAction ,
2019-03-27 18:22:04 +00:00
volumeSnapshotterGetter VolumeSnapshotterGetter ,
2023-04-25 05:50:52 +00:00
) ( results . Result , results . Result )
2021-12-10 17:53:47 +00:00
RestoreWithResolvers (
2023-02-02 08:51:23 +00:00
req * Request ,
2022-11-03 20:21:21 +00:00
restoreItemActionResolver framework . RestoreItemActionResolverV2 ,
2021-12-10 17:53:47 +00:00
volumeSnapshotterGetter VolumeSnapshotterGetter ,
2023-04-25 05:50:52 +00:00
) ( results . Result , results . Result )
2017-08-02 17:27:17 +00:00
}
// kubernetesRestorer implements Restorer for restoring into a Kubernetes cluster.
type kubernetesRestorer struct {
2018-09-07 14:42:57 +00:00
discoveryHelper discovery . Helper
dynamicFactory client . DynamicFactory
namespaceClient corev1 . NamespaceInterface
2022-10-12 11:12:40 +00:00
podVolumeRestorerFactory podvolume . RestorerFactory
podVolumeTimeout time . Duration
2018-09-07 14:42:57 +00:00
resourceTerminatingTimeout time . Duration
2023-02-27 23:13:31 +00:00
resourceTimeout time . Duration
2022-10-31 08:28:06 +00:00
resourcePriorities Priorities
2018-09-07 14:42:57 +00:00
fileSystem filesystem . Interface
2019-12-17 18:23:58 +00:00
pvRenamer func ( string ) ( string , error )
2018-09-07 14:42:57 +00:00
logger logrus . FieldLogger
2020-09-08 18:33:15 +00:00
podCommandExecutor podexec . PodCommandExecutor
podGetter cache . Getter
2022-04-01 17:29:52 +00:00
credentialFileStore credentials . FileStore
2023-02-03 07:57:43 +00:00
kbClient crclient . Client
2017-08-02 17:27:17 +00:00
}
// NewKubernetesRestorer creates a new kubernetesRestorer.
func NewKubernetesRestorer (
discoveryHelper discovery . Helper ,
dynamicFactory client . DynamicFactory ,
2022-10-31 08:28:06 +00:00
resourcePriorities Priorities ,
2017-08-02 17:27:17 +00:00
namespaceClient corev1 . NamespaceInterface ,
2022-10-12 11:12:40 +00:00
podVolumeRestorerFactory podvolume . RestorerFactory ,
podVolumeTimeout time . Duration ,
2018-09-07 14:42:57 +00:00
resourceTerminatingTimeout time . Duration ,
2023-02-27 23:13:31 +00:00
resourceTimeout time . Duration ,
2017-12-12 23:22:46 +00:00
logger logrus . FieldLogger ,
2020-09-08 18:33:15 +00:00
podCommandExecutor podexec . PodCommandExecutor ,
podGetter cache . Getter ,
2022-04-01 17:29:52 +00:00
credentialStore credentials . FileStore ,
2023-02-03 07:57:43 +00:00
kbClient crclient . Client ,
2017-08-02 17:27:17 +00:00
) ( Restorer , error ) {
return & kubernetesRestorer {
2018-09-07 14:42:57 +00:00
discoveryHelper : discoveryHelper ,
dynamicFactory : dynamicFactory ,
namespaceClient : namespaceClient ,
2022-10-12 11:12:40 +00:00
podVolumeRestorerFactory : podVolumeRestorerFactory ,
podVolumeTimeout : podVolumeTimeout ,
2018-09-07 14:42:57 +00:00
resourceTerminatingTimeout : resourceTerminatingTimeout ,
2023-02-27 23:13:31 +00:00
resourceTimeout : resourceTimeout ,
2018-09-07 14:42:57 +00:00
resourcePriorities : resourcePriorities ,
logger : logger ,
2019-12-17 18:23:58 +00:00
pvRenamer : func ( string ) ( string , error ) {
2023-04-25 05:50:52 +00:00
veleroCloneUUID , err := uuid . NewRandom ( )
2019-12-17 18:23:58 +00:00
if err != nil {
return "" , errors . WithStack ( err )
}
2023-04-25 05:50:52 +00:00
veleroCloneName := "velero-clone-" + veleroCloneUUID . String ( )
2019-12-17 18:23:58 +00:00
return veleroCloneName , nil
} ,
2022-04-01 17:29:52 +00:00
fileSystem : filesystem . NewFileSystem ( ) ,
podCommandExecutor : podCommandExecutor ,
podGetter : podGetter ,
credentialFileStore : credentialStore ,
2023-02-03 07:57:43 +00:00
kbClient : kbClient ,
2017-08-02 17:27:17 +00:00
} , nil
}
// Restore executes a restore into the target Kubernetes cluster according to the restore spec
// and using data from the provided backup/backup reader. Returns a warnings and errors RestoreResult,
// respectively, summarizing info about the restore.
2018-10-16 14:28:05 +00:00
func ( kr * kubernetesRestorer ) Restore (
2023-02-02 08:51:23 +00:00
req * Request ,
2022-11-03 20:21:21 +00:00
actions [ ] riav2 . RestoreItemAction ,
2019-03-27 18:22:04 +00:00
volumeSnapshotterGetter VolumeSnapshotterGetter ,
2023-04-25 05:50:52 +00:00
) ( results . Result , results . Result ) {
2022-11-03 20:21:21 +00:00
resolver := framework . NewRestoreItemActionResolverV2 ( actions )
2023-03-21 01:05:29 +00:00
return kr . RestoreWithResolvers ( req , resolver , volumeSnapshotterGetter )
2021-12-10 17:53:47 +00:00
}
func ( kr * kubernetesRestorer ) RestoreWithResolvers (
2023-02-02 08:51:23 +00:00
req * Request ,
2022-11-03 20:21:21 +00:00
restoreItemActionResolver framework . RestoreItemActionResolverV2 ,
2021-12-10 17:53:47 +00:00
volumeSnapshotterGetter VolumeSnapshotterGetter ,
2023-04-25 05:50:52 +00:00
) ( results . Result , results . Result ) {
2017-08-02 17:27:17 +00:00
// metav1.LabelSelectorAsSelector converts a nil LabelSelector to a
// Nothing Selector, i.e. a selector that matches nothing. We want
// a selector that matches everything. This can be accomplished by
// passing a non-nil empty LabelSelector.
2019-08-06 20:17:36 +00:00
ls := req . Restore . Spec . LabelSelector
2017-08-02 17:27:17 +00:00
if ls == nil {
ls = & metav1 . LabelSelector { }
}
2022-02-15 14:52:47 +00:00
var OrSelectors [ ] labels . Selector
if req . Restore . Spec . OrLabelSelectors != nil {
for _ , s := range req . Restore . Spec . OrLabelSelectors {
labelAsSelector , err := metav1 . LabelSelectorAsSelector ( s )
if err != nil {
2023-04-25 05:50:52 +00:00
return results . Result { } , results . Result { Velero : [ ] string { err . Error ( ) } }
2022-02-15 14:52:47 +00:00
}
OrSelectors = append ( OrSelectors , labelAsSelector )
}
}
2017-08-02 17:27:17 +00:00
selector , err := metav1 . LabelSelectorAsSelector ( ls )
if err != nil {
2023-04-25 05:50:52 +00:00
return results . Result { } , results . Result { Velero : [ ] string { err . Error ( ) } }
2017-08-02 17:27:17 +00:00
}
2021-03-15 22:51:07 +00:00
// Get resource includes-excludes.
resourceIncludesExcludes := collections . GetResourceIncludesExcludes (
kr . discoveryHelper ,
req . Restore . Spec . IncludedResources ,
req . Restore . Spec . ExcludedResources ,
)
2017-11-21 17:24:43 +00:00
2022-05-12 14:58:47 +00:00
// Get resource status includes-excludes. Defaults to excluding all resources
2022-06-15 16:57:37 +00:00
var restoreStatusIncludesExcludes * collections . IncludesExcludes
2022-05-12 14:58:47 +00:00
if req . Restore . Spec . RestoreStatus != nil {
restoreStatusIncludesExcludes = collections . GetResourceIncludesExcludes (
kr . discoveryHelper ,
req . Restore . Spec . RestoreStatus . IncludedResources ,
req . Restore . Spec . RestoreStatus . ExcludedResources ,
)
}
2021-03-15 22:51:07 +00:00
// Get namespace includes-excludes.
2019-06-27 20:48:50 +00:00
namespaceIncludesExcludes := collections . NewIncludesExcludes ( ) .
2019-08-06 20:17:36 +00:00
Includes ( req . Restore . Spec . IncludedNamespaces ... ) .
Excludes ( req . Restore . Spec . ExcludedNamespaces ... )
2019-06-27 20:48:50 +00:00
2022-09-01 18:30:07 +00:00
resolvedActions , err := restoreItemActionResolver . ResolveActions ( kr . discoveryHelper , kr . logger )
2021-12-10 17:53:47 +00:00
if err != nil {
2023-04-25 05:50:52 +00:00
return results . Result { } , results . Result { Velero : [ ] string { err . Error ( ) } }
2021-12-10 17:53:47 +00:00
}
2022-10-12 11:12:40 +00:00
podVolumeTimeout := kr . podVolumeTimeout
2019-08-06 20:17:36 +00:00
if val := req . Restore . Annotations [ velerov1api . PodVolumeOperationTimeoutAnnotation ] ; val != "" {
2018-02-28 01:35:35 +00:00
parsed , err := time . ParseDuration ( val )
if err != nil {
2021-03-15 22:51:07 +00:00
req . Log . WithError ( errors . WithStack ( err ) ) . Errorf (
"Unable to parse pod volume timeout annotation %s, using server value." ,
val ,
)
2018-02-28 01:35:35 +00:00
} else {
podVolumeTimeout = parsed
}
}
ctx , cancelFunc := go_context . WithTimeout ( go_context . Background ( ) , podVolumeTimeout )
defer cancelFunc ( )
2022-10-12 11:12:40 +00:00
var podVolumeRestorer podvolume . Restorer
if kr . podVolumeRestorerFactory != nil {
podVolumeRestorer , err = kr . podVolumeRestorerFactory . NewRestorer ( ctx , req . Restore )
2018-02-28 01:35:35 +00:00
if err != nil {
2023-04-25 05:50:52 +00:00
return results . Result { } , results . Result { Velero : [ ] string { err . Error ( ) } }
2018-02-28 01:35:35 +00:00
}
}
2020-09-08 18:33:15 +00:00
resourceRestoreHooks , err := hook . GetRestoreHooksFromSpec ( & req . Restore . Spec . Hooks )
if err != nil {
2023-04-25 05:50:52 +00:00
return results . Result { } , results . Result { Velero : [ ] string { err . Error ( ) } }
2020-09-08 18:33:15 +00:00
}
hooksCtx , hooksCancelFunc := go_context . WithCancel ( go_context . Background ( ) )
waitExecHookHandler := & hook . DefaultWaitExecHookHandler {
PodCommandExecutor : kr . podCommandExecutor ,
ListWatchFactory : & hook . DefaultListWatchFactory {
PodsGetter : kr . podGetter ,
} ,
}
2018-06-22 19:32:03 +00:00
pvRestorer := & pvRestorer {
2019-08-06 20:17:36 +00:00
logger : req . Log ,
backup : req . Backup ,
snapshotVolumes : req . Backup . Spec . SnapshotVolumes ,
restorePVs : req . Restore . Spec . RestorePVs ,
volumeSnapshots : req . VolumeSnapshots ,
2019-03-27 18:22:04 +00:00
volumeSnapshotterGetter : volumeSnapshotterGetter ,
2023-02-03 07:57:43 +00:00
kbclient : kr . kbClient ,
2022-04-01 17:29:52 +00:00
credentialFileStore : kr . credentialFileStore ,
2018-06-22 19:32:03 +00:00
}
2023-02-28 23:40:32 +00:00
req . RestoredItems = make ( map [ itemKey ] restoredItemStatus )
2023-02-02 08:51:23 +00:00
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
restoreCtx := & restoreContext {
2022-05-12 14:58:47 +00:00
backup : req . Backup ,
backupReader : req . BackupReader ,
restore : req . Restore ,
resourceIncludesExcludes : resourceIncludesExcludes ,
resourceStatusIncludesExcludes : restoreStatusIncludesExcludes ,
namespaceIncludesExcludes : namespaceIncludesExcludes ,
2023-09-01 08:52:24 +00:00
resourceMustHave : sets . NewString ( resourceMustHave ... ) ,
2022-05-12 14:58:47 +00:00
chosenGrpVersToRestore : make ( map [ string ] ChosenGroupVersion ) ,
selector : selector ,
OrSelectors : OrSelectors ,
log : req . Log ,
dynamicFactory : kr . dynamicFactory ,
fileSystem : kr . fileSystem ,
namespaceClient : kr . namespaceClient ,
restoreItemActions : resolvedActions ,
volumeSnapshotterGetter : volumeSnapshotterGetter ,
2022-10-12 11:12:40 +00:00
podVolumeRestorer : podVolumeRestorer ,
podVolumeErrs : make ( chan error ) ,
2022-05-12 14:58:47 +00:00
pvsToProvision : sets . NewString ( ) ,
pvRestorer : pvRestorer ,
volumeSnapshots : req . VolumeSnapshots ,
podVolumeBackups : req . PodVolumeBackups ,
resourceTerminatingTimeout : kr . resourceTerminatingTimeout ,
2023-02-27 23:13:31 +00:00
resourceTimeout : kr . resourceTimeout ,
2022-05-12 14:58:47 +00:00
resourceClients : make ( map [ resourceClientKey ] client . Dynamic ) ,
2023-08-21 15:36:46 +00:00
dynamicInformerFactories : make ( map [ string ] * informerFactoryWithContext ) ,
resourceInformers : make ( map [ resourceClientKey ] informers . GenericInformer ) ,
2023-02-02 08:51:23 +00:00
restoredItems : req . RestoredItems ,
2022-05-12 14:58:47 +00:00
renamedPVs : make ( map [ string ] string ) ,
pvRenamer : kr . pvRenamer ,
discoveryHelper : kr . discoveryHelper ,
resourcePriorities : kr . resourcePriorities ,
resourceRestoreHooks : resourceRestoreHooks ,
hooksErrs : make ( chan error ) ,
waitExecHookHandler : waitExecHookHandler ,
hooksContext : hooksCtx ,
hooksCancelFunc : hooksCancelFunc ,
2023-02-03 07:57:43 +00:00
kbClient : kr . kbClient ,
2023-03-10 17:40:20 +00:00
itemOperationsList : req . GetItemOperationsList ( ) ,
2023-07-03 08:18:24 +00:00
resourceModifiers : req . ResourceModifiers ,
2023-08-21 15:36:46 +00:00
disableInformerCache : req . DisableInformerCache ,
2017-09-12 19:54:08 +00:00
}
2018-02-28 01:35:35 +00:00
return restoreCtx . execute ( )
2017-09-12 19:54:08 +00:00
}
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
type restoreContext struct {
2022-05-12 14:58:47 +00:00
backup * velerov1api . Backup
backupReader io . Reader
restore * velerov1api . Restore
restoreDir string
resourceIncludesExcludes * collections . IncludesExcludes
resourceStatusIncludesExcludes * collections . IncludesExcludes
namespaceIncludesExcludes * collections . IncludesExcludes
2023-09-01 08:52:24 +00:00
resourceMustHave sets . String
2022-05-12 14:58:47 +00:00
chosenGrpVersToRestore map [ string ] ChosenGroupVersion
selector labels . Selector
OrSelectors [ ] labels . Selector
log logrus . FieldLogger
dynamicFactory client . DynamicFactory
fileSystem filesystem . Interface
namespaceClient corev1 . NamespaceInterface
2022-11-03 20:21:21 +00:00
restoreItemActions [ ] framework . RestoreItemResolvedActionV2
2022-05-12 14:58:47 +00:00
volumeSnapshotterGetter VolumeSnapshotterGetter
2022-10-12 11:12:40 +00:00
podVolumeRestorer podvolume . Restorer
podVolumeWaitGroup sync . WaitGroup
podVolumeErrs chan error
2022-05-12 14:58:47 +00:00
pvsToProvision sets . String
pvRestorer PVRestorer
volumeSnapshots [ ] * volume . Snapshot
podVolumeBackups [ ] * velerov1api . PodVolumeBackup
resourceTerminatingTimeout time . Duration
2023-02-27 23:13:31 +00:00
resourceTimeout time . Duration
2022-05-12 14:58:47 +00:00
resourceClients map [ resourceClientKey ] client . Dynamic
2023-08-21 15:36:46 +00:00
dynamicInformerFactories map [ string ] * informerFactoryWithContext
resourceInformers map [ resourceClientKey ] informers . GenericInformer
2023-02-28 23:40:32 +00:00
restoredItems map [ itemKey ] restoredItemStatus
2022-05-12 14:58:47 +00:00
renamedPVs map [ string ] string
pvRenamer func ( string ) ( string , error )
discoveryHelper discovery . Helper
2022-10-31 08:28:06 +00:00
resourcePriorities Priorities
2022-05-12 14:58:47 +00:00
hooksWaitGroup sync . WaitGroup
hooksErrs chan error
resourceRestoreHooks [ ] hook . ResourceRestoreHook
waitExecHookHandler hook . WaitExecHookHandler
hooksContext go_context . Context
hooksCancelFunc go_context . CancelFunc
2023-02-03 07:57:43 +00:00
kbClient crclient . Client
2023-03-10 17:40:20 +00:00
itemOperationsList * [ ] * itemoperation . RestoreOperation
2023-07-03 08:18:24 +00:00
resourceModifiers * resourcemodifiers . ResourceModifiers
2023-08-21 15:36:46 +00:00
disableInformerCache bool
2019-03-28 19:21:56 +00:00
}
type resourceClientKey struct {
2020-01-27 21:59:08 +00:00
resource schema . GroupVersionResource
2019-03-28 19:21:56 +00:00
namespace string
2017-09-12 19:54:08 +00:00
}
2023-08-21 15:36:46 +00:00
type informerFactoryWithContext struct {
factory dynamicinformer . DynamicSharedInformerFactory
context go_context . Context
cancel go_context . CancelFunc
}
2021-03-15 22:51:07 +00:00
// getOrderedResources returns an ordered list of resource identifiers to restore,
// based on the provided resource priorities and backup contents. The returned list
2022-10-31 08:28:06 +00:00
// begins with all of the high prioritized resources (in order), ends with all of
// the low prioritized resources(in order), and an alphabetized list of resources
// in the backup(pick out the prioritized resources) is put in the middle.
func getOrderedResources ( resourcePriorities Priorities , backupResources map [ string ] * archive . ResourceItems ) [ ] string {
priorities := map [ string ] struct { } { }
for _ , priority := range resourcePriorities . HighPriorities {
priorities [ priority ] = struct { } { }
}
for _ , priority := range resourcePriorities . LowPriorities {
priorities [ priority ] = struct { } { }
}
// pick the prioritized resources out
var orderedBackupResources [ ] string
2020-02-04 21:33:12 +00:00
for resource := range backupResources {
2022-10-31 08:28:06 +00:00
if _ , exist := priorities [ resource ] ; exist {
continue
}
2020-02-04 21:33:12 +00:00
orderedBackupResources = append ( orderedBackupResources , resource )
}
2022-10-31 08:28:06 +00:00
// alphabetize resources in the backup
2020-02-04 21:33:12 +00:00
sort . Strings ( orderedBackupResources )
2022-10-31 08:28:06 +00:00
list := append ( resourcePriorities . HighPriorities , orderedBackupResources ... )
return append ( list , resourcePriorities . LowPriorities ... )
2020-02-04 21:33:12 +00:00
}
2021-06-19 07:03:11 +00:00
type progressUpdate struct {
totalItems , itemsRestored int
}
2023-04-25 05:50:52 +00:00
func ( ctx * restoreContext ) execute ( ) ( results . Result , results . Result ) {
warnings , errs := results . Result { } , results . Result { }
2019-08-29 01:03:01 +00:00
2018-09-30 20:45:32 +00:00
ctx . log . Infof ( "Starting restore of backup %s" , kube . NamespaceAndName ( ctx . backup ) )
2017-09-12 19:54:08 +00:00
2019-08-29 01:03:01 +00:00
dir , err := archive . NewExtractor ( ctx . log , ctx . fileSystem ) . UnzipAndExtractBackup ( ctx . backupReader )
2017-08-02 17:27:17 +00:00
if err != nil {
2018-09-30 20:45:32 +00:00
ctx . log . Infof ( "error unzipping and extracting: %v" , err )
2020-02-03 18:56:57 +00:00
errs . AddVeleroError ( err )
2019-08-29 01:03:01 +00:00
return warnings , errs
2017-08-02 17:27:17 +00:00
}
2023-04-27 12:39:53 +00:00
defer func ( ) {
if err := ctx . fileSystem . RemoveAll ( dir ) ; err != nil {
ctx . log . Errorf ( "error removing temporary directory %s: %s" , dir , err . Error ( ) )
}
} ( )
2017-08-02 17:27:17 +00:00
2023-08-21 15:36:46 +00:00
// Need to stop all informers if enabled
if ! ctx . disableInformerCache {
defer func ( ) {
// Call the cancel func to close the channel for each started informer
for _ , factory := range ctx . dynamicInformerFactories {
factory . cancel ( )
}
// After upgrading to client-go 0.27 or newer, also call Shutdown for each informer factory
} ( )
}
2021-03-15 22:51:07 +00:00
// Need to set this for additionalItems to be restored.
2019-03-28 19:21:56 +00:00
ctx . restoreDir = dir
2019-08-29 01:03:01 +00:00
backupResources , err := archive . NewParser ( ctx . log , ctx . fileSystem ) . Parse ( ctx . restoreDir )
2022-09-07 03:48:34 +00:00
// If ErrNotExist occurs, it implies that the backup to be restored includes zero items.
// Need to add a warning about it and jump out of the function.
if errors . Cause ( err ) == archive . ErrNotExist {
warnings . AddVeleroError ( errors . Wrap ( err , "zero items to be restored" ) )
return warnings , errs
}
2017-08-11 21:05:06 +00:00
if err != nil {
2020-02-03 18:56:57 +00:00
errs . AddVeleroError ( errors . Wrap ( err , "error parsing backup contents" ) )
2017-10-10 18:43:53 +00:00
return warnings , errs
2017-08-11 21:05:06 +00:00
}
2021-02-16 17:36:17 +00:00
// TODO: Remove outer feature flag check to make this feature a default in Velero.
if features . IsEnabled ( velerov1api . APIGroupVersionsFeatureFlag ) {
if ctx . backup . Status . FormatVersion >= "1.1.0" {
if err := ctx . chooseAPIVersionsToRestore ( ) ; err != nil {
errs . AddVeleroError ( errors . Wrap ( err , "choosing API version to restore" ) )
return warnings , errs
}
}
}
2021-03-04 21:21:44 +00:00
update := make ( chan progressUpdate )
2020-01-30 17:19:13 +00:00
2021-03-04 21:21:44 +00:00
quit := make ( chan struct { } )
2020-01-30 17:19:13 +00:00
2021-03-04 21:21:44 +00:00
go func ( ) {
ticker := time . NewTicker ( 1 * time . Second )
var lastUpdate * progressUpdate
for {
select {
case <- quit :
ticker . Stop ( )
return
case val := <- update :
lastUpdate = & val
case <- ticker . C :
if lastUpdate != nil {
2023-02-03 07:57:43 +00:00
updated := ctx . restore . DeepCopy ( )
if updated . Status . Progress == nil {
updated . Status . Progress = & velerov1api . RestoreProgress { }
}
updated . Status . Progress . TotalItems = lastUpdate . totalItems
updated . Status . Progress . ItemsRestored = lastUpdate . itemsRestored
err = kube . PatchResource ( ctx . restore , updated , ctx . kbClient )
2021-03-15 22:51:07 +00:00
if err != nil {
ctx . log . WithError ( errors . WithStack ( ( err ) ) ) .
Warn ( "Got error trying to update restore's status.progress" )
2021-03-04 21:21:44 +00:00
}
lastUpdate = nil
}
2020-01-30 17:19:13 +00:00
}
2021-03-04 21:21:44 +00:00
}
} ( )
2020-01-30 17:19:13 +00:00
2021-03-15 22:51:07 +00:00
// totalItems: previously discovered items, i: iteration counter.
2021-06-19 07:03:11 +00:00
totalItems , processedItems , existingNamespaces := 0 , 0 , sets . NewString ( )
// First restore CRDs. This is needed so that they are available in the cluster
// when getOrderedResourceCollection is called again on the whole backup and
// needs to validate all resources listed.
crdResourceCollection , processedResources , w , e := ctx . getOrderedResourceCollection (
backupResources ,
make ( [ ] restoreableResource , 0 ) ,
sets . NewString ( ) ,
2022-10-31 08:28:06 +00:00
Priorities { HighPriorities : [ ] string { "customresourcedefinitions" } } ,
2021-06-19 07:03:11 +00:00
false ,
)
warnings . Merge ( & w )
errs . Merge ( & e )
2021-03-04 21:21:44 +00:00
2021-06-19 07:03:11 +00:00
for _ , selectedResource := range crdResourceCollection {
2021-03-04 21:21:44 +00:00
totalItems += selectedResource . totalItems
}
2021-03-15 22:51:07 +00:00
2021-06-19 07:03:11 +00:00
for _ , selectedResource := range crdResourceCollection {
2023-04-25 05:50:52 +00:00
var w , e results . Result
2023-05-31 03:45:57 +00:00
// Restore this resource, the update channel is set to nil, to avoid misleading value of "totalItems"
// more details see #5990
2021-06-19 07:03:11 +00:00
processedItems , w , e = ctx . processSelectedResource (
selectedResource ,
totalItems ,
processedItems ,
existingNamespaces ,
2023-05-31 03:45:57 +00:00
nil ,
2021-06-19 07:03:11 +00:00
)
warnings . Merge ( & w )
errs . Merge ( & e )
}
2021-03-15 22:51:07 +00:00
2021-06-19 07:03:11 +00:00
// Restore everything else
selectedResourceCollection , _ , w , e := ctx . getOrderedResourceCollection (
backupResources ,
crdResourceCollection ,
processedResources ,
ctx . resourcePriorities ,
true ,
)
warnings . Merge ( & w )
errs . Merge ( & e )
2021-03-15 22:51:07 +00:00
2023-08-21 15:36:46 +00:00
// initialize informer caches for selected resources if enabled
if ! ctx . disableInformerCache {
// CRD informer will have already been initialized if any CRDs were created,
// but already-initialized informers aren't re-initialized because getGenericInformer
// looks for an existing one first.
factoriesToStart := make ( map [ string ] * informerFactoryWithContext )
for _ , informerResource := range selectedResourceCollection {
gr := schema . ParseGroupResource ( informerResource . resource )
for _ , items := range informerResource . selectedItemsByNamespace {
// don't use ns key since it represents original ns, not mapped ns
if len ( items ) == 0 {
continue
}
// use the first item in the list to initialize the informer. The rest of the list
// should share the same gvr and namespace
_ , factory := ctx . getGenericInformerInternal ( gr , items [ 0 ] . version , items [ 0 ] . targetNamespace )
if factory != nil {
factoriesToStart [ items [ 0 ] . targetNamespace ] = factory
}
}
}
for _ , factoryWithContext := range factoriesToStart {
factoryWithContext . factory . WaitForCacheSync ( factoryWithContext . context . Done ( ) )
}
}
2021-06-19 07:03:11 +00:00
// reset processedItems and totalItems before processing full resource list
processedItems = 0
totalItems = 0
for _ , selectedResource := range selectedResourceCollection {
totalItems += selectedResource . totalItems
}
2020-02-04 21:33:12 +00:00
2021-06-19 07:03:11 +00:00
for _ , selectedResource := range selectedResourceCollection {
2023-04-25 05:50:52 +00:00
var w , e results . Result
2021-06-19 07:03:11 +00:00
// Restore this resource
processedItems , w , e = ctx . processSelectedResource (
selectedResource ,
totalItems ,
processedItems ,
existingNamespaces ,
update ,
)
warnings . Merge ( & w )
errs . Merge ( & e )
2020-01-30 17:19:13 +00:00
}
2021-03-15 22:51:07 +00:00
// Close the progress update channel.
2021-03-04 21:21:44 +00:00
quit <- struct { } { }
2023-06-16 09:37:10 +00:00
// Clean the DataUploadResult ConfigMaps
defer func ( ) {
opts := [ ] crclient . DeleteAllOfOption {
crclient . InNamespace ( ctx . restore . Namespace ) ,
crclient . MatchingLabels {
velerov1api . RestoreUIDLabel : string ( ctx . restore . UID ) ,
velerov1api . ResourceUsageLabel : string ( velerov1api . VeleroResourceUsageDataUploadResult ) ,
} ,
}
err := ctx . kbClient . DeleteAllOf ( go_context . Background ( ) , & v1 . ConfigMap { } , opts ... )
if err != nil {
ctx . log . Errorf ( "Fail to batch delete DataUploadResult ConfigMaps for restore %s: %s" , ctx . restore . Name , err . Error ( ) )
}
} ( )
2021-03-15 22:51:07 +00:00
// Do a final progress update as stopping the ticker might have left last few
// updates from taking place.
2023-02-03 07:57:43 +00:00
updated := ctx . restore . DeepCopy ( )
if updated . Status . Progress == nil {
updated . Status . Progress = & velerov1api . RestoreProgress { }
}
updated . Status . Progress . TotalItems = len ( ctx . restoredItems )
updated . Status . Progress . ItemsRestored = len ( ctx . restoredItems )
2021-03-15 22:51:07 +00:00
2023-02-03 07:57:43 +00:00
err = kube . PatchResource ( ctx . restore , updated , ctx . kbClient )
2021-03-15 22:51:07 +00:00
if err != nil {
ctx . log . WithError ( errors . WithStack ( ( err ) ) ) . Warn ( "Updating restore status.progress" )
2021-03-04 21:21:44 +00:00
}
2022-10-12 11:12:40 +00:00
// Wait for all of the pod volume restore goroutines to be done, which is
2020-01-15 17:27:21 +00:00
// only possible once all of their errors have been received by the loop
2022-10-12 11:12:40 +00:00
// below, then close the podVolumeErrs channel so the loop terminates.
2020-01-15 17:27:21 +00:00
go func ( ) {
2022-10-12 11:12:40 +00:00
ctx . log . Info ( "Waiting for all pod volume restores to complete" )
2020-01-21 19:36:46 +00:00
2020-01-15 17:27:21 +00:00
// TODO timeout?
2022-10-12 11:12:40 +00:00
ctx . podVolumeWaitGroup . Wait ( )
close ( ctx . podVolumeErrs )
2020-01-15 17:27:21 +00:00
} ( )
2022-10-12 11:12:40 +00:00
// This loop will only terminate when the ctx.podVolumeErrs channel is closed
2020-01-15 17:27:21 +00:00
// in the above goroutine, *after* all errors from the goroutines have been
// received by this loop.
2022-10-12 11:12:40 +00:00
for err := range ctx . podVolumeErrs {
2021-03-15 22:51:07 +00:00
// TODO: not ideal to be adding these to Velero-level errors
2018-02-28 01:35:35 +00:00
// rather than a specific namespace, but don't have a way
// to track the namespace right now.
2019-01-25 03:33:07 +00:00
errs . Velero = append ( errs . Velero , err . Error ( ) )
2017-08-02 17:27:17 +00:00
}
2022-10-12 11:12:40 +00:00
ctx . log . Info ( "Done waiting for all pod volume restores to complete" )
2017-08-02 17:27:17 +00:00
2022-10-12 11:12:40 +00:00
// Wait for all post-restore exec hooks with same logic as pod volume wait above.
2020-09-08 18:33:15 +00:00
go func ( ) {
ctx . log . Info ( "Waiting for all post-restore-exec hooks to complete" )
ctx . hooksWaitGroup . Wait ( )
close ( ctx . hooksErrs )
} ( )
for err := range ctx . hooksErrs {
errs . Velero = append ( errs . Velero , err . Error ( ) )
}
ctx . log . Info ( "Done waiting for all post-restore exec hooks to complete" )
2017-10-10 18:43:53 +00:00
return warnings , errs
2017-08-02 17:27:17 +00:00
}
2021-06-19 07:03:11 +00:00
// Process and restore one restoreableResource from the backup and update restore progress
// metadata. At this point, the resource has already been validated and counted for inclusion
// in the expected total restore count.
func ( ctx * restoreContext ) processSelectedResource (
selectedResource restoreableResource ,
totalItems int ,
processedItems int ,
existingNamespaces sets . String ,
update chan progressUpdate ,
2023-04-25 05:50:52 +00:00
) ( int , results . Result , results . Result ) {
warnings , errs := results . Result { } , results . Result { }
2021-06-19 07:03:11 +00:00
groupResource := schema . ParseGroupResource ( selectedResource . resource )
for namespace , selectedItems := range selectedResource . selectedItemsByNamespace {
for _ , selectedItem := range selectedItems {
// If we don't know whether this namespace exists yet, attempt to create
// it in order to ensure it exists. Try to get it from the backup tarball
// (in order to get any backed-up metadata), but if we don't find it there,
// create a blank one.
if namespace != "" && ! existingNamespaces . Has ( selectedItem . targetNamespace ) {
logger := ctx . log . WithField ( "namespace" , namespace )
ns := getNamespace (
logger ,
archive . GetItemFilePath ( ctx . restoreDir , "namespaces" , "" , namespace ) ,
selectedItem . targetNamespace ,
)
_ , nsCreated , err := kube . EnsureNamespaceExistsAndIsReady (
ns ,
ctx . namespaceClient ,
ctx . resourceTerminatingTimeout ,
)
if err != nil {
errs . AddVeleroError ( err )
continue
}
// Add the newly created namespace to the list of restored items.
if nsCreated {
2023-02-02 08:51:23 +00:00
itemKey := itemKey {
resource : resourceKey ( ns ) ,
namespace : ns . Namespace ,
name : ns . Name ,
2021-06-19 07:03:11 +00:00
}
2023-02-28 23:40:32 +00:00
ctx . restoredItems [ itemKey ] = restoredItemStatus { action : itemRestoreResultCreated , itemExists : true }
2021-06-19 07:03:11 +00:00
}
// Keep track of namespaces that we know exist so we don't
// have to try to create them multiple times.
existingNamespaces . Insert ( selectedItem . targetNamespace )
}
obj , err := archive . Unmarshal ( ctx . fileSystem , selectedItem . path )
if err != nil {
errs . Add (
selectedItem . targetNamespace ,
fmt . Errorf (
"error decoding %q: %v" ,
strings . Replace ( selectedItem . path , ctx . restoreDir + "/" , "" , - 1 ) ,
err ,
) ,
)
continue
}
2023-02-28 23:40:32 +00:00
w , e , _ := ctx . restoreItem ( obj , groupResource , selectedItem . targetNamespace )
2021-06-19 07:03:11 +00:00
warnings . Merge ( & w )
errs . Merge ( & e )
processedItems ++
// totalItems keeps the count of items previously known. There
// may be additional items restored by plugins. We want to include
// the additional items by looking at restoredItems at the same
// time, we don't want previously known items counted twice as
// they are present in both restoredItems and totalItems.
actualTotalItems := len ( ctx . restoredItems ) + ( totalItems - processedItems )
2023-05-31 03:45:57 +00:00
if update != nil {
update <- progressUpdate {
totalItems : actualTotalItems ,
itemsRestored : len ( ctx . restoredItems ) ,
}
2021-06-19 07:03:11 +00:00
}
ctx . log . WithFields ( map [ string ] interface { } {
"progress" : "" ,
"resource" : groupResource . String ( ) ,
"namespace" : selectedItem . targetNamespace ,
"name" : selectedItem . name ,
} ) . Infof ( "Restored %d items out of an estimated total of %d (estimate will change throughout the restore)" , len ( ctx . restoredItems ) , actualTotalItems )
}
}
// If we just restored custom resource definitions (CRDs), refresh
// discovery because the restored CRDs may have created new APIs that
// didn't previously exist in the cluster, and we want to be able to
// resolve & restore instances of them in subsequent loop iterations.
if groupResource == kuberesource . CustomResourceDefinitions {
if err := ctx . discoveryHelper . Refresh ( ) ; err != nil {
warnings . Add ( "" , errors . Wrap ( err , "refresh discovery after restoring CRDs" ) )
}
}
return processedItems , warnings , errs
}
2017-11-21 17:24:43 +00:00
// getNamespace returns a namespace API object that we should attempt to
// create before restoring anything into it. It will come from the backup
// tarball if it exists, else will be a new one. If from the tarball, it
// will retain its labels, annotations, and spec.
func getNamespace ( logger logrus . FieldLogger , path , remappedName string ) * v1 . Namespace {
var nsBytes [ ] byte
var err error
2023-03-16 01:25:58 +00:00
if nsBytes , err = os . ReadFile ( path ) ; err != nil {
2017-11-21 17:24:43 +00:00
return & v1 . Namespace {
2023-02-02 08:51:23 +00:00
TypeMeta : metav1 . TypeMeta {
Kind : "Namespace" ,
APIVersion : "v1" ,
} ,
2017-11-21 17:24:43 +00:00
ObjectMeta : metav1 . ObjectMeta {
Name : remappedName ,
} ,
}
}
var backupNS v1 . Namespace
if err := json . Unmarshal ( nsBytes , & backupNS ) ; err != nil {
2023-03-24 04:15:08 +00:00
logger . Warnf ( "Error unmarshaling namespace from backup, creating new one." )
2017-11-21 17:24:43 +00:00
return & v1 . Namespace {
2023-02-02 08:51:23 +00:00
TypeMeta : metav1 . TypeMeta {
Kind : "Namespace" ,
APIVersion : "v1" ,
} ,
2017-11-21 17:24:43 +00:00
ObjectMeta : metav1 . ObjectMeta {
Name : remappedName ,
} ,
}
}
return & v1 . Namespace {
2023-02-02 08:51:23 +00:00
TypeMeta : metav1 . TypeMeta {
Kind : backupNS . Kind ,
APIVersion : backupNS . APIVersion ,
} ,
2017-11-21 17:24:43 +00:00
ObjectMeta : metav1 . ObjectMeta {
Name : remappedName ,
Labels : backupNS . Labels ,
Annotations : backupNS . Annotations ,
} ,
Spec : backupNS . Spec ,
}
}
2022-11-03 20:21:21 +00:00
func ( ctx * restoreContext ) getApplicableActions ( groupResource schema . GroupResource , namespace string ) [ ] framework . RestoreItemResolvedActionV2 {
var actions [ ] framework . RestoreItemResolvedActionV2
2021-12-10 17:53:47 +00:00
for _ , action := range ctx . restoreItemActions {
if action . ShouldUse ( groupResource , namespace , nil , ctx . log ) {
actions = append ( actions , action )
2019-03-28 19:21:56 +00:00
}
2021-12-10 17:53:47 +00:00
}
return actions
}
2019-03-28 19:21:56 +00:00
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
func ( ctx * restoreContext ) shouldRestore ( name string , pvClient client . Dynamic ) ( bool , error ) {
2018-09-07 14:42:57 +00:00
pvLogger := ctx . log . WithField ( "pvName" , name )
var shouldRestore bool
err := wait . PollImmediate ( time . Second , ctx . resourceTerminatingTimeout , func ( ) ( bool , error ) {
2019-02-11 23:45:04 +00:00
unstructuredPV , err := pvClient . Get ( name , metav1 . GetOptions { } )
2018-09-07 14:42:57 +00:00
if apierrors . IsNotFound ( err ) {
pvLogger . Debug ( "PV not found, safe to restore" )
// PV not found, can safely exit loop and proceed with restore.
shouldRestore = true
return true , nil
}
if err != nil {
return false , errors . Wrapf ( err , "could not retrieve in-cluster copy of PV %s" , name )
}
2019-02-11 23:45:04 +00:00
clusterPV := new ( v1 . PersistentVolume )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( unstructuredPV . Object , clusterPV ) ; err != nil {
return false , errors . Wrap ( err , "error converting PV from unstructured" )
2018-09-07 14:42:57 +00:00
}
2019-02-11 23:45:04 +00:00
if clusterPV . Status . Phase == v1 . VolumeReleased || clusterPV . DeletionTimestamp != nil {
2018-09-07 14:42:57 +00:00
// PV was found and marked for deletion, or it was released; wait for it to go away.
pvLogger . Debugf ( "PV found, but marked for deletion, waiting" )
return false , nil
}
// Check for the namespace and PVC to see if anything that's referencing the PV is deleting.
// If either the namespace or PVC is in a deleting/terminating state, wait for them to finish before
// trying to restore the PV
// Not doing so may result in the underlying PV disappearing but not restoring due to timing issues,
// then the PVC getting restored and showing as lost.
2019-02-11 23:45:04 +00:00
if clusterPV . Spec . ClaimRef == nil {
pvLogger . Debugf ( "PV is not marked for deletion and is not claimed by a PVC" )
return true , nil
2018-09-07 14:42:57 +00:00
}
2019-02-11 23:45:04 +00:00
namespace := clusterPV . Spec . ClaimRef . Namespace
pvcName := clusterPV . Spec . ClaimRef . Name
2018-09-07 14:42:57 +00:00
// Have to create the PVC client here because we don't know what namespace we're using til we get to this point.
// Using a dynamic client since it's easier to mock for testing
pvcResource := metav1 . APIResource { Name : "persistentvolumeclaims" , Namespaced : true }
pvcClient , err := ctx . dynamicFactory . ClientForGroupVersionResource ( schema . GroupVersion { Group : "" , Version : "v1" } , pvcResource , namespace )
if err != nil {
return false , errors . Wrapf ( err , "error getting pvc client" )
}
pvc , err := pvcClient . Get ( pvcName , metav1 . GetOptions { } )
if apierrors . IsNotFound ( err ) {
pvLogger . Debugf ( "PVC %s for PV not found, waiting" , pvcName )
// PVC wasn't found, but the PV still exists, so continue to wait.
return false , nil
}
if err != nil {
return false , errors . Wrapf ( err , "error getting claim %s for persistent volume" , pvcName )
}
if pvc != nil && pvc . GetDeletionTimestamp ( ) != nil {
pvLogger . Debugf ( "PVC for PV marked for deletion, waiting" )
// PVC is still deleting, continue to wait.
return false , nil
}
2021-03-15 22:51:07 +00:00
// Check the namespace associated with the claimRef to see if it's
// deleting/terminating before proceeding.
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
ns , err := ctx . namespaceClient . Get ( go_context . TODO ( ) , namespace , metav1 . GetOptions { } )
2018-09-07 14:42:57 +00:00
if apierrors . IsNotFound ( err ) {
pvLogger . Debugf ( "namespace %s for PV not found, waiting" , namespace )
2021-03-15 22:51:07 +00:00
// Namespace not found but the PV still exists, so continue to wait.
2018-09-07 14:42:57 +00:00
return false , nil
}
if err != nil {
return false , errors . Wrapf ( err , "error getting namespace %s associated with PV %s" , namespace , name )
}
if ns != nil && ( ns . GetDeletionTimestamp ( ) != nil || ns . Status . Phase == v1 . NamespaceTerminating ) {
pvLogger . Debugf ( "namespace %s associated with PV is deleting, waiting" , namespace )
2021-03-15 22:51:07 +00:00
// Namespace is in the process of deleting, keep looping.
2018-09-07 14:42:57 +00:00
return false , nil
}
// None of the PV, PVC, or NS are marked for deletion, break the loop.
pvLogger . Debug ( "PV, associated PVC and namespace are not marked for deletion" )
return true , nil
} )
if err == wait . ErrWaitTimeout {
2021-01-22 00:48:33 +00:00
pvLogger . Warn ( "timeout reached waiting for persistent volume to delete" )
2018-09-07 14:42:57 +00:00
}
return shouldRestore , err
}
2021-03-15 22:51:07 +00:00
// crdAvailable waits for a CRD to be available for use before letting the
// restore continue.
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
func ( ctx * restoreContext ) crdAvailable ( name string , crdClient client . Dynamic ) ( bool , error ) {
2020-01-30 17:19:13 +00:00
crdLogger := ctx . log . WithField ( "crdName" , name )
var available bool
2023-02-27 23:13:31 +00:00
err := wait . PollImmediate ( time . Second , ctx . resourceTimeout , func ( ) ( bool , error ) {
2020-01-30 17:19:13 +00:00
unstructuredCRD , err := crdClient . Get ( name , metav1 . GetOptions { } )
if err != nil {
return true , err
}
2021-09-01 05:38:17 +00:00
available , err = kube . IsCRDReady ( unstructuredCRD )
2020-01-30 17:19:13 +00:00
if err != nil {
return true , err
}
if ! available {
crdLogger . Debug ( "CRD not yet ready for use" )
}
2021-03-15 22:51:07 +00:00
// If the CRD is not available, keep polling (false, nil).
// If the CRD is available, break the poll and return to caller (true, nil).
2020-01-30 17:19:13 +00:00
return available , nil
} )
if err == wait . ErrWaitTimeout {
crdLogger . Debug ( "timeout reached waiting for custom resource definition to be ready" )
}
return available , err
}
2023-02-28 23:40:32 +00:00
// itemsAvailable waits for the passed-in additional items to be available for use before letting the restore continue.
func ( ctx * restoreContext ) itemsAvailable ( action framework . RestoreItemResolvedActionV2 , restoreItemOut * velero . RestoreItemActionExecuteOutput ) ( bool , error ) {
// if RestoreItemAction doesn't define set WaitForAdditionalItems, then return true
if ! restoreItemOut . WaitForAdditionalItems {
return true , nil
}
var available bool
timeout := ctx . resourceTimeout
if restoreItemOut . AdditionalItemsReadyTimeout != 0 {
timeout = restoreItemOut . AdditionalItemsReadyTimeout
}
err := wait . PollImmediate ( time . Second , timeout , func ( ) ( bool , error ) {
var err error
available , err = action . AreAdditionalItemsReady ( restoreItemOut . AdditionalItems , ctx . restore )
if err != nil {
return true , err
}
if ! available {
ctx . log . Debug ( "AdditionalItems not yet ready for use" )
}
// If the AdditionalItems are not available, keep polling (false, nil)
// If the AdditionalItems are available, break the poll and return back to caller (true, nil)
return available , nil
} )
if err == wait . ErrWaitTimeout {
ctx . log . Debug ( "timeout reached waiting for AdditionalItems to be ready" )
}
return available , err
}
2023-08-21 15:36:46 +00:00
func getResourceClientKey ( groupResource schema . GroupResource , version , namespace string ) resourceClientKey {
return resourceClientKey {
resource : groupResource . WithVersion ( version ) ,
2019-03-28 19:21:56 +00:00
namespace : namespace ,
}
2023-08-21 15:36:46 +00:00
}
func ( ctx * restoreContext ) getResourceClient ( groupResource schema . GroupResource , obj * unstructured . Unstructured , namespace string ) ( client . Dynamic , error ) {
key := getResourceClientKey ( groupResource , obj . GroupVersionKind ( ) . Version , namespace )
2017-08-02 17:27:17 +00:00
2019-03-28 19:21:56 +00:00
if client , ok := ctx . resourceClients [ key ] ; ok {
return client , nil
}
2017-08-02 17:27:17 +00:00
2021-03-15 22:51:07 +00:00
// Initialize client for this resource. We need metadata from an object to
// do this.
2019-03-28 19:21:56 +00:00
ctx . log . Infof ( "Getting client for %v" , obj . GroupVersionKind ( ) )
2018-06-22 19:32:03 +00:00
2019-03-28 19:21:56 +00:00
resource := metav1 . APIResource {
Namespaced : len ( namespace ) > 0 ,
Name : groupResource . Resource ,
}
2018-06-28 14:06:55 +00:00
2019-03-28 19:21:56 +00:00
client , err := ctx . dynamicFactory . ClientForGroupVersionResource ( obj . GroupVersionKind ( ) . GroupVersion ( ) , resource , namespace )
if err != nil {
return nil , err
}
2018-06-22 19:32:03 +00:00
2019-03-28 19:21:56 +00:00
ctx . resourceClients [ key ] = client
return client , nil
}
2018-06-22 19:32:03 +00:00
2023-08-21 15:36:46 +00:00
// if new informer is created, non-nil factory is returned
func ( ctx * restoreContext ) getGenericInformerInternal ( groupResource schema . GroupResource , version , namespace string ) ( informers . GenericInformer , * informerFactoryWithContext ) {
var returnFactory * informerFactoryWithContext
key := getResourceClientKey ( groupResource , version , namespace )
factoryWithContext , ok := ctx . dynamicInformerFactories [ key . namespace ]
if ! ok {
factory := ctx . dynamicFactory . DynamicSharedInformerFactoryForNamespace ( namespace )
informerContext , informerCancel := signal . NotifyContext ( go_context . Background ( ) , os . Interrupt )
factoryWithContext = & informerFactoryWithContext {
factory : factory ,
context : informerContext ,
cancel : informerCancel ,
}
ctx . dynamicInformerFactories [ key . namespace ] = factoryWithContext
}
informer , ok := ctx . resourceInformers [ key ]
if ! ok {
ctx . log . Infof ( "[debug] Creating factory for %s in namespace %s" , key . resource , key . namespace )
informer = factoryWithContext . factory . ForResource ( key . resource )
factoryWithContext . factory . Start ( factoryWithContext . context . Done ( ) )
ctx . resourceInformers [ key ] = informer
returnFactory = factoryWithContext
}
return informer , returnFactory
}
func ( ctx * restoreContext ) getGenericInformer ( groupResource schema . GroupResource , version , namespace string ) informers . GenericInformer {
informer , factoryWithContext := ctx . getGenericInformerInternal ( groupResource , version , namespace )
if factoryWithContext != nil {
factoryWithContext . factory . WaitForCacheSync ( factoryWithContext . context . Done ( ) )
}
return informer
}
func ( ctx * restoreContext ) getResourceLister ( groupResource schema . GroupResource , obj * unstructured . Unstructured , namespace string ) cache . GenericNamespaceLister {
informer := ctx . getGenericInformer ( groupResource , obj . GroupVersionKind ( ) . Version , namespace )
if namespace == "" {
return informer . Lister ( )
} else {
return informer . Lister ( ) . ByNamespace ( namespace )
}
}
2019-03-28 19:21:56 +00:00
func getResourceID ( groupResource schema . GroupResource , namespace , name string ) string {
if namespace == "" {
return fmt . Sprintf ( "%s/%s" , groupResource . String ( ) , name )
}
2018-06-22 19:32:03 +00:00
2019-03-28 19:21:56 +00:00
return fmt . Sprintf ( "%s/%s/%s" , groupResource . String ( ) , namespace , name )
}
2018-09-07 14:42:57 +00:00
2023-08-21 15:36:46 +00:00
func ( ctx * restoreContext ) getResource ( groupResource schema . GroupResource , obj * unstructured . Unstructured , namespace , name string ) ( * unstructured . Unstructured , error ) {
lister := ctx . getResourceLister ( groupResource , obj , namespace )
clusterObj , err := lister . Get ( name )
if err != nil {
return nil , errors . Wrapf ( err , "error getting resource from lister for %s, %s/%s" , groupResource , namespace , name )
}
u , ok := clusterObj . ( * unstructured . Unstructured )
if ! ok {
ctx . log . WithError ( errors . WithStack ( fmt . Errorf ( "expected *unstructured.Unstructured but got %T" , u ) ) ) . Error ( "unable to understand entry returned from client" )
return nil , fmt . Errorf ( "expected *unstructured.Unstructured but got %T" , u )
}
return u , nil
}
2023-04-25 05:50:52 +00:00
func ( ctx * restoreContext ) restoreItem ( obj * unstructured . Unstructured , groupResource schema . GroupResource , namespace string ) ( results . Result , results . Result , bool ) {
warnings , errs := results . Result { } , results . Result { }
2023-02-28 23:40:32 +00:00
// itemExists bool is used to determine whether to include this item in the "wait for additional items" list
itemExists := false
2019-03-28 19:21:56 +00:00
resourceID := getResourceID ( groupResource , namespace , obj . GetName ( ) )
2017-08-02 17:27:17 +00:00
2019-06-27 20:48:50 +00:00
// Check if group/resource should be restored. We need to do this here since
// this method may be getting called for an additional item which is a group/resource
// that's excluded.
if ! ctx . resourceIncludesExcludes . ShouldInclude ( groupResource . String ( ) ) {
ctx . log . WithFields ( logrus . Fields {
"namespace" : obj . GetNamespace ( ) ,
"name" : obj . GetName ( ) ,
"groupResource" : groupResource . String ( ) ,
} ) . Info ( "Not restoring item because resource is excluded" )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-06-27 20:48:50 +00:00
}
// Check if namespace/cluster-scoped resource should be restored. We need
// to do this here since this method may be getting called for an additional
// item which is in a namespace that's excluded, or which is cluster-scoped
2019-09-26 01:01:16 +00:00
// and should be excluded. Note that we're checking the object's namespace (
// via obj.GetNamespace()) instead of the namespace parameter, because we want
// to check the *original* namespace, not the remapped one if it's been remapped.
2019-06-27 20:48:50 +00:00
if namespace != "" {
2023-09-01 08:52:24 +00:00
if ! ctx . namespaceIncludesExcludes . ShouldInclude ( obj . GetNamespace ( ) ) && ! ctx . resourceMustHave . Has ( groupResource . String ( ) ) {
2019-06-27 20:48:50 +00:00
ctx . log . WithFields ( logrus . Fields {
"namespace" : obj . GetNamespace ( ) ,
"name" : obj . GetName ( ) ,
"groupResource" : groupResource . String ( ) ,
} ) . Info ( "Not restoring item because namespace is excluded" )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-06-27 20:48:50 +00:00
}
2020-04-03 14:57:01 +00:00
2021-03-15 22:51:07 +00:00
// If the namespace scoped resource should be restored, ensure that the
// namespace into which the resource is being restored into exists.
2020-04-03 14:57:01 +00:00
// This is the *remapped* namespace that we are ensuring exists.
2020-08-21 00:24:29 +00:00
nsToEnsure := getNamespace ( ctx . log , archive . GetItemFilePath ( ctx . restoreDir , "namespaces" , "" , obj . GetNamespace ( ) ) , namespace )
2023-04-26 06:05:22 +00:00
_ , nsCreated , err := kube . EnsureNamespaceExistsAndIsReady ( nsToEnsure , ctx . namespaceClient , ctx . resourceTerminatingTimeout )
if err != nil {
2020-04-03 14:57:01 +00:00
errs . AddVeleroError ( err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2023-04-26 06:05:22 +00:00
}
// Add the newly created namespace to the list of restored items.
if nsCreated {
itemKey := itemKey {
resource : resourceKey ( nsToEnsure ) ,
namespace : nsToEnsure . Namespace ,
name : nsToEnsure . Name ,
2021-03-04 21:21:44 +00:00
}
2023-04-26 06:05:22 +00:00
ctx . restoredItems [ itemKey ] = restoredItemStatus { action : itemRestoreResultCreated , itemExists : true }
2020-04-03 14:57:01 +00:00
}
2019-06-27 20:48:50 +00:00
} else {
if boolptr . IsSetToFalse ( ctx . restore . Spec . IncludeClusterResources ) {
ctx . log . WithFields ( logrus . Fields {
"namespace" : obj . GetNamespace ( ) ,
"name" : obj . GetName ( ) ,
"groupResource" : groupResource . String ( ) ,
} ) . Info ( "Not restoring item because it's cluster-scoped" )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-06-27 20:48:50 +00:00
}
}
2021-03-15 22:51:07 +00:00
// Make a copy of object retrieved from backup to make it available unchanged
//inside restore actions.
2019-03-28 19:21:56 +00:00
itemFromBackup := obj . DeepCopy ( )
2017-08-02 17:27:17 +00:00
2019-03-28 19:21:56 +00:00
complete , err := isCompleted ( obj , groupResource )
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , fmt . Errorf ( "error checking completion of %q: %v" , resourceID , err ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
if complete {
ctx . log . Infof ( "%s is complete - skipping" , kube . NamespaceAndName ( obj ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2018-06-22 19:32:03 +00:00
2019-03-28 19:21:56 +00:00
name := obj . GetName ( )
2018-06-22 19:32:03 +00:00
2021-03-15 22:51:07 +00:00
// Check if we've already restored this itemKey.
2023-02-02 08:51:23 +00:00
itemKey := itemKey {
resource : resourceKey ( obj ) ,
namespace : namespace ,
name : name ,
2019-03-28 19:21:56 +00:00
}
2023-02-28 23:40:32 +00:00
if prevRestoredItemStatus , exists := ctx . restoredItems [ itemKey ] ; exists {
2019-03-28 19:21:56 +00:00
ctx . log . Infof ( "Skipping %s because it's already been restored." , resourceID )
2023-02-28 23:40:32 +00:00
itemExists = prevRestoredItemStatus . itemExists
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2023-02-28 23:40:32 +00:00
ctx . restoredItems [ itemKey ] = restoredItemStatus { itemExists : itemExists }
2023-02-02 08:51:23 +00:00
defer func ( ) {
2023-02-28 23:40:32 +00:00
itemStatus := ctx . restoredItems [ itemKey ]
2023-02-02 08:51:23 +00:00
// the action field is set explicitly
2023-02-28 23:40:32 +00:00
if len ( itemStatus . action ) > 0 {
2023-02-02 08:51:23 +00:00
return
}
// no action specified, and no warnings and errors
if errs . IsEmpty ( ) && warnings . IsEmpty ( ) {
2023-02-28 23:40:32 +00:00
itemStatus . action = itemRestoreResultSkipped
ctx . restoredItems [ itemKey ] = itemStatus
2023-02-02 08:51:23 +00:00
return
}
// others are all failed
2023-02-28 23:40:32 +00:00
itemStatus . action = itemRestoreResultFailed
ctx . restoredItems [ itemKey ] = itemStatus
2023-02-02 08:51:23 +00:00
} ( )
2018-06-22 19:32:03 +00:00
2021-03-15 22:51:07 +00:00
// TODO: move to restore item action if/when we add a ShouldRestore() method
// to the interface.
2019-03-28 19:21:56 +00:00
if groupResource == kuberesource . Pods && obj . GetAnnotations ( ) [ v1 . MirrorPodAnnotationKey ] != "" {
ctx . log . Infof ( "Not restoring pod because it's a mirror pod" )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
resourceClient , err := ctx . getResourceClient ( groupResource , obj , namespace )
if err != nil {
2020-02-03 18:56:57 +00:00
errs . AddVeleroError ( fmt . Errorf ( "error getting resource client for namespace %q, resource %q: %v" , namespace , & groupResource , err ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
if groupResource == kuberesource . PersistentVolumes {
2019-08-06 20:40:35 +00:00
switch {
case hasSnapshot ( name , ctx . volumeSnapshots ) :
2020-02-10 20:17:15 +00:00
oldName := obj . GetName ( )
2019-08-27 23:42:38 +00:00
shouldRenamePV , err := shouldRenamePV ( ctx , obj , resourceClient )
2019-08-06 20:40:35 +00:00
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2018-06-22 19:32:03 +00:00
}
2021-03-15 22:51:07 +00:00
// Check to see if the claimRef.namespace field needs to be remapped,
// and do so if necessary.
2020-10-15 23:57:43 +00:00
_ , err = remapClaimRefNS ( ctx , obj )
if err != nil {
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2020-10-15 23:57:43 +00:00
}
2019-08-27 23:42:38 +00:00
var shouldRestoreSnapshot bool
if ! shouldRenamePV {
// Check if the PV exists in the cluster before attempting to create
// a volume from the snapshot, in order to avoid orphaned volumes (GH #609)
shouldRestoreSnapshot , err = ctx . shouldRestore ( name , resourceClient )
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , errors . Wrapf ( err , "error waiting on in-cluster persistentvolume %s" , name ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-08-27 23:42:38 +00:00
}
} else {
2021-03-15 22:51:07 +00:00
// If we're renaming the PV, we're going to give it a new random name,
2019-08-27 23:42:38 +00:00
// so we can assume it doesn't already exist in the cluster and therefore
// we should proceed with restoring from snapshot.
shouldRestoreSnapshot = true
}
2019-08-06 20:40:35 +00:00
if shouldRestoreSnapshot {
2021-03-15 22:51:07 +00:00
// Reset the PV's binding status so that Kubernetes can properly
// associate it with the restored PVC.
2020-10-15 23:57:43 +00:00
obj = resetVolumeBindingInfo ( obj )
2021-03-15 22:51:07 +00:00
// Even if we're renaming the PV, obj still has the old name here, because the pvRestorer
2019-08-27 23:42:38 +00:00
// uses the original name to look up metadata about the snapshot.
2019-08-06 20:40:35 +00:00
ctx . log . Infof ( "Restoring persistent volume from snapshot." )
updatedObj , err := ctx . pvRestorer . executePVAction ( obj )
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , fmt . Errorf ( "error executing PVAction for %s: %v" , resourceID , err ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-08-06 20:40:35 +00:00
}
obj = updatedObj
2020-09-01 21:25:13 +00:00
2021-03-15 22:51:07 +00:00
// VolumeSnapshotter has modified the PV name, we should rename the PV.
2020-09-01 21:25:13 +00:00
if oldName != obj . GetName ( ) {
shouldRenamePV = true
}
2019-08-06 20:40:35 +00:00
}
2019-08-27 23:42:38 +00:00
if shouldRenamePV {
2020-02-10 20:17:15 +00:00
var pvName string
if oldName == obj . GetName ( ) {
2021-03-15 22:51:07 +00:00
// pvRestorer hasn't modified the PV name, we need to rename the PV.
2020-02-10 20:17:15 +00:00
pvName , err = ctx . pvRenamer ( oldName )
if err != nil {
errs . Add ( namespace , errors . Wrapf ( err , "error renaming PV" ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2020-02-10 20:17:15 +00:00
}
} else {
2021-03-15 22:51:07 +00:00
// VolumeSnapshotter could have modified the PV name through
// function `SetVolumeID`,
2020-02-10 20:17:15 +00:00
pvName = obj . GetName ( )
2019-12-17 18:23:58 +00:00
}
2019-08-27 23:42:38 +00:00
2020-02-10 20:17:15 +00:00
ctx . renamedPVs [ oldName ] = pvName
obj . SetName ( pvName )
2023-08-21 15:36:46 +00:00
name = pvName
2019-08-27 23:42:38 +00:00
2021-03-15 22:51:07 +00:00
// Add the original PV name as an annotation.
2019-08-27 23:42:38 +00:00
annotations := obj . GetAnnotations ( )
if annotations == nil {
annotations = map [ string ] string { }
}
annotations [ "velero.io/original-pv-name" ] = oldName
obj . SetAnnotations ( annotations )
}
2022-10-12 11:12:40 +00:00
case hasPodVolumeBackup ( obj , ctx ) :
ctx . log . Infof ( "Dynamically re-provisioning persistent volume because it has a pod volume backup to be restored." )
2019-03-28 19:21:56 +00:00
ctx . pvsToProvision . Insert ( name )
2019-08-06 20:40:35 +00:00
2021-03-15 22:51:07 +00:00
// Return early because we don't want to restore the PV itself, we
// want to dynamically re-provision it.
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-08-27 23:42:38 +00:00
2019-08-06 20:40:35 +00:00
case hasDeleteReclaimPolicy ( obj . Object ) :
ctx . log . Infof ( "Dynamically re-provisioning persistent volume because it doesn't have a snapshot and its reclaim policy is Delete." )
ctx . pvsToProvision . Insert ( name )
2018-11-02 08:42:58 +00:00
2021-03-15 22:51:07 +00:00
// Return early because we don't want to restore the PV itself, we
// want to dynamically re-provision it.
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-08-27 23:42:38 +00:00
2019-08-06 20:40:35 +00:00
default :
ctx . log . Infof ( "Restoring persistent volume as-is because it doesn't have a snapshot and its reclaim policy is not Delete." )
2017-08-02 17:27:17 +00:00
2021-11-10 01:30:16 +00:00
// Check to see if the claimRef.namespace field needs to be remapped, and do so if necessary.
_ , err = remapClaimRefNS ( ctx , obj )
if err != nil {
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2021-11-10 01:30:16 +00:00
}
2020-10-15 23:57:43 +00:00
obj = resetVolumeBindingInfo ( obj )
2021-03-15 22:51:07 +00:00
// We call the pvRestorer here to clear out the PV's claimRef.UID,
// so it can be re-claimed when its PVC is restored and gets a new UID.
2019-03-28 19:21:56 +00:00
updatedObj , err := ctx . pvRestorer . executePVAction ( obj )
2017-11-21 17:24:43 +00:00
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , fmt . Errorf ( "error executing PVAction for %s: %v" , resourceID , err ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2017-11-21 17:24:43 +00:00
}
2019-03-28 19:21:56 +00:00
obj = updatedObj
}
}
2017-11-21 17:24:43 +00:00
2022-05-17 13:39:14 +00:00
objStatus , statusFieldExists , statusFieldErr := unstructured . NestedFieldCopy ( obj . Object , "status" )
// Clear out non-core metadata fields and status.
if obj , err = resetMetadataAndStatus ( obj ) ; err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2017-11-21 17:24:43 +00:00
2022-03-28 17:52:01 +00:00
ctx . log . Infof ( "restore status includes excludes: %+v" , ctx . resourceStatusIncludesExcludes )
2019-03-28 19:21:56 +00:00
for _ , action := range ctx . getApplicableActions ( groupResource , namespace ) {
2021-12-10 17:53:47 +00:00
if ! action . Selector . Matches ( labels . Set ( obj . GetLabels ( ) ) ) {
2022-05-05 12:26:50 +00:00
continue
2017-08-02 17:27:17 +00:00
}
2023-10-17 12:57:11 +00:00
// If the EnableCSI feature is not enabled, but the executing action is from CSI plugin, skip the action.
if csiutil . ShouldSkipAction ( action . Name ( ) ) {
ctx . log . Infof ( "Skip action %s for resource %s:%s/%s, because the CSI feature is not enabled. Feature setting is %s." ,
action . Name ( ) , groupResource . String ( ) , obj . GetNamespace ( ) , obj . GetName ( ) , features . Serialize ( ) )
continue
}
2019-03-28 19:21:56 +00:00
ctx . log . Infof ( "Executing item action for %v" , & groupResource )
2022-10-12 16:41:50 +00:00
executeOutput , err := action . RestoreItemAction . Execute ( & velero . RestoreItemActionExecuteInput {
2019-03-28 19:21:56 +00:00
Item : obj ,
ItemFromBackup : itemFromBackup ,
Restore : ctx . restore ,
} )
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , fmt . Errorf ( "error preparing %s: %v" , resourceID , err ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2018-03-29 18:50:30 +00:00
}
2017-08-02 17:27:17 +00:00
2023-03-10 17:40:20 +00:00
// If async plugin started async operation, add it to the ItemOperations list
if executeOutput . OperationID != "" {
resourceIdentifier := velero . ResourceIdentifier {
GroupResource : groupResource ,
Namespace : namespace ,
Name : name ,
}
now := metav1 . Now ( )
newOperation := itemoperation . RestoreOperation {
Spec : itemoperation . RestoreOperationSpec {
RestoreName : ctx . restore . Name ,
RestoreUID : string ( ctx . restore . UID ) ,
RestoreItemAction : action . RestoreItemAction . Name ( ) ,
ResourceIdentifier : resourceIdentifier ,
OperationID : executeOutput . OperationID ,
} ,
Status : itemoperation . OperationStatus {
2023-03-21 22:54:03 +00:00
Phase : itemoperation . OperationPhaseNew ,
2023-03-10 17:40:20 +00:00
Created : & now ,
} ,
}
itemOperList := ctx . itemOperationsList
* itemOperList = append ( * itemOperList , & newOperation )
}
2019-04-04 19:39:54 +00:00
if executeOutput . SkipRestore {
ctx . log . Infof ( "Skipping restore of %s: %v because a registered plugin discarded it" , obj . GroupVersionKind ( ) . Kind , name )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-04-04 19:39:54 +00:00
}
2019-03-28 19:21:56 +00:00
unstructuredObj , ok := executeOutput . UpdatedItem . ( * unstructured . Unstructured )
if ! ok {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , fmt . Errorf ( "%s: unexpected type %T" , resourceID , executeOutput . UpdatedItem ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
obj = unstructuredObj
2023-02-28 23:40:32 +00:00
var filteredAdditionalItems [ ] velero . ResourceIdentifier
2019-03-28 19:21:56 +00:00
for _ , additionalItem := range executeOutput . AdditionalItems {
2020-08-21 00:24:29 +00:00
itemPath := archive . GetItemFilePath ( ctx . restoreDir , additionalItem . GroupResource . String ( ) , additionalItem . Namespace , additionalItem . Name )
2019-03-28 19:21:56 +00:00
if _ , err := ctx . fileSystem . Stat ( itemPath ) ; err != nil {
ctx . log . WithError ( err ) . WithFields ( logrus . Fields {
"additionalResource" : additionalItem . GroupResource . String ( ) ,
"additionalResourceNamespace" : additionalItem . Namespace ,
"additionalResourceName" : additionalItem . Name ,
} ) . Warn ( "unable to restore additional item" )
2020-02-03 18:56:57 +00:00
warnings . Add ( additionalItem . Namespace , err )
2017-08-02 17:27:17 +00:00
2018-04-11 15:07:43 +00:00
continue
2018-03-29 18:50:30 +00:00
}
2019-03-28 19:21:56 +00:00
additionalResourceID := getResourceID ( additionalItem . GroupResource , additionalItem . Namespace , additionalItem . Name )
2020-08-21 00:24:29 +00:00
additionalObj , err := archive . Unmarshal ( ctx . fileSystem , itemPath )
2018-04-11 15:07:43 +00:00
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , errors . Wrapf ( err , "error restoring additional item %s" , additionalResourceID ) )
2018-04-11 15:07:43 +00:00
}
2019-03-28 19:21:56 +00:00
additionalItemNamespace := additionalItem . Namespace
if additionalItemNamespace != "" {
if remapped , ok := ctx . restore . Spec . NamespaceMapping [ additionalItemNamespace ] ; ok {
additionalItemNamespace = remapped
2018-04-11 15:07:43 +00:00
}
2018-03-29 18:50:30 +00:00
}
2019-03-28 19:21:56 +00:00
2023-02-28 23:40:32 +00:00
w , e , additionalItemExists := ctx . restoreItem ( additionalObj , additionalItem . GroupResource , additionalItemNamespace )
if additionalItemExists {
filteredAdditionalItems = append ( filteredAdditionalItems , additionalItem )
}
2020-02-03 18:56:57 +00:00
warnings . Merge ( & w )
errs . Merge ( & e )
2017-08-02 17:27:17 +00:00
}
2023-02-28 23:40:32 +00:00
executeOutput . AdditionalItems = filteredAdditionalItems
available , err := ctx . itemsAvailable ( action , executeOutput )
if err != nil {
errs . Add ( namespace , errors . Wrapf ( err , "error verifying additional items are ready to use" ) )
} else if ! available {
2023-04-25 05:50:52 +00:00
errs . Add ( namespace , fmt . Errorf ( "additional items for %s are not ready to use" , resourceID ) )
2023-02-28 23:40:32 +00:00
}
2019-03-28 19:21:56 +00:00
}
// This comes after running item actions because we have built-in actions that restore
// a PVC's associated PV (if applicable). As part of the PV being restored, the 'pvsToProvision'
// set may be inserted into, and this needs to happen *before* running the following block of logic.
//
// The side effect of this is that it's impossible for a user to write a restore item action that
// adjusts this behavior (i.e. of resetting the PVC for dynamic provisioning if it claims a PV with
// a reclaim policy of Delete and no snapshot). If/when that becomes an issue for users, we can
// revisit. This would be easier with a multi-pass restore process.
if groupResource == kuberesource . PersistentVolumeClaims {
pvc := new ( v1 . PersistentVolumeClaim )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pvc ) ; err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2020-10-15 23:57:43 +00:00
if pvc . Spec . VolumeName != "" {
2022-10-12 11:12:40 +00:00
// This used to only happen with PVB volumes, but now always remove this binding metadata
2020-10-15 23:57:43 +00:00
obj = resetVolumeBindingInfo ( obj )
2022-10-12 11:12:40 +00:00
// This is the case for PVB volumes, where we need to actually have an empty volume created instead of restoring one.
2020-10-15 23:57:43 +00:00
// The assumption is that any PV in pvsToProvision doesn't have an associated snapshot.
if ctx . pvsToProvision . Has ( pvc . Spec . VolumeName ) {
ctx . log . Infof ( "Resetting PersistentVolumeClaim %s/%s for dynamic provisioning" , namespace , name )
unstructured . RemoveNestedField ( obj . Object , "spec" , "volumeName" )
}
2019-03-28 19:21:56 +00:00
}
2019-08-27 23:42:38 +00:00
if newName , ok := ctx . renamedPVs [ pvc . Spec . VolumeName ] ; ok {
ctx . log . Infof ( "Updating persistent volume claim %s/%s to reference renamed persistent volume (%s -> %s)" , namespace , name , pvc . Spec . VolumeName , newName )
if err := unstructured . SetNestedField ( obj . Object , newName , "spec" , "volumeName" ) ; err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-08-27 23:42:38 +00:00
}
}
2019-03-28 19:21:56 +00:00
}
2023-09-08 09:59:46 +00:00
if ctx . resourceModifiers != nil {
if errList := ctx . resourceModifiers . ApplyResourceModifierRules ( obj , groupResource . String ( ) , ctx . log ) ; errList != nil {
for _ , err := range errList {
errs . Add ( namespace , err )
}
}
}
2021-03-15 22:51:07 +00:00
// Necessary because we may have remapped the namespace if the namespace is
// blank, don't create the key.
2019-03-28 19:21:56 +00:00
originalNamespace := obj . GetNamespace ( )
if namespace != "" {
obj . SetNamespace ( namespace )
}
2021-03-15 22:51:07 +00:00
// Label the resource with the restore's name and the restored backup's name
2019-03-28 19:21:56 +00:00
// for easy identification of all cluster resources created by this restore
2021-03-15 22:51:07 +00:00
// and which backup they came from.
2019-03-28 19:21:56 +00:00
addRestoreLabels ( obj , ctx . restore . Name , ctx . restore . Spec . BackupName )
2023-08-10 02:44:15 +00:00
// The object apiVersion might get modified by a RestorePlugin so we need to
// get a new client to reflect updated resource path.
2023-08-10 10:46:14 +00:00
newGR := schema . GroupResource { Group : obj . GroupVersionKind ( ) . Group , Resource : groupResource . Resource }
resourceClient , err = ctx . getResourceClient ( newGR , obj , obj . GetNamespace ( ) )
2023-08-10 02:44:15 +00:00
if err != nil {
errs . AddVeleroError ( fmt . Errorf ( "error getting updated resource client for namespace %q, resource %q: %v" , namespace , & groupResource , err ) )
return warnings , errs , itemExists
}
2019-03-28 19:21:56 +00:00
ctx . log . Infof ( "Attempting to restore %s: %v" , obj . GroupVersionKind ( ) . Kind , name )
2023-08-21 15:36:46 +00:00
// check if we want to treat the error as a warning, in some cases the creation call might not get executed due to object API validations
// and Velero might not get the already exists error type but in reality the object already exists
var fromCluster , createdObj * unstructured . Unstructured
var restoreErr error
// only attempt Get before Create if using informer cache, otherwise this will slow down restore into
// new namespace
if ! ctx . disableInformerCache {
ctx . log . Debugf ( "Checking for existence %s: %v" , obj . GroupVersionKind ( ) . Kind , name )
fromCluster , err = ctx . getResource ( groupResource , obj , namespace , name )
}
if err != nil || fromCluster == nil {
// couldn't find the resource, attempt to create
ctx . log . Debugf ( "Creating %s: %v" , obj . GroupVersionKind ( ) . Kind , name )
createdObj , restoreErr = resourceClient . Create ( obj )
if restoreErr == nil {
itemExists = true
ctx . restoredItems [ itemKey ] = restoredItemStatus { action : itemRestoreResultCreated , itemExists : itemExists }
}
2023-02-02 08:51:23 +00:00
}
2023-08-21 15:36:46 +00:00
2022-01-11 01:55:49 +00:00
isAlreadyExistsError , err := isAlreadyExistsError ( ctx , obj , restoreErr , resourceClient )
if err != nil {
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2022-01-11 01:55:49 +00:00
}
2022-08-22 13:50:30 +00:00
2022-09-06 14:43:57 +00:00
if restoreErr != nil {
2022-08-22 13:50:30 +00:00
// check for the existence of the object in cluster, if no error then it implies that object exists
2022-09-06 14:43:57 +00:00
// and if err then we want to judge whether there is an existing error in the previous creation.
// if so, we will return the 'get' error.
// otherwise, we will return the original creation error.
2023-08-21 15:36:46 +00:00
if ! ctx . disableInformerCache {
fromCluster , err = ctx . getResource ( groupResource , obj , namespace , name )
} else {
fromCluster , err = resourceClient . Get ( name , metav1 . GetOptions { } )
}
2022-09-06 14:43:57 +00:00
if err != nil && isAlreadyExistsError {
ctx . log . Errorf ( "Error retrieving in-cluster version of %s: %v" , kube . NamespaceAndName ( obj ) , err )
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2022-08-22 13:50:30 +00:00
}
}
2022-09-06 14:43:57 +00:00
if fromCluster != nil {
2023-02-28 23:40:32 +00:00
itemExists = true
itemStatus := ctx . restoredItems [ itemKey ]
itemStatus . itemExists = itemExists
ctx . restoredItems [ itemKey ] = itemStatus
2021-03-15 22:51:07 +00:00
// Remove insubstantial metadata.
2022-05-17 13:39:14 +00:00
fromCluster , err = resetMetadataAndStatus ( fromCluster )
2019-03-28 19:21:56 +00:00
if err != nil {
ctx . log . Infof ( "Error trying to reset metadata for %s: %v" , kube . NamespaceAndName ( obj ) , err )
2020-02-03 18:56:57 +00:00
warnings . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2017-08-02 17:27:17 +00:00
}
2021-03-15 22:51:07 +00:00
// We know the object from the cluster won't have the backup/restore name
// labels, so copy them from the object we attempted to restore.
2019-03-28 19:21:56 +00:00
labels := obj . GetLabels ( )
2019-08-06 20:17:36 +00:00
addRestoreLabels ( fromCluster , labels [ velerov1api . RestoreNameLabel ] , labels [ velerov1api . BackupNameLabel ] )
2022-02-10 07:16:29 +00:00
fromClusterWithLabels := fromCluster . DeepCopy ( ) // saving the in-cluster object so that we can create label patch if overall patch fails
2019-03-28 19:21:56 +00:00
2022-05-17 13:39:14 +00:00
if ! equality . Semantic . DeepEqual ( fromCluster , obj ) {
2019-03-28 19:21:56 +00:00
switch groupResource {
case kuberesource . ServiceAccounts :
desired , err := mergeServiceAccounts ( fromCluster , obj )
if err != nil {
ctx . log . Infof ( "error merging secrets for ServiceAccount %s: %v" , kube . NamespaceAndName ( obj ) , err )
2020-02-03 18:56:57 +00:00
warnings . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
patchBytes , err := generatePatch ( fromCluster , desired )
if err != nil {
ctx . log . Infof ( "error generating patch for ServiceAccount %s: %v" , kube . NamespaceAndName ( obj ) , err )
2020-02-03 18:56:57 +00:00
warnings . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
if patchBytes == nil {
2021-03-15 22:51:07 +00:00
// In-cluster and desired state are the same, so move on to
// the next item.
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
_ , err = resourceClient . Patch ( name , patchBytes )
if err != nil {
2020-02-03 18:56:57 +00:00
warnings . Add ( namespace , err )
2022-02-10 07:16:29 +00:00
// check if there is existingResourcePolicy and if it is set to update policy
if len ( ctx . restore . Spec . ExistingResourcePolicy ) > 0 && ctx . restore . Spec . ExistingResourcePolicy == velerov1api . PolicyTypeUpdate {
// remove restore labels so that we apply the latest backup/restore names on the object via patch
removeRestoreLabels ( fromCluster )
//try patching just the backup/restore labels
warningsFromUpdate , errsFromUpdate := ctx . updateBackupRestoreLabels ( fromCluster , fromClusterWithLabels , namespace , resourceClient )
warnings . Merge ( & warningsFromUpdate )
errs . Merge ( & errsFromUpdate )
}
2019-03-28 19:21:56 +00:00
} else {
2023-02-28 23:40:32 +00:00
itemStatus . action = itemRestoreResultUpdated
ctx . restoredItems [ itemKey ] = itemStatus
2019-03-28 19:21:56 +00:00
ctx . log . Infof ( "ServiceAccount %s successfully updated" , kube . NamespaceAndName ( obj ) )
}
default :
2022-02-10 07:16:29 +00:00
// check for the presence of existingResourcePolicy
if len ( ctx . restore . Spec . ExistingResourcePolicy ) > 0 {
resourcePolicy := ctx . restore . Spec . ExistingResourcePolicy
ctx . log . Infof ( "restore API has resource policy defined %s , executing restore workflow accordingly for changed resource %s %s" , resourcePolicy , fromCluster . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) )
// existingResourcePolicy is set as none, add warning
if resourcePolicy == velerov1api . PolicyTypeNone {
2023-04-26 06:05:22 +00:00
e := errors . Errorf ( "could not restore, %s %q already exists. Warning: the in-cluster version is different than the backed-up version" ,
2022-02-10 07:16:29 +00:00
obj . GetKind ( ) , obj . GetName ( ) )
warnings . Add ( namespace , e )
// existingResourcePolicy is set as update, attempt patch on the resource and add warning if it fails
} else if resourcePolicy == velerov1api . PolicyTypeUpdate {
// processing update as existingResourcePolicy
warningsFromUpdateRP , errsFromUpdateRP := ctx . processUpdateResourcePolicy ( fromCluster , fromClusterWithLabels , obj , namespace , resourceClient )
2023-02-02 08:51:23 +00:00
if warningsFromUpdateRP . IsEmpty ( ) && errsFromUpdateRP . IsEmpty ( ) {
2023-02-28 23:40:32 +00:00
itemStatus . action = itemRestoreResultUpdated
ctx . restoredItems [ itemKey ] = itemStatus
2023-02-02 08:51:23 +00:00
}
2022-02-10 07:16:29 +00:00
warnings . Merge ( & warningsFromUpdateRP )
errs . Merge ( & errsFromUpdateRP )
}
} else {
// Preserved Velero behavior when existingResourcePolicy is not specified by the user
2023-04-26 06:05:22 +00:00
e := errors . Errorf ( "could not restore, %s %q already exists. Warning: the in-cluster version is different than the backed-up version" ,
2022-02-10 07:16:29 +00:00
obj . GetKind ( ) , obj . GetName ( ) )
warnings . Add ( namespace , e )
}
2018-02-28 01:35:35 +00:00
}
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2022-02-10 07:16:29 +00:00
//update backup/restore labels on the unchanged resources if existingResourcePolicy is set as update
if ctx . restore . Spec . ExistingResourcePolicy == velerov1api . PolicyTypeUpdate {
resourcePolicy := ctx . restore . Spec . ExistingResourcePolicy
ctx . log . Infof ( "restore API has resource policy defined %s , executing restore workflow accordingly for unchanged resource %s %s " , resourcePolicy , obj . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) )
// remove restore labels so that we apply the latest backup/restore names on the object via patch
removeRestoreLabels ( fromCluster )
// try updating the backup/restore labels for the in-cluster object
warningsFromUpdate , errsFromUpdate := ctx . updateBackupRestoreLabels ( fromCluster , obj , namespace , resourceClient )
warnings . Merge ( & warningsFromUpdate )
errs . Merge ( & errsFromUpdate )
}
2019-10-01 20:47:21 +00:00
ctx . log . Infof ( "Restore of %s, %v skipped: it already exists in the cluster and is the same as the backed up version" , obj . GroupVersionKind ( ) . Kind , name )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2021-03-15 22:51:07 +00:00
// Error was something other than an AlreadyExists.
2019-03-28 19:21:56 +00:00
if restoreErr != nil {
2020-12-09 17:32:34 +00:00
ctx . log . Errorf ( "error restoring %s: %+v" , name , restoreErr )
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , fmt . Errorf ( "error restoring %s: %v" , resourceID , restoreErr ) )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2019-03-28 19:21:56 +00:00
}
2022-06-15 16:57:37 +00:00
shouldRestoreStatus := ctx . resourceStatusIncludesExcludes != nil && ctx . resourceStatusIncludesExcludes . ShouldInclude ( groupResource . String ( ) )
2022-05-17 13:39:14 +00:00
if shouldRestoreStatus && statusFieldErr != nil {
err := fmt . Errorf ( "could not get status to be restored %s: %v" , kube . NamespaceAndName ( obj ) , statusFieldErr )
ctx . log . Errorf ( err . Error ( ) )
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2022-05-17 13:39:14 +00:00
}
2022-06-15 16:57:37 +00:00
ctx . log . Debugf ( "status field for %s: exists: %v, should restore: %v" , groupResource , statusFieldExists , shouldRestoreStatus )
2022-05-12 14:58:47 +00:00
// if it should restore status, run a UpdateStatus
2022-05-17 13:39:14 +00:00
if statusFieldExists && shouldRestoreStatus {
if err := unstructured . SetNestedField ( obj . Object , objStatus , "status" ) ; err != nil {
ctx . log . Errorf ( "could not set status field %s: %v" , kube . NamespaceAndName ( obj ) , err )
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2022-05-17 13:39:14 +00:00
}
2022-03-28 17:52:01 +00:00
obj . SetResourceVersion ( createdObj . GetResourceVersion ( ) )
2022-05-12 14:58:47 +00:00
updated , err := resourceClient . UpdateStatus ( obj , metav1 . UpdateOptions { } )
if err != nil {
2022-06-15 16:57:37 +00:00
ctx . log . Infof ( "status field update failed %s: %v" , kube . NamespaceAndName ( obj ) , err )
2022-05-12 14:58:47 +00:00
warnings . Add ( namespace , err )
} else {
createdObj = updated
}
}
2023-02-20 02:26:45 +00:00
// restore the managedFields
withoutManagedFields := createdObj . DeepCopy ( )
createdObj . SetManagedFields ( obj . GetManagedFields ( ) )
patchBytes , err := generatePatch ( withoutManagedFields , createdObj )
if err != nil {
ctx . log . Errorf ( "error generating patch for managed fields %s: %v" , kube . NamespaceAndName ( obj ) , err )
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2023-02-20 02:26:45 +00:00
}
if patchBytes != nil {
if _ , err = resourceClient . Patch ( name , patchBytes ) ; err != nil {
ctx . log . Errorf ( "error patch for managed fields %s: %v" , kube . NamespaceAndName ( obj ) , err )
2023-04-12 09:17:13 +00:00
if ! apierrors . IsNotFound ( err ) {
errs . Add ( namespace , err )
return warnings , errs , itemExists
}
} else {
ctx . log . Infof ( "the managed fields for %s is patched" , kube . NamespaceAndName ( obj ) )
2023-02-20 02:26:45 +00:00
}
}
2021-06-17 18:00:37 +00:00
if groupResource == kuberesource . Pods {
pod := new ( v1 . Pod )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pod ) ; err != nil {
errs . Add ( namespace , err )
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2021-06-17 18:00:37 +00:00
}
2022-03-23 07:29:52 +00:00
// Do not create podvolumerestore when current restore excludes pv/pvc
if ctx . resourceIncludesExcludes . ShouldInclude ( kuberesource . PersistentVolumeClaims . String ( ) ) &&
ctx . resourceIncludesExcludes . ShouldInclude ( kuberesource . PersistentVolumes . String ( ) ) &&
2022-08-05 09:15:38 +00:00
len ( podvolume . GetVolumeBackupsForPod ( ctx . podVolumeBackups , pod , originalNamespace ) ) > 0 {
2021-06-17 18:00:37 +00:00
restorePodVolumeBackups ( ctx , createdObj , originalNamespace )
}
2017-08-02 17:27:17 +00:00
}
2020-09-08 18:33:15 +00:00
if groupResource == kuberesource . Pods {
ctx . waitExec ( createdObj )
}
2020-01-30 17:19:13 +00:00
// Wait for a CRD to be available for instantiating resources
// before continuing.
if groupResource == kuberesource . CustomResourceDefinitions {
available , err := ctx . crdAvailable ( name , resourceClient )
if err != nil {
2020-02-03 18:56:57 +00:00
errs . Add ( namespace , errors . Wrapf ( err , "error verifying custom resource definition is ready to use" ) )
2020-01-30 17:19:13 +00:00
} else if ! available {
2023-04-25 05:50:52 +00:00
errs . Add ( namespace , fmt . Errorf ( "the CRD %s is not available to use for custom resources" , name ) )
2020-01-30 17:19:13 +00:00
}
}
2023-02-28 23:40:32 +00:00
return warnings , errs , itemExists
2018-02-28 01:35:35 +00:00
}
2022-01-11 01:55:49 +00:00
func isAlreadyExistsError ( ctx * restoreContext , obj * unstructured . Unstructured , err error , client client . Dynamic ) ( bool , error ) {
2021-11-15 12:25:04 +00:00
if err == nil {
2022-01-11 01:55:49 +00:00
return false , nil
2021-11-15 12:25:04 +00:00
}
if apierrors . IsAlreadyExists ( err ) {
2022-01-11 01:55:49 +00:00
return true , nil
2021-11-15 12:25:04 +00:00
}
2021-12-20 21:42:27 +00:00
// The "invalid value error" or "internal error" rather than "already exists" error returns when restoring nodePort service in the following two cases:
// 1. For NodePort service, the service has nodePort preservation while the same nodePort service already exists. - Get invalid value error
// 2. For LoadBalancer service, the "healthCheckNodePort" already exists. - Get internal error
2021-11-15 12:25:04 +00:00
// If this is the case, the function returns true to avoid reporting error.
// Refer to https://github.com/vmware-tanzu/velero/issues/2308 for more details
2021-12-20 21:42:27 +00:00
if obj . GetKind ( ) != "Service" {
2022-01-11 01:55:49 +00:00
return false , nil
2021-11-15 12:25:04 +00:00
}
statusErr , ok := err . ( * apierrors . StatusError )
if ! ok || statusErr . Status ( ) . Details == nil || len ( statusErr . Status ( ) . Details . Causes ) == 0 {
2022-01-11 01:55:49 +00:00
return false , nil
2021-11-15 12:25:04 +00:00
}
// make sure all the causes are "port allocated" error
for _ , cause := range statusErr . Status ( ) . Details . Causes {
if ! strings . Contains ( cause . Message , "provided port is already allocated" ) {
2022-01-11 01:55:49 +00:00
return false , nil
2021-11-15 12:25:04 +00:00
}
}
2022-01-11 01:55:49 +00:00
2022-09-06 14:43:57 +00:00
// the "already allocated" error may be caused by other services, check whether the expected service exists or not
2022-01-11 01:55:49 +00:00
if _ , err = client . Get ( obj . GetName ( ) , metav1 . GetOptions { } ) ; err != nil {
if apierrors . IsNotFound ( err ) {
ctx . log . Debugf ( "Service %s not found" , kube . NamespaceAndName ( obj ) )
return false , nil
}
return false , errors . Wrapf ( err , "Unable to get the service %s while checking the NodePort is already allocated error" , kube . NamespaceAndName ( obj ) )
2021-11-15 12:25:04 +00:00
}
2022-01-11 01:55:49 +00:00
ctx . log . Infof ( "Service %s exists, ignore the provided port is already allocated error" , kube . NamespaceAndName ( obj ) )
return true , nil
2021-11-15 12:25:04 +00:00
}
2021-03-15 22:51:07 +00:00
// shouldRenamePV returns a boolean indicating whether a persistent volume should
// be given a new name before being restored, or an error if this cannot be determined.
// A persistent volume will be given a new name if and only if (a) a PV with the
// original name already exists in-cluster, and (b) in the backup, the PV is claimed
// by a PVC in a namespace that's being remapped during the restore.
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
func shouldRenamePV ( ctx * restoreContext , obj * unstructured . Unstructured , client client . Dynamic ) ( bool , error ) {
2019-08-27 23:42:38 +00:00
if len ( ctx . restore . Spec . NamespaceMapping ) == 0 {
ctx . log . Debugf ( "Persistent volume does not need to be renamed because restore is not remapping any namespaces" )
return false , nil
}
pv := new ( v1 . PersistentVolume )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . Object , pv ) ; err != nil {
return false , errors . Wrapf ( err , "error converting persistent volume to structured" )
}
if pv . Spec . ClaimRef == nil {
ctx . log . Debugf ( "Persistent volume does not need to be renamed because it's not claimed" )
return false , nil
}
if _ , ok := ctx . restore . Spec . NamespaceMapping [ pv . Spec . ClaimRef . Namespace ] ; ! ok {
ctx . log . Debugf ( "Persistent volume does not need to be renamed because it's not claimed by a PVC in a namespace that's being remapped" )
return false , nil
}
_ , err := client . Get ( pv . Name , metav1 . GetOptions { } )
switch {
case apierrors . IsNotFound ( err ) :
ctx . log . Debugf ( "Persistent volume does not need to be renamed because it does not exist in the cluster" )
return false , nil
case err != nil :
return false , errors . Wrapf ( err , "error checking if persistent volume exists in the cluster" )
}
2021-03-15 22:51:07 +00:00
// No error returned: the PV was found in-cluster, so we need to rename it.
2019-08-27 23:42:38 +00:00
return true , nil
}
2021-03-15 22:51:07 +00:00
// remapClaimRefNS remaps a PersistentVolume's claimRef.Namespace based on a
// restore's NamespaceMappings, if necessary. Returns true if the namespace was
// remapped, false if it was not required.
2022-10-18 13:08:55 +00:00
func remapClaimRefNS ( ctx * restoreContext , obj * unstructured . Unstructured ) ( bool , error ) { //nolint:unparam
2020-10-15 23:57:43 +00:00
if len ( ctx . restore . Spec . NamespaceMapping ) == 0 {
ctx . log . Debug ( "Persistent volume does not need to have the claimRef.namespace remapped because restore is not remapping any namespaces" )
return false , nil
}
2021-03-15 22:51:07 +00:00
// Conversion to the real type here is more readable than all the error checking
// involved with reading each field individually.
2020-10-15 23:57:43 +00:00
pv := new ( v1 . PersistentVolume )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . Object , pv ) ; err != nil {
return false , errors . Wrapf ( err , "error converting persistent volume to structured" )
}
if pv . Spec . ClaimRef == nil {
Fix various typos found by codespell (#3057)
By running the following command:
codespell -S .git,*.png,*.jpg,*.woff,*.ttf,*.gif,*.ico -L \
iam,aks,ist,bridget,ue
Signed-off-by: Mateusz Gozdek <mgozdekof@gmail.com>
2020-11-10 16:48:35 +00:00
ctx . log . Debugf ( "Persistent volume does not need to have the claimRef.namespace remapped because it's not claimed" )
2020-10-15 23:57:43 +00:00
return false , nil
}
targetNS , ok := ctx . restore . Spec . NamespaceMapping [ pv . Spec . ClaimRef . Namespace ]
if ! ok {
ctx . log . Debugf ( "Persistent volume does not need to have the claimRef.namespace remapped because it's not claimed by a PVC in a namespace that's being remapped" )
return false , nil
}
err := unstructured . SetNestedField ( obj . Object , targetNS , "spec" , "claimRef" , "namespace" )
if err != nil {
return false , err
}
ctx . log . Debug ( "Persistent volume's namespace was updated" )
return true , nil
}
2019-08-06 20:17:36 +00:00
// restorePodVolumeBackups restores the PodVolumeBackups for the given restored pod
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
func restorePodVolumeBackups ( ctx * restoreContext , createdObj * unstructured . Unstructured , originalNamespace string ) {
2022-10-12 11:12:40 +00:00
if ctx . podVolumeRestorer == nil {
ctx . log . Warn ( "No pod volume restorer, not restoring pod's volumes" )
2019-08-06 20:17:36 +00:00
} else {
2022-10-12 11:12:40 +00:00
ctx . podVolumeWaitGroup . Add ( 1 )
2020-01-15 17:27:21 +00:00
go func ( ) {
2021-03-15 22:51:07 +00:00
// Done() will only be called after all errors have been successfully
2022-10-12 11:12:40 +00:00
// sent on the ctx.podVolumeErrs channel
defer ctx . podVolumeWaitGroup . Done ( )
2020-01-15 17:27:21 +00:00
2019-08-06 20:17:36 +00:00
pod := new ( v1 . Pod )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( createdObj . UnstructuredContent ( ) , & pod ) ; err != nil {
ctx . log . WithError ( err ) . Error ( "error converting unstructured pod" )
2022-10-12 11:12:40 +00:00
ctx . podVolumeErrs <- err
2020-01-15 17:27:21 +00:00
return
2019-08-06 20:17:36 +00:00
}
2022-08-05 09:15:38 +00:00
data := podvolume . RestoreData {
2019-08-06 20:17:36 +00:00
Restore : ctx . restore ,
Pod : pod ,
PodVolumeBackups : ctx . podVolumeBackups ,
SourceNamespace : originalNamespace ,
BackupLocation : ctx . backup . Spec . StorageLocation ,
}
2022-10-12 11:12:40 +00:00
if errs := ctx . podVolumeRestorer . RestorePodVolumes ( data ) ; errs != nil {
ctx . log . WithError ( kubeerrs . NewAggregate ( errs ) ) . Error ( "unable to successfully complete pod volume restores of pod's volumes" )
2019-08-06 20:17:36 +00:00
2020-01-15 17:27:21 +00:00
for _ , err := range errs {
2022-10-12 11:12:40 +00:00
ctx . podVolumeErrs <- err
2020-01-15 17:27:21 +00:00
}
}
} ( )
2019-08-06 20:17:36 +00:00
}
}
2021-03-15 22:51:07 +00:00
// waitExec executes hooks in a restored pod's containers when they become ready.
2020-09-08 18:33:15 +00:00
func ( ctx * restoreContext ) waitExec ( createdObj * unstructured . Unstructured ) {
ctx . hooksWaitGroup . Add ( 1 )
go func ( ) {
// Done() will only be called after all errors have been successfully sent
2022-10-12 11:12:40 +00:00
// on the ctx.podVolumeErrs channel.
2020-09-08 18:33:15 +00:00
defer ctx . hooksWaitGroup . Done ( )
pod := new ( v1 . Pod )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( createdObj . UnstructuredContent ( ) , & pod ) ; err != nil {
ctx . log . WithError ( err ) . Error ( "error converting unstructured pod" )
ctx . hooksErrs <- err
return
}
execHooksByContainer , err := hook . GroupRestoreExecHooks (
ctx . resourceRestoreHooks ,
pod ,
ctx . log ,
)
if err != nil {
ctx . log . WithError ( err ) . Errorf ( "error getting exec hooks for pod %s/%s" , pod . Namespace , pod . Name )
ctx . hooksErrs <- err
return
}
if errs := ctx . waitExecHookHandler . HandleHooks ( ctx . hooksContext , ctx . log , pod , execHooksByContainer ) ; len ( errs ) > 0 {
ctx . log . WithError ( kubeerrs . NewAggregate ( errs ) ) . Error ( "unable to successfully execute post-restore hooks" )
ctx . hooksCancelFunc ( )
for _ , err := range errs {
2021-03-15 22:51:07 +00:00
// Errors are already logged in the HandleHooks method.
2020-09-08 18:33:15 +00:00
ctx . hooksErrs <- err
}
}
} ( )
}
2019-08-06 20:40:35 +00:00
func hasSnapshot ( pvName string , snapshots [ ] * volume . Snapshot ) bool {
for _ , snapshot := range snapshots {
if snapshot . Spec . PersistentVolumeName == pvName {
return true
}
}
return false
}
2022-10-12 11:12:40 +00:00
func hasPodVolumeBackup ( unstructuredPV * unstructured . Unstructured , ctx * restoreContext ) bool {
2019-08-06 20:40:35 +00:00
if len ( ctx . podVolumeBackups ) == 0 {
return false
}
pv := new ( v1 . PersistentVolume )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( unstructuredPV . Object , pv ) ; err != nil {
ctx . log . WithError ( err ) . Warnf ( "Unable to convert PV from unstructured to structured" )
return false
}
if pv . Spec . ClaimRef == nil {
return false
}
var found bool
for _ , pvb := range ctx . podVolumeBackups {
2022-08-05 09:15:38 +00:00
if pvb . Spec . Pod . Namespace == pv . Spec . ClaimRef . Namespace && pvb . GetAnnotations ( ) [ podvolume . PVCNameAnnotation ] == pv . Spec . ClaimRef . Name {
2019-08-06 20:40:35 +00:00
found = true
break
}
}
return found
}
2018-10-23 18:44:05 +00:00
func hasDeleteReclaimPolicy ( obj map [ string ] interface { } ) bool {
2019-01-07 22:07:53 +00:00
policy , _ , _ := unstructured . NestedString ( obj , "spec" , "persistentVolumeReclaimPolicy" )
return policy == string ( v1 . PersistentVolumeReclaimDelete )
2018-10-23 18:44:05 +00:00
}
2021-03-15 22:51:07 +00:00
// resetVolumeBindingInfo clears any necessary metadata out of a PersistentVolume
// or PersistentVolumeClaim that would make it ineligible to be re-bound by Velero.
2020-10-15 23:57:43 +00:00
func resetVolumeBindingInfo ( obj * unstructured . Unstructured ) * unstructured . Unstructured {
2021-03-15 22:51:07 +00:00
// Clean out ClaimRef UID and resourceVersion, since this information is
// highly unique.
2020-10-15 23:57:43 +00:00
unstructured . RemoveNestedField ( obj . Object , "spec" , "claimRef" , "uid" )
unstructured . RemoveNestedField ( obj . Object , "spec" , "claimRef" , "resourceVersion" )
2021-03-15 22:51:07 +00:00
// Clear out any annotations used by the Kubernetes PV controllers to track
// bindings.
2020-10-15 23:57:43 +00:00
annotations := obj . GetAnnotations ( )
2021-03-15 22:51:07 +00:00
// Upon restore, this new PV will look like a statically provisioned, manually-
// bound volume rather than one bound by the controller, so remove the annotation
// that signals that a controller bound it.
2022-01-11 06:47:57 +00:00
delete ( annotations , kube . KubeAnnBindCompleted )
2021-03-15 22:51:07 +00:00
// Remove the annotation that signals that the PV is already bound; we want
// the PV(C) controller to take the two objects and bind them again.
2022-01-11 06:47:57 +00:00
delete ( annotations , kube . KubeAnnBoundByController )
2020-10-15 23:57:43 +00:00
2021-03-15 22:51:07 +00:00
// GetAnnotations returns a copy, so we have to set them again.
2020-10-15 23:57:43 +00:00
obj . SetAnnotations ( annotations )
return obj
}
2022-03-28 17:52:01 +00:00
func resetMetadata ( obj * unstructured . Unstructured ) ( * unstructured . Unstructured , error ) {
2019-01-07 22:07:53 +00:00
res , ok := obj . Object [ "metadata" ]
if ! ok {
return nil , errors . New ( "metadata not found" )
}
metadata , ok := res . ( map [ string ] interface { } )
if ! ok {
return nil , errors . Errorf ( "metadata was of type %T, expected map[string]interface{}" , res )
2017-11-21 17:24:43 +00:00
}
for k := range metadata {
2017-12-21 21:23:48 +00:00
switch k {
2023-02-20 02:26:45 +00:00
case "generateName" , "selfLink" , "uid" , "resourceVersion" , "generation" , "creationTimestamp" , "deletionTimestamp" ,
"deletionGracePeriodSeconds" , "ownerReferences" :
2017-12-21 21:23:48 +00:00
delete ( metadata , k )
2017-11-21 17:24:43 +00:00
}
}
2022-03-28 17:52:01 +00:00
return obj , nil
}
2017-11-21 17:24:43 +00:00
2022-05-23 20:53:32 +00:00
func resetStatus ( obj * unstructured . Unstructured ) {
unstructured . RemoveNestedField ( obj . UnstructuredContent ( ) , "status" )
2017-11-21 17:24:43 +00:00
}
2022-05-17 13:39:14 +00:00
func resetMetadataAndStatus ( obj * unstructured . Unstructured ) ( * unstructured . Unstructured , error ) {
2022-05-23 20:53:32 +00:00
_ , err := resetMetadata ( obj )
2022-05-17 13:39:14 +00:00
if err != nil {
return nil , err
}
2022-05-23 20:53:32 +00:00
resetStatus ( obj )
return obj , nil
2022-05-17 13:39:14 +00:00
}
2021-03-15 22:51:07 +00:00
// addRestoreLabels labels the provided object with the restore name and the
// restored backup's name.
2018-08-08 23:51:33 +00:00
func addRestoreLabels ( obj metav1 . Object , restoreName , backupName string ) {
2017-08-02 17:27:17 +00:00
labels := obj . GetLabels ( )
if labels == nil {
labels = make ( map [ string ] string )
}
2019-08-06 20:17:36 +00:00
labels [ velerov1api . BackupNameLabel ] = label . GetValidName ( backupName )
labels [ velerov1api . RestoreNameLabel ] = label . GetValidName ( restoreName )
2018-08-08 23:33:09 +00:00
2017-08-02 17:27:17 +00:00
obj . SetLabels ( labels )
}
2021-03-15 22:51:07 +00:00
// isCompleted returns whether or not an object is considered completed. Used to
// identify whether or not an object should be restored. Only Jobs or Pods are
// considered.
2018-04-26 20:07:50 +00:00
func isCompleted ( obj * unstructured . Unstructured , groupResource schema . GroupResource ) ( bool , error ) {
switch groupResource {
2018-05-11 19:40:19 +00:00
case kuberesource . Pods :
2018-04-26 20:07:50 +00:00
phase , _ , err := unstructured . NestedString ( obj . UnstructuredContent ( ) , "status" , "phase" )
if err != nil {
return false , errors . WithStack ( err )
}
if phase == string ( v1 . PodFailed ) || phase == string ( v1 . PodSucceeded ) {
return true , nil
}
2018-05-11 19:40:19 +00:00
case kuberesource . Jobs :
2018-04-26 20:07:50 +00:00
ct , found , err := unstructured . NestedString ( obj . UnstructuredContent ( ) , "status" , "completionTime" )
if err != nil {
return false , errors . WithStack ( err )
}
if found && ct != "" {
return true , nil
}
}
2021-03-15 22:51:07 +00:00
// Assume any other resource isn't complete and can be restored.
2018-04-26 20:07:50 +00:00
return false , nil
}
2021-03-04 21:21:44 +00:00
2021-03-15 22:51:07 +00:00
// restoreableResource represents map of individual items of each resource
// identifier grouped by their original namespaces.
2021-03-04 21:21:44 +00:00
type restoreableResource struct {
resource string
selectedItemsByNamespace map [ string ] [ ] restoreableItem
totalItems int
}
2021-03-15 22:51:07 +00:00
// restoreableItem represents an item by its target namespace contains enough
// information required to restore the item.
2021-03-04 21:21:44 +00:00
type restoreableItem struct {
path string
targetNamespace string
name string
2023-08-21 15:36:46 +00:00
version string // used for initializing informer cache
2021-03-04 21:21:44 +00:00
}
2021-03-15 22:51:07 +00:00
// getOrderedResourceCollection iterates over list of ordered resource
// identifiers, applies resource include/exclude criteria, and Kubernetes
// selectors to make a list of resources to be actually restored preserving the
// original order.
2021-06-19 07:03:11 +00:00
func ( ctx * restoreContext ) getOrderedResourceCollection (
backupResources map [ string ] * archive . ResourceItems ,
restoreResourceCollection [ ] restoreableResource ,
processedResources sets . String ,
2022-10-31 08:28:06 +00:00
resourcePriorities Priorities ,
2021-06-19 07:03:11 +00:00
includeAllResources bool ,
2023-04-25 05:50:52 +00:00
) ( [ ] restoreableResource , sets . String , results . Result , results . Result ) {
var warnings , errs results . Result
2021-03-15 22:51:07 +00:00
// Iterate through an ordered list of resources to restore, checking each
// one to see if it should be restored. Note that resources *may* be in this
// list twice, i.e. once due to being a prioritized resource, and once due
// to being in the backup tarball. We can't de-dupe this upfront, because
// it's possible that items in the prioritized resources list may not be
// fully resolved group-resource strings (e.g. may be specified as "po"
// instead of "pods"), and we don't want to fully resolve them via discovery
// until we reach them in the loop, because it is possible that the
// resource/API itself is being restored via a custom resource definition,
// meaning it's not available via discovery prior to beginning the restore.
2021-03-04 21:21:44 +00:00
//
2021-03-15 22:51:07 +00:00
// Since we keep track of the fully-resolved group-resources that we *have*
// restored, we won't try to restore a resource twice even if it's in the
// ordered list twice.
2021-06-19 07:03:11 +00:00
var resourceList [ ] string
if includeAllResources {
resourceList = getOrderedResources ( resourcePriorities , backupResources )
} else {
2022-10-31 08:28:06 +00:00
resourceList = resourcePriorities . HighPriorities
2021-06-19 07:03:11 +00:00
}
for _ , resource := range resourceList {
2021-03-04 21:21:44 +00:00
// try to resolve the resource via discovery to a complete group/version/resource
gvr , _ , err := ctx . discoveryHelper . ResourceFor ( schema . ParseGroupResource ( resource ) . WithVersion ( "" ) )
if err != nil {
ctx . log . WithField ( "resource" , resource ) . Infof ( "Skipping restore of resource because it cannot be resolved via discovery" )
continue
}
groupResource := gvr . GroupResource ( )
2021-03-15 22:51:07 +00:00
// Check if we've already restored this resource (this would happen if
// the resource we're currently looking at was already restored because
// it was a prioritized resource, and now we're looking at it as part of
// the backup contents).
2021-03-04 21:21:44 +00:00
if processedResources . Has ( groupResource . String ( ) ) {
ctx . log . WithField ( "resource" , groupResource . String ( ) ) . Debugf ( "Skipping restore of resource because it's already been processed" )
continue
}
2021-03-15 22:51:07 +00:00
// Check if the resource should be restored according to the resource
// includes/excludes.
2021-03-04 21:21:44 +00:00
if ! ctx . resourceIncludesExcludes . ShouldInclude ( groupResource . String ( ) ) {
ctx . log . WithField ( "resource" , groupResource . String ( ) ) . Infof ( "Skipping restore of resource because the restore spec excludes it" )
continue
}
2021-03-15 22:51:07 +00:00
// We don't want to explicitly restore namespace API objs because we'll handle
2021-03-04 21:21:44 +00:00
// them as a special case prior to restoring anything into them
if groupResource == kuberesource . Namespaces {
continue
}
2021-03-15 22:51:07 +00:00
// Check if the resource is present in the backup
2021-03-04 21:21:44 +00:00
resourceList := backupResources [ groupResource . String ( ) ]
if resourceList == nil {
ctx . log . WithField ( "resource" , groupResource . String ( ) ) . Debugf ( "Skipping restore of resource because it's not present in the backup tarball" )
continue
}
2021-03-15 22:51:07 +00:00
// Iterate through each namespace that contains instances of the
// resource and append to the list of to-be restored resources.
2021-03-04 21:21:44 +00:00
for namespace , items := range resourceList . ItemsByNamespace {
2023-09-01 08:52:24 +00:00
if namespace != "" && ! ctx . namespaceIncludesExcludes . ShouldInclude ( namespace ) && ! ctx . resourceMustHave . Has ( groupResource . String ( ) ) {
2021-03-04 21:21:44 +00:00
ctx . log . Infof ( "Skipping namespace %s" , namespace )
continue
}
// get target namespace to restore into, if different
// from source namespace
targetNamespace := namespace
if target , ok := ctx . restore . Spec . NamespaceMapping [ namespace ] ; ok {
targetNamespace = target
}
if targetNamespace == "" && boolptr . IsSetToFalse ( ctx . restore . Spec . IncludeClusterResources ) {
ctx . log . Infof ( "Skipping resource %s because it's cluster-scoped" , resource )
continue
}
if targetNamespace == "" && ! boolptr . IsSetToTrue ( ctx . restore . Spec . IncludeClusterResources ) && ! ctx . namespaceIncludesExcludes . IncludeEverything ( ) {
ctx . log . Infof ( "Skipping resource %s because it's cluster-scoped and only specific namespaces are included in the restore" , resource )
continue
}
res , w , e := ctx . getSelectedRestoreableItems ( groupResource . String ( ) , targetNamespace , namespace , items )
warnings . Merge ( & w )
errs . Merge ( & e )
2021-03-15 22:51:07 +00:00
restoreResourceCollection = append ( restoreResourceCollection , res )
2021-03-04 21:21:44 +00:00
}
// record that we've restored the resource
processedResources . Insert ( groupResource . String ( ) )
}
2021-06-19 07:03:11 +00:00
return restoreResourceCollection , processedResources , warnings , errs
2021-03-04 21:21:44 +00:00
}
2021-03-15 22:51:07 +00:00
// getSelectedRestoreableItems applies Kubernetes selectors on individual items
// of each resource type to create a list of items which will be actually
// restored.
2023-04-25 05:50:52 +00:00
func ( ctx * restoreContext ) getSelectedRestoreableItems ( resource , targetNamespace , originalNamespace string , items [ ] string ) ( restoreableResource , results . Result , results . Result ) {
warnings , errs := results . Result { } , results . Result { }
2021-03-15 22:51:07 +00:00
restorable := restoreableResource {
resource : resource ,
}
if restorable . selectedItemsByNamespace == nil {
restorable . selectedItemsByNamespace = make ( map [ string ] [ ] restoreableItem )
2021-03-04 21:21:44 +00:00
}
if targetNamespace != "" {
ctx . log . Infof ( "Resource '%s' will be restored into namespace '%s'" , resource , targetNamespace )
} else {
ctx . log . Infof ( "Resource '%s' will be restored at cluster scope" , resource )
}
2021-03-15 22:51:07 +00:00
// If the APIGroupVersionsFeatureFlag is enabled, the item path will be
// updated to include the API group version that was chosen for restore. For
// example, for "horizontalpodautoscalers.autoscaling", if v2beta1 is chosen
// to be restored, then "horizontalpodautoscalers.autoscaling/v2beta1" will
// be part of item path. Different versions would only have been stored
// if the APIGroupVersionsFeatureFlag was enabled during backup. The
// chosenGrpVersToRestore map would only be populated if
// APIGroupVersionsFeatureFlag was enabled for restore and the minimum
// required backup format version has been met.
cgv , ok := ctx . chosenGrpVersToRestore [ resource ]
if ok {
resource = filepath . Join ( resource , cgv . Dir )
}
2021-03-04 21:21:44 +00:00
for _ , item := range items {
itemPath := archive . GetItemFilePath ( ctx . restoreDir , resource , originalNamespace , item )
obj , err := archive . Unmarshal ( ctx . fileSystem , itemPath )
if err != nil {
2021-03-15 22:51:07 +00:00
errs . Add (
targetNamespace ,
fmt . Errorf (
"error decoding %q: %v" ,
strings . Replace ( itemPath , ctx . restoreDir + "/" , "" , - 1 ) ,
err ,
) ,
)
2021-03-04 21:21:44 +00:00
continue
}
if ! ctx . selector . Matches ( labels . Set ( obj . GetLabels ( ) ) ) {
continue
}
2022-02-15 14:52:47 +00:00
// Processing OrLabelSelectors when specified in the restore request. LabelSelectors as well as OrLabelSelectors
// cannot co-exist, only one of them can be specified
var skipItem = false
var skip = 0
ctx . log . Debugf ( "orSelectors specified: %s for item: %s" , ctx . OrSelectors , item )
for _ , s := range ctx . OrSelectors {
if ! s . Matches ( labels . Set ( obj . GetLabels ( ) ) ) {
skip ++
}
if len ( ctx . OrSelectors ) == skip && skip > 0 {
ctx . log . Infof ( "setting skip flag to true for item: %s" , item )
skipItem = true
}
}
if skipItem {
ctx . log . Infof ( "restore orSelector labels did not match, skipping restore of item: %s" , skipItem , item )
continue
}
2021-03-04 21:21:44 +00:00
selectedItem := restoreableItem {
path : itemPath ,
name : item ,
targetNamespace : targetNamespace ,
2023-08-21 15:36:46 +00:00
version : obj . GroupVersionKind ( ) . Version ,
2021-03-04 21:21:44 +00:00
}
2021-03-15 22:51:07 +00:00
restorable . selectedItemsByNamespace [ originalNamespace ] =
append ( restorable . selectedItemsByNamespace [ originalNamespace ] , selectedItem )
restorable . totalItems ++
2021-03-04 21:21:44 +00:00
}
2021-03-15 22:51:07 +00:00
return restorable , warnings , errs
2021-03-04 21:21:44 +00:00
}
2022-02-10 07:16:29 +00:00
// removeRestoreLabels removes the restore name and the
// restored backup's name.
func removeRestoreLabels ( obj metav1 . Object ) {
labels := obj . GetLabels ( )
if labels == nil {
labels = make ( map [ string ] string )
}
labels [ velerov1api . BackupNameLabel ] = ""
labels [ velerov1api . RestoreNameLabel ] = ""
obj . SetLabels ( labels )
}
// updates the backup/restore labels
2023-04-25 05:50:52 +00:00
func ( ctx * restoreContext ) updateBackupRestoreLabels ( fromCluster , fromClusterWithLabels * unstructured . Unstructured , namespace string , resourceClient client . Dynamic ) ( warnings , errs results . Result ) {
2022-02-10 07:16:29 +00:00
patchBytes , err := generatePatch ( fromCluster , fromClusterWithLabels )
if err != nil {
ctx . log . Errorf ( "error generating patch for %s %s: %v" , fromCluster . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) , err )
errs . Add ( namespace , err )
return warnings , errs
}
if patchBytes == nil {
// In-cluster and desired state are the same, so move on to
// the next items
ctx . log . Errorf ( "skipped updating backup/restore labels for %s %s: in-cluster and desired state are the same along-with the labels" , fromCluster . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) )
return warnings , errs
}
// try patching the in-cluster resource (with only latest backup/restore labels)
_ , err = resourceClient . Patch ( fromCluster . GetName ( ) , patchBytes )
if err != nil {
ctx . log . Errorf ( "backup/restore label patch attempt failed for %s %s: %v" , fromCluster . GroupVersionKind ( ) , kube . NamespaceAndName ( fromCluster ) , err )
errs . Add ( namespace , err )
} else {
ctx . log . Infof ( "backup/restore labels successfully updated for %s %s" , fromCluster . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) )
}
return warnings , errs
}
// function to process existingResourcePolicy as update, tries to patch the diff between in-cluster and restore obj first
// if the patch fails then tries to update the backup/restore labels for the in-cluster version
2023-04-25 05:50:52 +00:00
func ( ctx * restoreContext ) processUpdateResourcePolicy ( fromCluster , fromClusterWithLabels , obj * unstructured . Unstructured , namespace string , resourceClient client . Dynamic ) ( warnings , errs results . Result ) {
2022-02-10 07:16:29 +00:00
ctx . log . Infof ( "restore API has existingResourcePolicy defined as update , executing restore workflow accordingly for changed resource %s %s " , obj . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) )
ctx . log . Infof ( "attempting patch on %s %q" , fromCluster . GetKind ( ) , fromCluster . GetName ( ) )
// remove restore labels so that we apply the latest backup/restore names on the object via patch
removeRestoreLabels ( fromCluster )
patchBytes , err := generatePatch ( fromCluster , obj )
if err != nil {
ctx . log . Errorf ( "error generating patch for %s %s: %v" , obj . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( obj ) , err )
errs . Add ( namespace , err )
return warnings , errs
}
if patchBytes == nil {
// In-cluster and desired state are the same, so move on to
// the next items
ctx . log . Errorf ( "skipped updating %s %s: in-cluster and desired state are the same" , fromCluster . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( fromCluster ) )
return warnings , errs
}
// try patching the in-cluster resource (resource diff plus latest backup/restore labels)
_ , err = resourceClient . Patch ( obj . GetName ( ) , patchBytes )
if err != nil {
2022-08-22 13:50:30 +00:00
ctx . log . Warnf ( "patch attempt failed for %s %s: %v" , fromCluster . GroupVersionKind ( ) , kube . NamespaceAndName ( fromCluster ) , err )
2022-02-10 07:16:29 +00:00
warnings . Add ( namespace , err )
// try just patching the labels
warningsFromUpdate , errsFromUpdate := ctx . updateBackupRestoreLabels ( fromCluster , fromClusterWithLabels , namespace , resourceClient )
warnings . Merge ( & warningsFromUpdate )
errs . Merge ( & errsFromUpdate )
} else {
ctx . log . Infof ( "%s %s successfully updated" , obj . GroupVersionKind ( ) . Kind , kube . NamespaceAndName ( obj ) )
}
return warnings , errs
}