2019-06-20 15:06:58 +00:00
/ *
Copyright 2019 the Velero contributors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package restore
import (
"archive/tar"
"bytes"
"compress/gzip"
2019-08-27 22:49:23 +00:00
ctx "context"
2019-06-20 15:06:58 +00:00
"encoding/json"
"fmt"
"io"
2019-06-27 18:57:47 +00:00
"sort"
2019-06-20 15:06:58 +00:00
"testing"
"time"
2019-06-27 22:43:59 +00:00
"github.com/pkg/errors"
2019-06-20 15:06:58 +00:00
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2019-06-21 17:00:49 +00:00
corev1api "k8s.io/api/core/v1"
2019-06-24 22:18:30 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2019-06-20 15:06:58 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-06-27 18:57:47 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-06-20 15:06:58 +00:00
"k8s.io/apimachinery/pkg/runtime"
2019-06-27 22:43:59 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2019-06-20 15:06:58 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2019-07-10 15:59:01 +00:00
discoveryfake "k8s.io/client-go/discovery/fake"
2019-06-27 18:57:47 +00:00
"k8s.io/client-go/dynamic"
2019-07-10 15:59:01 +00:00
kubefake "k8s.io/client-go/kubernetes/fake"
2019-06-24 22:18:30 +00:00
kubetesting "k8s.io/client-go/testing"
2019-06-20 15:06:58 +00:00
2019-09-30 21:26:56 +00:00
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/discovery"
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/restic"
resticmocks "github.com/vmware-tanzu/velero/pkg/restic/mocks"
"github.com/vmware-tanzu/velero/pkg/test"
testutil "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/util/collections"
"github.com/vmware-tanzu/velero/pkg/util/encode"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/volume"
2019-06-20 15:06:58 +00:00
)
2019-06-21 17:00:49 +00:00
// TestRestoreResourceFiltering runs restores with different combinations
// of resource filters (included/excluded resources, included/excluded
// namespaces, label selectors, "include cluster resources" flag), and
// verifies that the set of items created in the API are correct.
// Validation is done by looking at the namespaces/names of the items in
// the API; contents are not checked.
func TestRestoreResourceFiltering ( t * testing . T ) {
2019-06-20 15:06:58 +00:00
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
want map [ * test . APIResource ] [ ] string
} {
{
2019-06-21 17:00:49 +00:00
name : "no filters restores everything" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-20 15:06:58 +00:00
tarball : newTarWriter ( t ) .
2019-06-21 17:00:49 +00:00
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
2019-06-20 15:06:58 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
2019-06-21 17:00:49 +00:00
test . PVs ( ) ,
2019-06-20 15:06:58 +00:00
} ,
want : map [ * test . APIResource ] [ ] string {
2019-06-21 17:00:49 +00:00
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . PVs ( ) : { "/pv-1" , "/pv-2" } ,
2019-06-20 15:06:58 +00:00
} ,
} ,
{
2019-06-21 17:00:49 +00:00
name : "included resources filter only restores resources of those types" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedResources ( "pods" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-20 15:06:58 +00:00
tarball : newTarWriter ( t ) .
2019-06-21 17:00:49 +00:00
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
2019-06-20 15:06:58 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
2019-06-21 17:00:49 +00:00
test . PVs ( ) ,
2019-06-20 15:06:58 +00:00
} ,
want : map [ * test . APIResource ] [ ] string {
2019-06-21 17:00:49 +00:00
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
2019-06-20 15:06:58 +00:00
} ,
} ,
2019-06-21 17:00:49 +00:00
{
name : "excluded resources filter only restores resources not of those types" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . ExcludedResources ( "pvs" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
} ,
} ,
{
name : "included namespaces filter only restores resources in those namespaces" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . Deployments ( ) : { "ns-1/deploy-1" } ,
} ,
} ,
{
name : "excluded namespaces filter only restores resources not in those namespaces" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . ExcludedNamespaces ( "ns-2" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . Deployments ( ) : { "ns-1/deploy-1" } ,
} ,
} ,
{
name : "IncludeClusterResources=false only restores namespaced resources" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludeClusterResources ( false ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . Deployments ( ) : { "ns-1/deploy-1" , "ns-2/deploy-2" } ,
} ,
} ,
{
name : "label selector only restores matching resources" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . LabelSelector ( & metav1 . LabelSelector { MatchLabels : map [ string ] string { "a" : "b" } } ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . ObjectMeta ( builder . WithLabels ( "a" , "b" ) ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . ObjectMeta ( builder . WithLabels ( "a" , "b" ) ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . ObjectMeta ( builder . WithLabels ( "a" , "b" ) ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . ObjectMeta ( builder . WithLabels ( "a" , "c" ) ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . Deployments ( ) : { "ns-2/deploy-2" } ,
test . PVs ( ) : { "/pv-1" } ,
} ,
} ,
{
name : "should include cluster-scoped resources if restoring subset of namespaces and IncludeClusterResources=true" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" ) . IncludeClusterResources ( true ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . Deployments ( ) : { "ns-1/deploy-1" } ,
test . PVs ( ) : { "/pv-1" , "/pv-2" } ,
} ,
} ,
{
name : "should not include cluster-scoped resources if restoring subset of namespaces and IncludeClusterResources=false" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" ) . IncludeClusterResources ( false ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . Deployments ( ) : { "ns-1/deploy-1" } ,
2019-12-12 20:45:41 +00:00
test . PVs ( ) : { } ,
} ,
} ,
{
name : "should not include cluster-scoped resources if restoring subset of namespaces and IncludeClusterResources=nil" ,
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
) .
addItems ( "deployments.apps" ,
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
) .
addItems ( "persistentvolumes" ,
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . Deployments ( ) : { "ns-1/deploy-1" } ,
test . PVs ( ) : { } ,
2019-06-21 17:00:49 +00:00
} ,
} ,
{
name : "should include cluster-scoped resources if restoring all namespaces and IncludeClusterResources=true" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludeClusterResources ( true ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . Deployments ( ) : { "ns-1/deploy-1" , "ns-2/deploy-2" } ,
test . PVs ( ) : { "/pv-1" , "/pv-2" } ,
} ,
} ,
{
name : "should not include cluster-scoped resources if restoring all namespaces and IncludeClusterResources=false" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludeClusterResources ( false ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . Deployments ( ) : { "ns-1/deploy-1" , "ns-2/deploy-2" } ,
} ,
} ,
{
name : "when a wildcard and a specific resource are included, the wildcard takes precedence" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedResources ( "*" , "pods" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . Deployments ( ) : { "ns-1/deploy-1" , "ns-2/deploy-2" } ,
test . PVs ( ) : { "/pv-1" , "/pv-2" } ,
} ,
} ,
{
name : "wildcard excludes are ignored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . ExcludedResources ( "*" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . Deployments ( ) : { "ns-1/deploy-1" , "ns-2/deploy-2" } ,
test . PVs ( ) : { "/pv-1" , "/pv-2" } ,
} ,
} ,
{
name : "unresolvable included resources are ignored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedResources ( "pods" , "unresolvable" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
} ,
} ,
{
name : "unresolvable excluded resources are ignored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . ExcludedResources ( "deployments" , "unresolvable" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-21 17:00:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . Deployments ( ) ,
test . PVs ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
test . PVs ( ) : { "/pv-1" , "/pv-2" } ,
} ,
} ,
{
name : "mirror pods are not restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) . addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . ObjectMeta ( builder . WithAnnotations ( corev1api . MirrorPodAnnotationKey , "foo" ) ) . Result ( ) ) . done ( ) ,
2019-06-21 17:00:49 +00:00
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
want : map [ * test . APIResource ] [ ] string { test . Pods ( ) : { } } ,
} ,
{
name : "service accounts are restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) . addItems ( "serviceaccounts" , builder . ForServiceAccount ( "ns-1" , "sa-1" ) . Result ( ) ) . done ( ) ,
2019-06-21 17:00:49 +00:00
apiResources : [ ] * test . APIResource { test . ServiceAccounts ( ) } ,
want : map [ * test . APIResource ] [ ] string { test . ServiceAccounts ( ) : { "ns-1/sa-1" } } ,
} ,
2019-06-20 15:06:58 +00:00
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . DiscoveryClient . WithAPIResource ( r )
}
require . NoError ( t , h . restorer . discoveryHelper . Refresh ( ) )
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-20 15:06:58 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-20 15:06:58 +00:00
nil , // actions
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
assertAPIContents ( t , h , tc . want )
} )
}
}
2019-06-21 18:38:54 +00:00
// TestRestoreNamespaceMapping runs restores with namespace mappings specified,
// and verifies that the set of items created in the API are in the correct
// namespaces. Validation is done by looking at the namespaces/names of the items
// in the API; contents are not checked.
func TestRestoreNamespaceMapping ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
want map [ * test . APIResource ] [ ] string
} {
{
name : "namespace mappings are applied" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . NamespaceMappings ( "ns-1" , "mapped-ns-1" , "ns-2" , "mapped-ns-2" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-21 18:38:54 +00:00
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
builder . ForPod ( "ns-3" , "pod-3" ) . Result ( ) ,
2019-06-21 18:38:54 +00:00
) .
done ( ) ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "mapped-ns-1/pod-1" , "mapped-ns-2/pod-2" , "ns-3/pod-3" } ,
} ,
} ,
2019-09-26 01:01:16 +00:00
{
name : "namespace mappings are applied when IncludedNamespaces are specified" ,
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" , "ns-2" ) . NamespaceMappings ( "ns-1" , "mapped-ns-1" , "ns-2" , "mapped-ns-2" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
builder . ForPod ( "ns-3" , "pod-3" ) . Result ( ) ,
) .
done ( ) ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "mapped-ns-1/pod-1" , "mapped-ns-2/pod-2" } ,
} ,
} ,
2019-06-21 18:38:54 +00:00
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . DiscoveryClient . WithAPIResource ( r )
}
require . NoError ( t , h . restorer . discoveryHelper . Refresh ( ) )
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-21 18:38:54 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-21 18:38:54 +00:00
nil , // actions
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
assertAPIContents ( t , h , tc . want )
} )
}
}
2019-06-24 22:18:30 +00:00
// TestRestoreResourcePriorities runs restores with resource priorities specified,
// and verifies that the set of items created in the API are created in the expected
// order. Validation is done by adding a Reactor to the fake dynamic client that records
// resource identifiers as they're created, and comparing that to the expected order.
func TestRestoreResourcePriorities ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
resourcePriorities [ ] string
} {
{
name : "resources are restored according to the specified resource priorities" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-24 22:18:30 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ,
builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ,
2019-06-24 22:18:30 +00:00
) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ,
builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ,
2019-06-24 22:18:30 +00:00
) .
addItems ( "deployments.apps" ,
2019-07-31 14:46:48 +00:00
builder . ForDeployment ( "ns-1" , "deploy-1" ) . Result ( ) ,
builder . ForDeployment ( "ns-2" , "deploy-2" ) . Result ( ) ,
2019-06-24 22:18:30 +00:00
) .
addItems ( "serviceaccounts" ,
2019-07-31 14:46:48 +00:00
builder . ForServiceAccount ( "ns-1" , "sa-1" ) . Result ( ) ,
builder . ForServiceAccount ( "ns-2" , "sa-2" ) . Result ( ) ,
2019-06-24 22:18:30 +00:00
) .
addItems ( "persistentvolumeclaims" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) ,
builder . ForPersistentVolumeClaim ( "ns-2" , "pvc-2" ) . Result ( ) ,
2019-06-24 22:18:30 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
test . PVs ( ) ,
test . Deployments ( ) ,
test . ServiceAccounts ( ) ,
} ,
resourcePriorities : [ ] string { "persistentvolumes" , "serviceaccounts" , "pods" , "deployments.apps" } ,
} ,
}
for _ , tc := range tests {
h := newHarness ( t )
h . restorer . resourcePriorities = tc . resourcePriorities
recorder := & createRecorder { t : t }
h . DynamicClient . PrependReactor ( "create" , "*" , recorder . reactor ( ) )
for _ , r := range tc . apiResources {
h . DiscoveryClient . WithAPIResource ( r )
}
require . NoError ( t , h . restorer . discoveryHelper . Refresh ( ) )
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-24 22:18:30 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-24 22:18:30 +00:00
nil , // actions
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
assertResourceCreationOrder ( t , tc . resourcePriorities , recorder . resources )
}
}
2019-06-27 18:57:47 +00:00
// TestInvalidTarballContents runs restores for tarballs that are invalid in some way, and
// verifies that the set of items created in the API and the errors returned are correct.
// Validation is done by looking at the namespaces/names of the items in the API and the
// Result objects returned from the restorer.
func TestInvalidTarballContents ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
want map [ * test . APIResource ] [ ] string
wantErrs Result
} {
{
name : "empty tarball returns an error" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
done ( ) ,
wantErrs : Result {
2019-08-29 01:03:01 +00:00
Velero : [ ] string { "error parsing backup contents: directory \"resources\" does not exist" } ,
2019-06-27 18:57:47 +00:00
} ,
} ,
{
name : "invalid JSON is reported as an error and restore continues" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
add ( "resources/pods/namespaces/ns-1/pod-1.json" , [ ] byte ( "invalid JSON" ) ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-2" ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-2" } ,
} ,
wantErrs : Result {
Namespaces : map [ string ] [ ] string {
"ns-1" : { "error decoding \"resources/pods/namespaces/ns-1/pod-1.json\": invalid character 'i' looking for beginning of value" } ,
} ,
} ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . DiscoveryClient . WithAPIResource ( r )
}
require . NoError ( t , h . restorer . discoveryHelper . Refresh ( ) )
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-27 18:57:47 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-27 18:57:47 +00:00
nil , // actions
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings )
assert . Equal ( t , tc . wantErrs , errs )
assertAPIContents ( t , h , tc . want )
} )
}
}
// TestRestoreItems runs restores of specific items and validates that they are created
// with the expected metadata/spec/status in the API.
func TestRestoreItems ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
want [ ] * test . APIResource
} {
{
name : "metadata other than namespace/name/labels/annotations gets removed" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) .
ObjectMeta (
builder . WithLabels ( "key-1" , "val-1" ) ,
builder . WithAnnotations ( "key-1" , "val-1" ) ,
builder . WithClusterName ( "cluster-1" ) ,
builder . WithFinalizers ( "finalizer-1" ) ,
) .
Result ( ) ,
2019-06-27 18:57:47 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
want : [ ] * test . APIResource {
test . Pods (
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) .
ObjectMeta (
builder . WithLabels ( "key-1" , "val-1" , "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
builder . WithAnnotations ( "key-1" , "val-1" ) ,
) .
Result ( ) ,
2019-06-27 18:57:47 +00:00
) ,
} ,
} ,
{
name : "status gets removed" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
addItems ( "pods" ,
& corev1api . Pod {
TypeMeta : metav1 . TypeMeta {
APIVersion : "v1" ,
Kind : "Pod" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Namespace : "ns-1" ,
Name : "pod-1" ,
} ,
Status : corev1api . PodStatus {
Message : "a non-empty status" ,
} ,
} ,
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
want : [ ] * test . APIResource {
test . Pods (
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . ObjectMeta ( builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
) ,
} ,
} ,
{
name : "object gets labeled with full backup and restore names when they're both shorter than 63 characters" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
want : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . Pods ( builder . ForPod ( "ns-1" , "pod-1" ) . ObjectMeta ( builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ) . Result ( ) ) ,
2019-06-27 18:57:47 +00:00
} ,
} ,
{
name : "object gets labeled with full backup and restore names when they're both equal to 63 characters" ,
2019-07-31 14:46:48 +00:00
restore : builder . ForRestore ( velerov1api . DefaultNamespace , "the-really-long-kube-service-name-that-is-exactly-63-characters" ) .
2019-06-27 18:57:47 +00:00
Backup ( "the-really-long-kube-service-name-that-is-exactly-63-characters" ) .
2019-07-31 14:46:48 +00:00
Result ( ) ,
backup : builder . ForBackup ( velerov1api . DefaultNamespace , "the-really-long-kube-service-name-that-is-exactly-63-characters" ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
want : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . Pods (
builder . ForPod ( "ns-1" , "pod-1" ) .
ObjectMeta (
builder . WithLabels (
"velero.io/backup-name" , "the-really-long-kube-service-name-that-is-exactly-63-characters" ,
"velero.io/restore-name" , "the-really-long-kube-service-name-that-is-exactly-63-characters" ,
) ,
) . Result ( ) ,
) ,
2019-06-27 18:57:47 +00:00
} ,
} ,
{
name : "object gets labeled with shortened backup and restore names when they're both longer than 63 characters" ,
2019-07-31 14:46:48 +00:00
restore : builder . ForRestore ( velerov1api . DefaultNamespace , "the-really-long-kube-service-name-that-is-much-greater-than-63-characters" ) .
2019-06-27 18:57:47 +00:00
Backup ( "the-really-long-kube-service-name-that-is-much-greater-than-63-characters" ) .
2019-07-31 14:46:48 +00:00
Result ( ) ,
backup : builder . ForBackup ( velerov1api . DefaultNamespace , "the-really-long-kube-service-name-that-is-much-greater-than-63-characters" ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . Pods ( ) ,
} ,
want : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . Pods ( builder . ForPod ( "ns-1" , "pod-1" ) .
ObjectMeta (
builder . WithLabels (
"velero.io/backup-name" , "the-really-long-kube-service-name-that-is-much-greater-th8a11b3" ,
"velero.io/restore-name" , "the-really-long-kube-service-name-that-is-much-greater-th8a11b3" ,
) ,
) .
Result ( ) ,
) ,
2019-06-27 18:57:47 +00:00
} ,
} ,
{
name : "no error when service account already exists in cluster and is identical to the backed up one" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "serviceaccounts" , builder . ForServiceAccount ( "ns-1" , "sa-1" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . ServiceAccounts ( builder . ForServiceAccount ( "ns-1" , "sa-1" ) . Result ( ) ) ,
2019-06-27 18:57:47 +00:00
} ,
want : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . ServiceAccounts ( builder . ForServiceAccount ( "ns-1" , "sa-1" ) . Result ( ) ) ,
2019-06-27 18:57:47 +00:00
} ,
} ,
{
name : "service account secrets and image pull secrets are restored when service account already exists in cluster" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
addItems ( "serviceaccounts" , & corev1api . ServiceAccount {
TypeMeta : metav1 . TypeMeta {
APIVersion : "v1" ,
Kind : "ServiceAccount" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Namespace : "ns-1" ,
Name : "sa-1" ,
} ,
Secrets : [ ] corev1api . ObjectReference { { Name : "secret-1" } } ,
ImagePullSecrets : [ ] corev1api . LocalObjectReference { { Name : "pull-secret-1" } } ,
} ) .
done ( ) ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . ServiceAccounts ( builder . ForServiceAccount ( "ns-1" , "sa-1" ) . Result ( ) ) ,
2019-06-27 18:57:47 +00:00
} ,
want : [ ] * test . APIResource {
test . ServiceAccounts ( & corev1api . ServiceAccount {
TypeMeta : metav1 . TypeMeta {
APIVersion : "v1" ,
Kind : "ServiceAccount" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Namespace : "ns-1" ,
Name : "sa-1" ,
} ,
Secrets : [ ] corev1api . ObjectReference { { Name : "secret-1" } } ,
ImagePullSecrets : [ ] corev1api . LocalObjectReference { { Name : "pull-secret-1" } } ,
} ) ,
} ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . addItems ( t , r )
}
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-27 18:57:47 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-27 18:57:47 +00:00
nil , // actions
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
2019-06-27 22:43:59 +00:00
assertRestoredItems ( t , h , tc . want )
2019-06-27 18:57:47 +00:00
} )
}
}
// recordResourcesAction is a restore item action that can be configured
// to run for specific resources/namespaces and simply records the items
// that it is executed for.
type recordResourcesAction struct {
selector velero . ResourceSelector
ids [ ] string
additionalItems [ ] velero . ResourceIdentifier
}
func ( a * recordResourcesAction ) AppliesTo ( ) ( velero . ResourceSelector , error ) {
return a . selector , nil
}
func ( a * recordResourcesAction ) Execute ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
metadata , err := meta . Accessor ( input . Item )
if err != nil {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : a . additionalItems ,
} , err
}
a . ids = append ( a . ids , kubeutil . NamespaceAndName ( metadata ) )
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : a . additionalItems ,
} , nil
}
func ( a * recordResourcesAction ) ForResource ( resource string ) * recordResourcesAction {
a . selector . IncludedResources = append ( a . selector . IncludedResources , resource )
return a
}
func ( a * recordResourcesAction ) ForNamespace ( namespace string ) * recordResourcesAction {
a . selector . IncludedNamespaces = append ( a . selector . IncludedNamespaces , namespace )
return a
}
func ( a * recordResourcesAction ) ForLabelSelector ( selector string ) * recordResourcesAction {
a . selector . LabelSelector = selector
return a
}
func ( a * recordResourcesAction ) WithAdditionalItems ( items [ ] velero . ResourceIdentifier ) * recordResourcesAction {
a . additionalItems = items
return a
}
// TestRestoreActionsRunsForCorrectItems runs restores with restore item actions, and
// verifies that each restore item action is run for the correct set of resources based on its
// AppliesTo() resource selector. Verification is done by using the recordResourcesAction struct,
// which records which resources it's executed for.
func TestRestoreActionsRunForCorrectItems ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
actions map [ * recordResourcesAction ] [ ] string
} {
{
name : "single action with no selector runs for all items" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) , builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) : { "ns-1/pod-1" , "ns-2/pod-2" , "pv-1" , "pv-2" } ,
} ,
} ,
{
name : "single action with a resource selector for namespaced resources runs only for matching resources" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) , builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) . ForResource ( "pods" ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
} ,
} ,
{
name : "single action with a resource selector for cluster-scoped resources runs only for matching resources" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) , builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) . ForResource ( "persistentvolumes" ) : { "pv-1" , "pv-2" } ,
} ,
} ,
2019-06-26 17:25:47 +00:00
{
name : "single action with a namespace selector runs only for resources in that namespace" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-26 17:25:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) .
addItems ( "persistentvolumeclaims" , builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) , builder . ForPersistentVolumeClaim ( "ns-2" , "pvc-2" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) , builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ) .
2019-06-26 17:25:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVCs ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) . ForNamespace ( "ns-1" ) : { "ns-1/pod-1" , "ns-1/pvc-1" } ,
} ,
} ,
{
name : "single action with a resource and namespace selector runs only for matching resources in that namespace" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-26 17:25:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) .
addItems ( "persistentvolumeclaims" , builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) , builder . ForPersistentVolumeClaim ( "ns-2" , "pvc-2" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) , builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ) .
2019-06-26 17:25:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVCs ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) . ForNamespace ( "ns-1" ) . ForResource ( "pods" ) : { "ns-1/pod-1" } ,
} ,
} ,
2019-06-27 18:57:47 +00:00
{
name : "multiple actions, each with a different resource selector using short name, run for matching resources" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) .
addItems ( "persistentvolumeclaims" , builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) , builder . ForPersistentVolumeClaim ( "ns-2" , "pvc-2" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) , builder . ForPersistentVolume ( "pv-2" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVCs ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) . ForResource ( "po" ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
new ( recordResourcesAction ) . ForResource ( "pv" ) : { "pv-1" , "pv-2" } ,
} ,
} ,
{
name : "actions with selectors that don't match anything don't run for any resources" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 18:57:47 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
addItems ( "persistentvolumeclaims" , builder . ForPersistentVolumeClaim ( "ns-2" , "pvc-2" ) . Result ( ) ) .
2019-06-27 18:57:47 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVCs ( ) , test . PVs ( ) } ,
actions : map [ * recordResourcesAction ] [ ] string {
new ( recordResourcesAction ) . ForNamespace ( "ns-1" ) . ForResource ( "persistentvolumeclaims" ) : nil ,
new ( recordResourcesAction ) . ForNamespace ( "ns-2" ) . ForResource ( "pods" ) : nil ,
} ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . addItems ( t , r )
}
actions := [ ] velero . RestoreItemAction { }
for action := range tc . actions {
actions = append ( actions , action )
}
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-27 18:57:47 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-27 18:57:47 +00:00
actions ,
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
for action , want := range tc . actions {
sort . Strings ( want )
sort . Strings ( action . ids )
assert . Equal ( t , want , action . ids )
}
} )
}
}
2019-06-27 22:43:59 +00:00
// pluggableAction is a restore item action that can be plugged with an Execute
// function body at runtime.
type pluggableAction struct {
selector velero . ResourceSelector
executeFunc func ( * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error )
}
func ( a * pluggableAction ) Execute ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
if a . executeFunc == nil {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
} , nil
}
return a . executeFunc ( input )
}
func ( a * pluggableAction ) AppliesTo ( ) ( velero . ResourceSelector , error ) {
return a . selector , nil
}
// TestRestoreActionModifications runs restores with restore item actions that modify resources, and
// verifies that that the modified item is correctly created in the API. Verification is done by looking
// at the full object in the API.
func TestRestoreActionModifications ( t * testing . T ) {
// modifyingActionGetter is a helper function that returns a *pluggableAction, whose Execute(...)
// method modifies the item being passed in by calling the 'modify' function on it.
modifyingActionGetter := func ( modify func ( * unstructured . Unstructured ) ) * pluggableAction {
return & pluggableAction {
executeFunc : func ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
obj , ok := input . Item . ( * unstructured . Unstructured )
if ! ok {
return nil , errors . Errorf ( "unexpected type %T" , input . Item )
}
res := obj . DeepCopy ( )
modify ( res )
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : res ,
} , nil
} ,
}
}
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
tarball io . Reader
actions [ ] velero . RestoreItemAction
want [ ] * test . APIResource
} {
{
name : "action that adds a label to item gets restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) . addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) . done ( ) ,
2019-06-27 22:43:59 +00:00
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
actions : [ ] velero . RestoreItemAction {
modifyingActionGetter ( func ( item * unstructured . Unstructured ) {
item . SetLabels ( map [ string ] string { "updated" : "true" } )
} ) ,
} ,
want : [ ] * test . APIResource {
test . Pods (
2019-07-31 14:46:48 +00:00
builder . ForPod ( "ns-1" , "pod-1" ) . ObjectMeta ( builder . WithLabels ( "updated" , "true" ) ) . Result ( ) ,
) ,
2019-06-27 22:43:59 +00:00
} ,
} ,
{
name : "action that removes a label to item gets restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) . addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . ObjectMeta ( builder . WithLabels ( "should-be-removed" , "true" ) ) . Result ( ) ) . done ( ) ,
2019-06-27 22:43:59 +00:00
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
actions : [ ] velero . RestoreItemAction {
modifyingActionGetter ( func ( item * unstructured . Unstructured ) {
item . SetLabels ( nil )
} ) ,
} ,
want : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . Pods ( builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) ,
2019-06-27 22:43:59 +00:00
} ,
} ,
// TODO action that modifies namespace/name - what's the expected behavior?
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . addItems ( t , r )
}
// every restored item should have the restore and backup name labels, set
// them here so we don't have to do it in every test case definition above.
for _ , resource := range tc . want {
for _ , item := range resource . Items {
labels := item . GetLabels ( )
if labels == nil {
labels = make ( map [ string ] string )
}
labels [ "velero.io/restore-name" ] = tc . restore . Name
labels [ "velero.io/backup-name" ] = tc . restore . Spec . BackupName
item . SetLabels ( labels )
}
}
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-27 22:43:59 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-27 22:43:59 +00:00
tc . actions ,
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
assertRestoredItems ( t , h , tc . want )
} )
}
}
// TestRestoreActionAdditionalItems runs restores with restore item actions that return additional items
// to be restored, and verifies that that the correct set of items is created in the API. Verification is
// done by looking at the namespaces/names of the items in the API; contents are not checked.
func TestRestoreActionAdditionalItems ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
tarball io . Reader
apiResources [ ] * test . APIResource
actions [ ] velero . RestoreItemAction
want map [ * test . APIResource ] [ ] string
} {
{
name : "additional items that are already being restored are not restored twice" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) . addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) . done ( ) ,
2019-06-27 22:43:59 +00:00
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
actions : [ ] velero . RestoreItemAction {
& pluggableAction {
selector : velero . ResourceSelector { IncludedNamespaces : [ ] string { "ns-1" } } ,
executeFunc : func ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : [ ] velero . ResourceIdentifier {
{ GroupResource : kuberesource . Pods , Namespace : "ns-2" , Name : "pod-2" } ,
} ,
} , nil
} ,
} ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" , "ns-2/pod-2" } ,
} ,
} ,
2019-06-27 20:48:50 +00:00
{
name : "when using a restore namespace filter, additional items that are in a non-included namespace are not restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) . addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) , builder . ForPod ( "ns-2" , "pod-2" ) . Result ( ) ) . done ( ) ,
2019-06-27 20:48:50 +00:00
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
actions : [ ] velero . RestoreItemAction {
& pluggableAction {
executeFunc : func ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : [ ] velero . ResourceIdentifier {
{ GroupResource : kuberesource . Pods , Namespace : "ns-2" , Name : "pod-2" } ,
} ,
} , nil
} ,
} ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
} ,
} ,
2019-06-27 22:43:59 +00:00
{
2019-12-12 20:45:41 +00:00
name : "when using a restore namespace filter, additional items that are cluster-scoped are restored when IncludeClusterResources=nil" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . IncludedNamespaces ( "ns-1" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 22:43:59 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ) .
2019-06-27 22:43:59 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVs ( ) } ,
actions : [ ] velero . RestoreItemAction {
& pluggableAction {
executeFunc : func ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : [ ] velero . ResourceIdentifier {
{ GroupResource : kuberesource . PersistentVolumes , Name : "pv-1" } ,
} ,
} , nil
} ,
} ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . PVs ( ) : { "/pv-1" } ,
} ,
} ,
2019-06-27 20:48:50 +00:00
{
2019-12-12 20:45:41 +00:00
name : "additional items that are cluster-scoped are not restored when IncludeClusterResources=false" ,
restore : defaultRestore ( ) . IncludeClusterResources ( false ) . Result ( ) ,
2019-07-31 14:46:48 +00:00
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 20:48:50 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ) .
2019-06-27 20:48:50 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVs ( ) } ,
actions : [ ] velero . RestoreItemAction {
& pluggableAction {
executeFunc : func ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : [ ] velero . ResourceIdentifier {
{ GroupResource : kuberesource . PersistentVolumes , Name : "pv-1" } ,
} ,
} , nil
} ,
} ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . PVs ( ) : nil ,
} ,
} ,
{
2019-12-12 20:45:41 +00:00
name : "when using a restore resource filter, additional items that are non-included resources are not restored" ,
restore : defaultRestore ( ) . IncludedResources ( "pods" ) . Result ( ) ,
2019-07-31 14:46:48 +00:00
backup : defaultBackup ( ) . Result ( ) ,
2019-06-27 20:48:50 +00:00
tarball : newTarWriter ( t ) .
2019-07-31 14:46:48 +00:00
addItems ( "pods" , builder . ForPod ( "ns-1" , "pod-1" ) . Result ( ) ) .
addItems ( "persistentvolumes" , builder . ForPersistentVolume ( "pv-1" ) . Result ( ) ) .
2019-06-27 20:48:50 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) , test . PVs ( ) } ,
actions : [ ] velero . RestoreItemAction {
& pluggableAction {
executeFunc : func ( input * velero . RestoreItemActionExecuteInput ) ( * velero . RestoreItemActionExecuteOutput , error ) {
return & velero . RestoreItemActionExecuteOutput {
UpdatedItem : input . Item ,
AdditionalItems : [ ] velero . ResourceIdentifier {
{ GroupResource : kuberesource . PersistentVolumes , Name : "pv-1" } ,
} ,
} , nil
} ,
} ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-1" } ,
test . PVs ( ) : nil ,
} ,
} ,
2019-06-27 22:43:59 +00:00
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
for _ , r := range tc . apiResources {
h . addItems ( t , r )
}
2019-08-06 20:17:36 +00:00
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : nil ,
VolumeSnapshots : nil ,
BackupReader : tc . tarball ,
}
2019-06-27 22:43:59 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-27 22:43:59 +00:00
tc . actions ,
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
assertAPIContents ( t , h , tc . want )
} )
}
}
// TestShouldRestore runs the ShouldRestore function for various permutations of
// existing/nonexisting/being-deleted PVs, PVCs, and namespaces, and verifies the
// result/error matches expectations.
func TestShouldRestore ( t * testing . T ) {
tests := [ ] struct {
name string
pvName string
apiResources [ ] * test . APIResource
namespaces [ ] * corev1api . Namespace
want bool
wantErr error
} {
{
name : "when PV is not found, result is true" ,
pvName : "pv-1" ,
want : true ,
} ,
{
name : "when PV is found and has Phase=Released, result is false" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
test . PVs ( & corev1api . PersistentVolume {
TypeMeta : metav1 . TypeMeta {
APIVersion : "v1" ,
Kind : "PersistentVolume" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : "pv-1" ,
} ,
Status : corev1api . PersistentVolumeStatus {
Phase : corev1api . VolumeReleased ,
} ,
} ) ,
} ,
want : false ,
} ,
{
name : "when PV is found and has associated PVC and namespace that aren't deleting, result is false" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
) ,
test . PVCs ( builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) ) ,
2019-06-27 22:43:59 +00:00
} ,
2019-07-31 14:46:48 +00:00
namespaces : [ ] * corev1api . Namespace { builder . ForNamespace ( "ns-1" ) . Result ( ) } ,
2019-06-27 22:43:59 +00:00
want : false ,
} ,
{
name : "when PV is found and has associated PVC that is deleting, result is false + timeout error" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
) ,
2019-06-27 22:43:59 +00:00
test . PVCs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . ObjectMeta ( builder . WithDeletionTimestamp ( time . Now ( ) ) ) . Result ( ) ,
2019-06-27 22:43:59 +00:00
) ,
} ,
want : false ,
wantErr : errors . New ( "timed out waiting for the condition" ) ,
} ,
{
name : "when PV is found, has associated PVC that's not deleting, has associated NS that is terminating, result is false + timeout error" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
) ,
test . PVCs ( builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) ) ,
2019-06-27 22:43:59 +00:00
} ,
namespaces : [ ] * corev1api . Namespace {
2019-07-31 14:46:48 +00:00
builder . ForNamespace ( "ns-1" ) . Phase ( corev1api . NamespaceTerminating ) . Result ( ) ,
2019-06-27 22:43:59 +00:00
} ,
want : false ,
wantErr : errors . New ( "timed out waiting for the condition" ) ,
} ,
{
name : "when PV is found, has associated PVC that's not deleting, has associated NS that has deletion timestamp, result is false + timeout error" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
) ,
test . PVCs ( builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) ) ,
2019-06-27 22:43:59 +00:00
} ,
namespaces : [ ] * corev1api . Namespace {
2019-07-31 14:46:48 +00:00
builder . ForNamespace ( "ns-1" ) . ObjectMeta ( builder . WithDeletionTimestamp ( time . Now ( ) ) ) . Result ( ) ,
2019-06-27 22:43:59 +00:00
} ,
want : false ,
wantErr : errors . New ( "timed out waiting for the condition" ) ,
} ,
{
name : "when PV is found, associated PVC is not found, result is false + timeout error" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
) ,
2019-06-27 22:43:59 +00:00
} ,
want : false ,
wantErr : errors . New ( "timed out waiting for the condition" ) ,
} ,
{
name : "when PV is found, has associated PVC, associated namespace not found, result is false + timeout error" ,
pvName : "pv-1" ,
apiResources : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
) ,
test . PVCs ( builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) . Result ( ) ) ,
2019-06-27 22:43:59 +00:00
} ,
want : false ,
wantErr : errors . New ( "timed out waiting for the condition" ) ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
ctx := & context {
log : h . log ,
dynamicFactory : client . NewDynamicFactory ( h . DynamicClient ) ,
namespaceClient : h . KubeClient . CoreV1 ( ) . Namespaces ( ) ,
resourceTerminatingTimeout : time . Millisecond ,
}
for _ , resource := range tc . apiResources {
h . addItems ( t , resource )
}
for _ , ns := range tc . namespaces {
_ , err := ctx . namespaceClient . Create ( ns )
require . NoError ( t , err )
}
pvClient , err := ctx . dynamicFactory . ClientForGroupVersionResource (
schema . GroupVersion { Group : "" , Version : "v1" } ,
metav1 . APIResource { Name : "persistentvolumes" } ,
"" ,
)
require . NoError ( t , err )
res , err := ctx . shouldRestore ( tc . pvName , pvClient )
assert . Equal ( t , tc . want , res )
if tc . wantErr != nil {
if assert . NotNil ( t , err , "expected a non-nil error" ) {
assert . EqualError ( t , err , tc . wantErr . Error ( ) )
}
} else {
assert . Nil ( t , err )
}
} )
}
}
func assertRestoredItems ( t * testing . T , h * harness , want [ ] * test . APIResource ) {
t . Helper ( )
for _ , resource := range want {
resourceClient := h . DynamicClient . Resource ( resource . GVR ( ) )
for _ , item := range resource . Items {
var client dynamic . ResourceInterface
if item . GetNamespace ( ) != "" {
client = resourceClient . Namespace ( item . GetNamespace ( ) )
} else {
client = resourceClient
}
res , err := client . Get ( item . GetName ( ) , metav1 . GetOptions { } )
if ! assert . NoError ( t , err ) {
continue
}
itemJSON , err := json . Marshal ( item )
if ! assert . NoError ( t , err ) {
continue
}
t . Logf ( "%v" , string ( itemJSON ) )
u := make ( map [ string ] interface { } )
if ! assert . NoError ( t , json . Unmarshal ( itemJSON , & u ) ) {
continue
}
want := & unstructured . Unstructured { Object : u }
// These fields get non-nil zero values in the unstructured objects if they're
// empty in the structured objects. Remove them to make comparison easier.
unstructured . RemoveNestedField ( want . Object , "metadata" , "creationTimestamp" )
unstructured . RemoveNestedField ( want . Object , "status" )
assert . Equal ( t , want , res )
}
}
}
2019-06-28 18:58:49 +00:00
// volumeSnapshotterGetter is a simple implementation of the VolumeSnapshotterGetter
// interface that returns velero.VolumeSnapshotters from a map if they exist.
type volumeSnapshotterGetter map [ string ] velero . VolumeSnapshotter
func ( vsg volumeSnapshotterGetter ) GetVolumeSnapshotter ( name string ) ( velero . VolumeSnapshotter , error ) {
snapshotter , ok := vsg [ name ]
if ! ok {
return nil , errors . New ( "volume snapshotter not found" )
}
return snapshotter , nil
}
// volumeSnapshotter is a test fake for the velero.VolumeSnapshotter interface
type volumeSnapshotter struct {
// a map from snapshotID to volumeID
snapshotVolumes map [ string ] string
}
// Init is a no-op.
func ( vs * volumeSnapshotter ) Init ( config map [ string ] string ) error {
return nil
}
// CreateVolumeFromSnapshot looks up the specified snapshotID in the snapshotVolumes
// map and returns the corresponding volumeID if it exists, or an error otherwise.
func ( vs * volumeSnapshotter ) CreateVolumeFromSnapshot ( snapshotID , volumeType , volumeAZ string , iops * int64 ) ( volumeID string , err error ) {
volumeID , ok := vs . snapshotVolumes [ snapshotID ]
if ! ok {
return "" , errors . New ( "snapshot not found" )
}
return volumeID , nil
}
// SetVolumeID sets the persistent volume's spec.awsElasticBlockStore.volumeID field
// with the provided volumeID.
func ( * volumeSnapshotter ) SetVolumeID ( pv runtime . Unstructured , volumeID string ) ( runtime . Unstructured , error ) {
unstructured . SetNestedField ( pv . UnstructuredContent ( ) , volumeID , "spec" , "awsElasticBlockStore" , "volumeID" )
return pv , nil
}
// GetVolumeID panics because it's not expected to be used for restores.
func ( * volumeSnapshotter ) GetVolumeID ( pv runtime . Unstructured ) ( string , error ) {
panic ( "GetVolumeID should not be used for restores" )
}
// CreateSnapshot panics because it's not expected to be used for restores.
func ( * volumeSnapshotter ) CreateSnapshot ( volumeID , volumeAZ string , tags map [ string ] string ) ( snapshotID string , err error ) {
panic ( "CreateSnapshot should not be used for restores" )
}
// GetVolumeInfo panics because it's not expected to be used for restores.
func ( * volumeSnapshotter ) GetVolumeInfo ( volumeID , volumeAZ string ) ( string , * int64 , error ) {
panic ( "GetVolumeInfo should not be used for restores" )
}
// DeleteSnapshot panics because it's not expected to be used for restores.
func ( * volumeSnapshotter ) DeleteSnapshot ( snapshotID string ) error {
panic ( "DeleteSnapshot should not be used for backups" )
}
// TestRestorePersistentVolumes runs restores for persistent volumes and verifies that
// they are restored as expected, including restoring volumes from snapshots when expected.
// Verification is done by looking at the contents of the API and the metadata/spec/status of
// the items in the API.
func TestRestorePersistentVolumes ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
tarball io . Reader
apiResources [ ] * test . APIResource
volumeSnapshots [ ] * volume . Snapshot
volumeSnapshotLocations [ ] * velerov1api . VolumeSnapshotLocation
volumeSnapshotterGetter volumeSnapshotterGetter
want [ ] * test . APIResource
} {
{
name : "when a PV with a reclaim policy of delete has no snapshot and does not exist in-cluster, it does not get restored, and its PVC gets reset for dynamic provisioning" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . ReclaimPolicy ( corev1api . PersistentVolumeReclaimDelete ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
) .
addItems ( "persistentvolumeclaims" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) .
VolumeName ( "pv-1" ) .
ObjectMeta (
builder . WithAnnotations ( "pv.kubernetes.io/bind-completed" , "true" , "pv.kubernetes.io/bound-by-controller" , "true" , "foo" , "bar" ) ,
) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs ( ) ,
test . PVCs ( ) ,
} ,
want : [ ] * test . APIResource {
test . PVs ( ) ,
test . PVCs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolumeClaim ( "ns-1" , "pvc-1" ) .
ObjectMeta (
builder . WithAnnotations ( "foo" , "bar" ) ,
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
} ,
} ,
{
name : "when a PV with a reclaim policy of retain has no snapshot and does not exist in-cluster, it gets restored, without its claim ref" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
tarball : newTarWriter ( t ) .
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) . ClaimRef ( "ns-1" , "pvc-1" ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs ( ) ,
test . PVCs ( ) ,
} ,
want : [ ] * test . APIResource {
test . PVs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
ObjectMeta (
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
} ,
} ,
{
name : "when a PV with a reclaim policy of delete has a snapshot and does not exist in-cluster, the snapshot and PV are restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
tarball : newTarWriter ( t ) .
2019-07-02 15:49:34 +00:00
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) . ReclaimPolicy ( corev1api . PersistentVolumeReclaimDelete ) . AWSEBSVolumeID ( "old-volume" ) . Result ( ) ,
) .
2019-06-28 18:58:49 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs ( ) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "pv-1" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
2019-08-06 20:40:35 +00:00
builder . ForVolumeSnapshotLocation ( velerov1api . DefaultNamespace , "default" ) . Provider ( "provider-1" ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
"provider-1" : & volumeSnapshotter {
snapshotVolumes : map [ string ] string { "snapshot-1" : "new-volume" } ,
} ,
} ,
want : [ ] * test . APIResource {
test . PVs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimDelete ) .
AWSEBSVolumeID ( "new-volume" ) .
ObjectMeta (
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
} ,
} ,
{
name : "when a PV with a reclaim policy of retain has a snapshot and does not exist in-cluster, the snapshot and PV are restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
tarball : newTarWriter ( t ) .
2019-07-02 15:49:34 +00:00
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) .
2019-06-28 18:58:49 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs ( ) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "pv-1" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
2019-08-06 20:40:35 +00:00
builder . ForVolumeSnapshotLocation ( velerov1api . DefaultNamespace , "default" ) . Provider ( "provider-1" ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
"provider-1" : & volumeSnapshotter {
snapshotVolumes : map [ string ] string { "snapshot-1" : "new-volume" } ,
} ,
} ,
want : [ ] * test . APIResource {
test . PVs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "new-volume" ) .
ObjectMeta (
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
} ,
} ,
{
name : "when a PV with a reclaim policy of delete has a snapshot and exists in-cluster, neither the snapshot nor the PV are restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
tarball : newTarWriter ( t ) .
2019-07-02 15:49:34 +00:00
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimDelete ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) .
2019-06-28 18:58:49 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimDelete ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "pv-1" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
2019-08-06 20:40:35 +00:00
builder . ForVolumeSnapshotLocation ( velerov1api . DefaultNamespace , "default" ) . Provider ( "provider-1" ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
// the volume snapshotter fake is not configured with any snapshotID -> volumeID
// mappings as a way to verify that the snapshot is not restored, since if it were
// restored, we'd get an error of "snapshot not found".
"provider-1" : & volumeSnapshotter { } ,
} ,
want : [ ] * test . APIResource {
test . PVs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimDelete ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
} ,
} ,
{
name : "when a PV with a reclaim policy of retain has a snapshot and exists in-cluster, neither the snapshot nor the PV are restored" ,
2019-07-31 14:46:48 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
tarball : newTarWriter ( t ) .
2019-07-02 15:49:34 +00:00
addItems ( "persistentvolumes" ,
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) .
2019-06-28 18:58:49 +00:00
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs (
2019-07-31 14:46:48 +00:00
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
2019-06-28 18:58:49 +00:00
) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "pv-1" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
2019-08-06 20:40:35 +00:00
builder . ForVolumeSnapshotLocation ( velerov1api . DefaultNamespace , "default" ) . Provider ( "provider-1" ) . Result ( ) ,
2019-06-28 18:58:49 +00:00
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
// the volume snapshotter fake is not configured with any snapshotID -> volumeID
// mappings as a way to verify that the snapshot is not restored, since if it were
// restored, we'd get an error of "snapshot not found".
"provider-1" : & volumeSnapshotter { } ,
} ,
want : [ ] * test . APIResource {
2019-07-31 14:46:48 +00:00
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) ,
2019-06-28 18:58:49 +00:00
} ,
} ,
2019-08-27 23:42:38 +00:00
{
name : "when a PV with a snapshot is used by a PVC in a namespace that's being remapped, and the original PV exists in-cluster, the PV is renamed" ,
restore : defaultRestore ( ) . NamespaceMappings ( "source-ns" , "target-ns" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) .
addItems (
"persistentvolumes" ,
builder . ForPersistentVolume ( "source-pv" ) . AWSEBSVolumeID ( "source-volume" ) . ClaimRef ( "source-ns" , "pvc-1" ) . Result ( ) ,
) .
addItems (
"persistentvolumeclaims" ,
builder . ForPersistentVolumeClaim ( "source-ns" , "pvc-1" ) . VolumeName ( "source-pv" ) . Result ( ) ,
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs (
builder . ForPersistentVolume ( "source-pv" ) . AWSEBSVolumeID ( "source-volume" ) . ClaimRef ( "source-ns" , "pvc-1" ) . Result ( ) ,
) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "source-pv" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
builder . ForVolumeSnapshotLocation ( velerov1api . DefaultNamespace , "default" ) . Provider ( "provider-1" ) . Result ( ) ,
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
"provider-1" : & volumeSnapshotter {
snapshotVolumes : map [ string ] string { "snapshot-1" : "new-volume" } ,
} ,
} ,
want : [ ] * test . APIResource {
test . PVs (
builder . ForPersistentVolume ( "source-pv" ) . AWSEBSVolumeID ( "source-volume" ) . ClaimRef ( "source-ns" , "pvc-1" ) . Result ( ) ,
// note that the renamed PV is not expected to have a claimRef in this test; that would be
// added after creation by the Kubernetes PV/PVC controller when it does a bind.
builder . ForPersistentVolume ( "renamed-source-pv" ) .
ObjectMeta (
builder . WithAnnotations ( "velero.io/original-pv-name" , "source-pv" ) ,
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
AWSEBSVolumeID ( "new-volume" ) .
Result ( ) ,
) ,
test . PVCs (
builder . ForPersistentVolumeClaim ( "target-ns" , "pvc-1" ) .
ObjectMeta (
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
VolumeName ( "renamed-source-pv" ) .
Result ( ) ,
) ,
} ,
} ,
{
name : "when a PV with a snapshot is used by a PVC in a namespace that's being remapped, and the original PV does not exist in-cluster, the PV is not renamed" ,
restore : defaultRestore ( ) . NamespaceMappings ( "source-ns" , "target-ns" ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) .
addItems (
"persistentvolumes" ,
builder . ForPersistentVolume ( "source-pv" ) . AWSEBSVolumeID ( "source-volume" ) . ClaimRef ( "source-ns" , "pvc-1" ) . Result ( ) ,
) .
addItems (
"persistentvolumeclaims" ,
builder . ForPersistentVolumeClaim ( "source-ns" , "pvc-1" ) . VolumeName ( "source-pv" ) . Result ( ) ,
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs ( ) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "source-pv" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
builder . ForVolumeSnapshotLocation ( velerov1api . DefaultNamespace , "default" ) . Provider ( "provider-1" ) . Result ( ) ,
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
"provider-1" : & volumeSnapshotter {
snapshotVolumes : map [ string ] string { "snapshot-1" : "new-volume" } ,
} ,
} ,
want : [ ] * test . APIResource {
test . PVs (
builder . ForPersistentVolume ( "source-pv" ) .
ObjectMeta (
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
AWSEBSVolumeID ( "new-volume" ) .
Result ( ) ,
) ,
test . PVCs (
builder . ForPersistentVolumeClaim ( "target-ns" , "pvc-1" ) .
ObjectMeta (
builder . WithLabels ( "velero.io/backup-name" , "backup-1" , "velero.io/restore-name" , "restore-1" ) ,
) .
VolumeName ( "source-pv" ) .
Result ( ) ,
) ,
} ,
} ,
2019-08-06 20:17:36 +00:00
{
2019-08-27 22:49:23 +00:00
name : "when a PV with a reclaim policy of retain has a snapshot and exists in-cluster, neither the snapshot nor the PV are restored" ,
2019-08-06 20:17:36 +00:00
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
tarball : newTarWriter ( t ) .
addItems ( "persistentvolumes" ,
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) .
done ( ) ,
apiResources : [ ] * test . APIResource {
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) ,
test . PVCs ( ) ,
} ,
volumeSnapshots : [ ] * volume . Snapshot {
{
Spec : volume . SnapshotSpec {
BackupName : "backup-1" ,
Location : "default" ,
PersistentVolumeName : "pv-1" ,
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseCompleted ,
ProviderSnapshotID : "snapshot-1" ,
} ,
} ,
} ,
volumeSnapshotLocations : [ ] * velerov1api . VolumeSnapshotLocation {
{
ObjectMeta : metav1 . ObjectMeta {
Namespace : velerov1api . DefaultNamespace ,
Name : "default" ,
} ,
Spec : velerov1api . VolumeSnapshotLocationSpec {
Provider : "provider-1" ,
} ,
} ,
} ,
volumeSnapshotterGetter : map [ string ] velero . VolumeSnapshotter {
// the volume snapshotter fake is not configured with any snapshotID -> volumeID
// mappings as a way to verify that the snapshot is not restored, since if it were
// restored, we'd get an error of "snapshot not found".
"provider-1" : & volumeSnapshotter { } ,
} ,
2019-08-27 22:49:23 +00:00
2019-08-06 20:17:36 +00:00
want : [ ] * test . APIResource {
test . PVs (
builder . ForPersistentVolume ( "pv-1" ) .
ReclaimPolicy ( corev1api . PersistentVolumeReclaimRetain ) .
AWSEBSVolumeID ( "old-volume" ) .
Result ( ) ,
) ,
} ,
} ,
2019-06-28 18:58:49 +00:00
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
h . restorer . resourcePriorities = [ ] string { "persistentvolumes" , "persistentvolumeclaims" }
2019-08-27 23:42:38 +00:00
h . restorer . pvRenamer = func ( oldName string ) string { return "renamed-" + oldName }
2019-06-28 18:58:49 +00:00
// set up the VolumeSnapshotLocation informer/lister and add test data to it
vslInformer := velerov1informers . NewSharedInformerFactory ( h . VeleroClient , 0 ) . Velero ( ) . V1 ( ) . VolumeSnapshotLocations ( )
for _ , vsl := range tc . volumeSnapshotLocations {
require . NoError ( t , vslInformer . Informer ( ) . GetStore ( ) . Add ( vsl ) )
}
for _ , r := range tc . apiResources {
h . addItems ( t , r )
}
// Collect the IDs of all of the wanted resources so we can ensure the
// exact set exists in the API after restore.
wantIDs := make ( map [ * test . APIResource ] [ ] string )
for i , resource := range tc . want {
wantIDs [ tc . want [ i ] ] = [ ] string { }
for _ , item := range resource . Items {
wantIDs [ tc . want [ i ] ] = append ( wantIDs [ tc . want [ i ] ] , fmt . Sprintf ( "%s/%s" , item . GetNamespace ( ) , item . GetName ( ) ) )
}
}
2019-08-06 20:17:36 +00:00
data := Request {
2019-08-27 22:49:23 +00:00
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
VolumeSnapshots : tc . volumeSnapshots ,
BackupReader : tc . tarball ,
2019-08-06 20:17:36 +00:00
}
2019-06-28 18:58:49 +00:00
warnings , errs := h . restorer . Restore (
2019-08-06 20:17:36 +00:00
data ,
2019-06-28 18:58:49 +00:00
nil , // actions
vslInformer . Lister ( ) ,
tc . volumeSnapshotterGetter ,
)
assertEmptyResults ( t , warnings , errs )
assertAPIContents ( t , h , wantIDs )
assertRestoredItems ( t , h , tc . want )
} )
}
}
2019-08-27 22:49:23 +00:00
type fakeResticRestorerFactory struct {
restorer * resticmocks . Restorer
}
func ( f * fakeResticRestorerFactory ) NewRestorer ( ctx . Context , * velerov1api . Restore ) ( restic . Restorer , error ) {
return f . restorer , nil
}
// TestRestoreWithRestic verifies that a call to RestorePodVolumes was made as and when
// expected for the given pods by using a mock for the restic restorer.
func TestRestoreWithRestic ( t * testing . T ) {
tests := [ ] struct {
name string
restore * velerov1api . Restore
backup * velerov1api . Backup
apiResources [ ] * test . APIResource
podVolumeBackups [ ] * velerov1api . PodVolumeBackup
podWithPVBs , podWithoutPVBs [ ] * corev1api . Pod
want map [ * test . APIResource ] [ ] string
} {
{
name : "a pod that exists in given backup and contains associated PVBs should have should have RestorePodVolumes called" ,
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
podVolumeBackups : [ ] * velerov1api . PodVolumeBackup {
2019-11-04 23:18:08 +00:00
builder . ForPodVolumeBackup ( "velero" , "pvb-1" ) . PodName ( "pod-1" ) . SnapshotID ( "foo" ) . Result ( ) ,
builder . ForPodVolumeBackup ( "velero" , "pvb-2" ) . PodName ( "pod-2" ) . SnapshotID ( "foo" ) . Result ( ) ,
builder . ForPodVolumeBackup ( "velero" , "pvb-3" ) . PodName ( "pod-4" ) . SnapshotID ( "foo" ) . Result ( ) ,
2019-08-27 22:49:23 +00:00
} ,
podWithPVBs : [ ] * corev1api . Pod {
builder . ForPod ( "ns-1" , "pod-2" ) .
Result ( ) ,
builder . ForPod ( "ns-2" , "pod-4" ) .
Result ( ) ,
} ,
podWithoutPVBs : [ ] * corev1api . Pod {
builder . ForPod ( "ns-2" , "pod-3" ) .
Result ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-2" , "ns-2/pod-3" , "ns-2/pod-4" } ,
} ,
} ,
{
name : "a pod that exists in given backup but does not contain associated PVBs should not have should have RestorePodVolumes called" ,
restore : defaultRestore ( ) . Result ( ) ,
backup : defaultBackup ( ) . Result ( ) ,
apiResources : [ ] * test . APIResource { test . Pods ( ) } ,
podVolumeBackups : [ ] * velerov1api . PodVolumeBackup {
builder . ForPodVolumeBackup ( "velero" , "pvb-1" ) . PodName ( "pod-1" ) . Result ( ) ,
builder . ForPodVolumeBackup ( "velero" , "pvb-2" ) . PodName ( "pod-2" ) . Result ( ) ,
} ,
podWithPVBs : [ ] * corev1api . Pod { } ,
podWithoutPVBs : [ ] * corev1api . Pod {
builder . ForPod ( "ns-1" , "pod-3" ) .
Result ( ) ,
builder . ForPod ( "ns-2" , "pod-4" ) .
Result ( ) ,
} ,
want : map [ * test . APIResource ] [ ] string {
test . Pods ( ) : { "ns-1/pod-3" , "ns-2/pod-4" } ,
} ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
h := newHarness ( t )
restorer := new ( resticmocks . Restorer )
defer restorer . AssertExpectations ( t )
h . restorer . resticRestorerFactory = & fakeResticRestorerFactory {
restorer : restorer ,
}
// needed only to indicate resource types that can be restored, in this case, pods
for _ , resource := range tc . apiResources {
h . addItems ( t , resource )
}
tarball := newTarWriter ( t )
// these backed up pods don't have any PVBs associated with them, so a call to RestorePodVolumes is not expected to be made for them
for _ , pod := range tc . podWithoutPVBs {
tarball . addItems ( "pods" , pod )
}
// these backed up pods have PVBs associated with them, so a call to RestorePodVolumes will be made for each of them
for _ , pod := range tc . podWithPVBs {
tarball . addItems ( "pods" , pod )
// the restore process adds these labels before restoring, so we must add them here too otherwise they won't match
pod . Labels = map [ string ] string { "velero.io/backup-name" : tc . backup . Name , "velero.io/restore-name" : tc . restore . Name }
expectedArgs := restic . RestoreData {
Restore : tc . restore ,
Pod : pod ,
PodVolumeBackups : tc . podVolumeBackups ,
SourceNamespace : pod . Namespace ,
BackupLocation : "" ,
}
restorer .
On ( "RestorePodVolumes" , expectedArgs ) .
Return ( nil )
}
data := Request {
Log : h . log ,
Restore : tc . restore ,
Backup : tc . backup ,
PodVolumeBackups : tc . podVolumeBackups ,
BackupReader : tarball . done ( ) ,
}
warnings , errs := h . restorer . Restore (
data ,
nil , // actions
nil , // snapshot location lister
nil , // volume snapshotter getter
)
assertEmptyResults ( t , warnings , errs )
assertAPIContents ( t , h , tc . want )
} )
}
}
2019-07-10 15:59:01 +00:00
func TestPrioritizeResources ( t * testing . T ) {
tests := [ ] struct {
name string
apiResources map [ string ] [ ] string
priorities [ ] string
includes [ ] string
excludes [ ] string
expected [ ] string
} {
{
name : "priorities & ordering are correctly applied" ,
apiResources : map [ string ] [ ] string {
"v1" : { "aaa" , "bbb" , "configmaps" , "ddd" , "namespaces" , "ooo" , "pods" , "sss" } ,
} ,
priorities : [ ] string { "namespaces" , "configmaps" , "pods" } ,
includes : [ ] string { "*" } ,
expected : [ ] string { "namespaces" , "configmaps" , "pods" , "aaa" , "bbb" , "ddd" , "ooo" , "sss" } ,
} ,
{
name : "includes are correctly applied" ,
apiResources : map [ string ] [ ] string {
"v1" : { "aaa" , "bbb" , "configmaps" , "ddd" , "namespaces" , "ooo" , "pods" , "sss" } ,
} ,
priorities : [ ] string { "namespaces" , "configmaps" , "pods" } ,
includes : [ ] string { "namespaces" , "aaa" , "sss" } ,
expected : [ ] string { "namespaces" , "aaa" , "sss" } ,
} ,
{
name : "excludes are correctly applied" ,
apiResources : map [ string ] [ ] string {
"v1" : { "aaa" , "bbb" , "configmaps" , "ddd" , "namespaces" , "ooo" , "pods" , "sss" } ,
} ,
priorities : [ ] string { "namespaces" , "configmaps" , "pods" } ,
includes : [ ] string { "*" } ,
excludes : [ ] string { "ooo" , "pods" } ,
expected : [ ] string { "namespaces" , "configmaps" , "aaa" , "bbb" , "ddd" , "sss" } ,
} ,
}
logger := testutil . NewLogger ( )
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
discoveryClient := & test . DiscoveryClient {
FakeDiscovery : kubefake . NewSimpleClientset ( ) . Discovery ( ) . ( * discoveryfake . FakeDiscovery ) ,
}
helper , err := discovery . NewHelper ( discoveryClient , logger )
require . NoError ( t , err )
// add all the test case's API resources to the discovery client
for gvString , resources := range tc . apiResources {
gv , err := schema . ParseGroupVersion ( gvString )
require . NoError ( t , err )
for _ , resource := range resources {
discoveryClient . WithAPIResource ( & test . APIResource {
Group : gv . Group ,
Version : gv . Version ,
Name : resource ,
} )
}
}
require . NoError ( t , helper . Refresh ( ) )
includesExcludes := collections . NewIncludesExcludes ( ) . Includes ( tc . includes ... ) . Excludes ( tc . excludes ... )
result , err := prioritizeResources ( helper , tc . priorities , includesExcludes , logger )
require . NoError ( t , err )
require . Equal ( t , len ( tc . expected ) , len ( result ) )
for i := range result {
if e , a := tc . expected [ i ] , result [ i ] . Resource ; e != a {
t . Errorf ( "index %d, expected %s, got %s" , i , e , a )
}
}
} )
}
}
func TestResetMetadataAndStatus ( t * testing . T ) {
tests := [ ] struct {
name string
obj * unstructured . Unstructured
expectedErr bool
expectedRes * unstructured . Unstructured
} {
{
name : "no metadata causes error" ,
obj : & unstructured . Unstructured { } ,
expectedErr : true ,
} ,
{
name : "keep name, namespace, labels, annotations only" ,
obj : NewTestUnstructured ( ) . WithMetadata ( "name" , "blah" , "namespace" , "labels" , "annotations" , "foo" ) . Unstructured ,
expectedErr : false ,
expectedRes : NewTestUnstructured ( ) . WithMetadata ( "name" , "namespace" , "labels" , "annotations" ) . Unstructured ,
} ,
{
name : "don't keep status" ,
obj : NewTestUnstructured ( ) . WithMetadata ( ) . WithStatus ( ) . Unstructured ,
expectedErr : false ,
expectedRes : NewTestUnstructured ( ) . WithMetadata ( ) . Unstructured ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
res , err := resetMetadataAndStatus ( test . obj )
if assert . Equal ( t , test . expectedErr , err != nil ) {
assert . Equal ( t , test . expectedRes , res )
}
} )
}
}
func TestIsCompleted ( t * testing . T ) {
tests := [ ] struct {
name string
expected bool
content string
groupResource schema . GroupResource
expectedErr bool
} {
{
name : "Failed pods are complete" ,
expected : true ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}, "status": { "phase": "Failed"}} ` ,
groupResource : schema . GroupResource { Group : "" , Resource : "pods" } ,
} ,
{
name : "Succeeded pods are complete" ,
expected : true ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}, "status": { "phase": "Succeeded"}} ` ,
groupResource : schema . GroupResource { Group : "" , Resource : "pods" } ,
} ,
{
name : "Pending pods aren't complete" ,
expected : false ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}, "status": { "phase": "Pending"}} ` ,
groupResource : schema . GroupResource { Group : "" , Resource : "pods" } ,
} ,
{
name : "Running pods aren't complete" ,
expected : false ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}, "status": { "phase": "Running"}} ` ,
groupResource : schema . GroupResource { Group : "" , Resource : "pods" } ,
} ,
{
name : "Jobs without a completion time aren't complete" ,
expected : false ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}} ` ,
groupResource : schema . GroupResource { Group : "batch" , Resource : "jobs" } ,
} ,
{
name : "Jobs with a completion time are completed" ,
expected : true ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}, "status": { "completionTime": "bar"}} ` ,
groupResource : schema . GroupResource { Group : "batch" , Resource : "jobs" } ,
} ,
{
name : "Jobs with an empty completion time are not completed" ,
expected : false ,
content : ` { "apiVersion":"v1","kind":"Pod","metadata": { "namespace":"ns","name":"pod1"}, "status": { "completionTime": ""}} ` ,
groupResource : schema . GroupResource { Group : "batch" , Resource : "jobs" } ,
} ,
{
name : "Something not a pod or a job may actually be complete, but we're not concerned with that" ,
expected : false ,
content : ` { "apiVersion": "v1", "kind": "Namespace", "metadata": { "name": "ns"}, "status": { "completionTime": "bar", "phase":"Completed"}} ` ,
groupResource : schema . GroupResource { Group : "" , Resource : "namespaces" } ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
u := testutil . UnstructuredOrDie ( test . content )
backup , err := isCompleted ( u , test . groupResource )
if assert . Equal ( t , test . expectedErr , err != nil ) {
assert . Equal ( t , test . expected , backup )
}
} )
}
}
func TestGetItemFilePath ( t * testing . T ) {
res := getItemFilePath ( "root" , "resource" , "" , "item" )
assert . Equal ( t , "root/resources/resource/cluster/item.json" , res )
res = getItemFilePath ( "root" , "resource" , "namespace" , "item" )
assert . Equal ( t , "root/resources/resource/namespaces/namespace/item.json" , res )
}
2019-06-24 22:18:30 +00:00
// assertResourceCreationOrder ensures that resources were created in the expected
// order. Any resources *not* in resourcePriorities are required to come *after* all
// resources in any order.
func assertResourceCreationOrder ( t * testing . T , resourcePriorities [ ] string , createdResources [ ] resourceID ) {
// lastSeen tracks the index in 'resourcePriorities' of the last resource type
// we saw created. Once we've seen a resource in 'resourcePriorities', we should
// never see another instance of a prior resource.
lastSeen := 0
// Find the index in 'resourcePriorities' of the resource type for
// the current item, if it exists. This index ('current') *must*
// be greater than or equal to 'lastSeen', which was the last resource
// we saw, since otherwise the current resource would be out of order. By
// initializing current to len(ordered), we're saying that if the resource
// is not explicitly in orderedResources, then it must come *after*
// all orderedResources.
for _ , r := range createdResources {
current := len ( resourcePriorities )
for i , item := range resourcePriorities {
if item == r . groupResource {
current = i
break
}
}
// the index of the current resource must be the same as or greater than the index of
// the last resource we saw for the restored order to be correct.
assert . True ( t , current >= lastSeen , "%s was restored out of order" , r . groupResource )
lastSeen = current
}
}
type resourceID struct {
groupResource string
nsAndName string
}
// createRecorder provides a Reactor that can be used to capture
// resources created in a fake client.
type createRecorder struct {
t * testing . T
resources [ ] resourceID
}
func ( cr * createRecorder ) reactor ( ) func ( kubetesting . Action ) ( bool , runtime . Object , error ) {
return func ( action kubetesting . Action ) ( bool , runtime . Object , error ) {
createAction , ok := action . ( kubetesting . CreateAction )
if ! ok {
return false , nil , nil
}
accessor , err := meta . Accessor ( createAction . GetObject ( ) )
assert . NoError ( cr . t , err )
cr . resources = append ( cr . resources , resourceID {
groupResource : action . GetResource ( ) . GroupResource ( ) . String ( ) ,
nsAndName : fmt . Sprintf ( "%s/%s" , action . GetNamespace ( ) , accessor . GetName ( ) ) ,
} )
return false , nil , nil
}
}
2019-07-31 14:46:48 +00:00
func defaultRestore ( ) * builder . RestoreBuilder {
return builder . ForRestore ( velerov1api . DefaultNamespace , "restore-1" ) . Backup ( "backup-1" )
2019-06-20 15:06:58 +00:00
}
// assertAPIContents asserts that the dynamic client on the provided harness contains
// all of the items specified in 'want' (a map from an APIResource definition to a slice
// of resource identifiers, formatted as <namespace>/<name>).
func assertAPIContents ( t * testing . T , h * harness , want map [ * test . APIResource ] [ ] string ) {
2019-06-27 18:57:47 +00:00
t . Helper ( )
2019-06-20 15:06:58 +00:00
for r , want := range want {
res , err := h . DynamicClient . Resource ( r . GVR ( ) ) . List ( metav1 . ListOptions { } )
assert . NoError ( t , err )
if err != nil {
continue
}
got := sets . NewString ( )
for _ , item := range res . Items {
got . Insert ( fmt . Sprintf ( "%s/%s" , item . GetNamespace ( ) , item . GetName ( ) ) )
}
assert . Equal ( t , sets . NewString ( want ... ) , got )
}
}
func assertEmptyResults ( t * testing . T , res ... Result ) {
t . Helper ( )
for _ , r := range res {
assert . Empty ( t , r . Cluster )
assert . Empty ( t , r . Namespaces )
assert . Empty ( t , r . Velero )
}
}
type tarWriter struct {
t * testing . T
buf * bytes . Buffer
gzw * gzip . Writer
tw * tar . Writer
}
func newTarWriter ( t * testing . T ) * tarWriter {
tw := new ( tarWriter )
tw . t = t
tw . buf = new ( bytes . Buffer )
tw . gzw = gzip . NewWriter ( tw . buf )
tw . tw = tar . NewWriter ( tw . gzw )
return tw
}
2019-06-24 22:18:30 +00:00
func ( tw * tarWriter ) addItems ( groupResource string , items ... metav1 . Object ) * tarWriter {
2019-06-21 17:00:49 +00:00
tw . t . Helper ( )
for _ , obj := range items {
var path string
if obj . GetNamespace ( ) == "" {
2019-06-24 22:18:30 +00:00
path = fmt . Sprintf ( "resources/%s/cluster/%s.json" , groupResource , obj . GetName ( ) )
2019-06-21 17:00:49 +00:00
} else {
2019-06-24 22:18:30 +00:00
path = fmt . Sprintf ( "resources/%s/namespaces/%s/%s.json" , groupResource , obj . GetNamespace ( ) , obj . GetName ( ) )
2019-06-21 17:00:49 +00:00
}
tw . add ( path , obj )
}
return tw
}
2019-06-20 15:06:58 +00:00
func ( tw * tarWriter ) add ( name string , obj interface { } ) * tarWriter {
tw . t . Helper ( )
var data [ ] byte
var err error
switch obj . ( type ) {
case runtime . Object :
data , err = encode . Encode ( obj . ( runtime . Object ) , "json" )
case [ ] byte :
data = obj . ( [ ] byte )
default :
data , err = json . Marshal ( obj )
}
require . NoError ( tw . t , err )
require . NoError ( tw . t , tw . tw . WriteHeader ( & tar . Header {
Name : name ,
Size : int64 ( len ( data ) ) ,
Typeflag : tar . TypeReg ,
Mode : 0755 ,
ModTime : time . Now ( ) ,
} ) )
_ , err = tw . tw . Write ( data )
require . NoError ( tw . t , err )
return tw
}
func ( tw * tarWriter ) done ( ) * bytes . Buffer {
require . NoError ( tw . t , tw . tw . Close ( ) )
require . NoError ( tw . t , tw . gzw . Close ( ) )
return tw . buf
}
type harness struct {
* test . APIServer
restorer * kubernetesRestorer
log logrus . FieldLogger
}
func newHarness ( t * testing . T ) * harness {
t . Helper ( )
apiServer := test . NewAPIServer ( t )
log := logrus . StandardLogger ( )
discoveryHelper , err := discovery . NewHelper ( apiServer . DiscoveryClient , log )
require . NoError ( t , err )
return & harness {
APIServer : apiServer ,
restorer : & kubernetesRestorer {
discoveryHelper : discoveryHelper ,
dynamicFactory : client . NewDynamicFactory ( apiServer . DynamicClient ) ,
namespaceClient : apiServer . KubeClient . CoreV1 ( ) . Namespaces ( ) ,
resourceTerminatingTimeout : time . Minute ,
logger : log ,
fileSystem : testutil . NewFakeFileSystem ( ) ,
// unsupported
resticRestorerFactory : nil ,
resticTimeout : 0 ,
} ,
log : log ,
}
}
2019-06-27 18:57:47 +00:00
func ( h * harness ) addItems ( t * testing . T , resource * test . APIResource ) {
t . Helper ( )
h . DiscoveryClient . WithAPIResource ( resource )
require . NoError ( t , h . restorer . discoveryHelper . Refresh ( ) )
for _ , item := range resource . Items {
obj , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( item )
require . NoError ( t , err )
unstructuredObj := & unstructured . Unstructured { Object : obj }
// These fields have non-nil zero values in the unstructured objects. We remove
// them to make comparison easier in our tests.
unstructured . RemoveNestedField ( unstructuredObj . Object , "metadata" , "creationTimestamp" )
unstructured . RemoveNestedField ( unstructuredObj . Object , "status" )
if resource . Namespaced {
_ , err = h . DynamicClient . Resource ( resource . GVR ( ) ) . Namespace ( item . GetNamespace ( ) ) . Create ( unstructuredObj , metav1 . CreateOptions { } )
} else {
_ , err = h . DynamicClient . Resource ( resource . GVR ( ) ) . Create ( unstructuredObj , metav1 . CreateOptions { } )
}
require . NoError ( t , err )
}
}