2017-08-02 17:27:17 +00:00
|
|
|
/*
|
2019-03-28 19:21:56 +00:00
|
|
|
Copyright 2017, 2019 the Velero contributors.
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package restore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"testing"
|
2018-04-11 15:07:43 +00:00
|
|
|
"time"
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
"github.com/pkg/errors"
|
2018-10-16 14:28:05 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-08-02 17:27:17 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2018-06-22 19:32:03 +00:00
|
|
|
"github.com/stretchr/testify/mock"
|
2017-09-01 21:39:30 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-01-25 03:33:07 +00:00
|
|
|
v1 "k8s.io/api/core/v1"
|
2018-04-11 15:07:43 +00:00
|
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
2017-08-02 17:27:17 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2018-06-22 19:32:03 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2018-02-28 01:35:35 +00:00
|
|
|
"k8s.io/apimachinery/pkg/watch"
|
2018-10-16 14:28:05 +00:00
|
|
|
"k8s.io/client-go/kubernetes/scheme"
|
2017-08-02 17:27:17 +00:00
|
|
|
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
2018-10-23 14:36:11 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
api "github.com/heptio/velero/pkg/apis/velero/v1"
|
2019-03-28 19:21:56 +00:00
|
|
|
pkgclient "github.com/heptio/velero/pkg/client"
|
2019-01-25 03:33:07 +00:00
|
|
|
"github.com/heptio/velero/pkg/generated/clientset/versioned/fake"
|
|
|
|
informers "github.com/heptio/velero/pkg/generated/informers/externalversions"
|
|
|
|
"github.com/heptio/velero/pkg/kuberesource"
|
2019-03-14 20:35:06 +00:00
|
|
|
"github.com/heptio/velero/pkg/plugin/velero"
|
2019-01-25 03:33:07 +00:00
|
|
|
"github.com/heptio/velero/pkg/util/collections"
|
|
|
|
"github.com/heptio/velero/pkg/util/logging"
|
|
|
|
velerotest "github.com/heptio/velero/pkg/util/test"
|
|
|
|
"github.com/heptio/velero/pkg/volume"
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestPrioritizeResources(t *testing.T) {
|
2017-09-01 21:39:30 +00:00
|
|
|
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{
|
2017-10-26 21:22:39 +00:00
|
|
|
"v1": {"aaa", "bbb", "configmaps", "ddd", "namespaces", "ooo", "pods", "sss"},
|
2017-09-01 21:39:30 +00:00
|
|
|
},
|
|
|
|
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{
|
2017-10-26 21:22:39 +00:00
|
|
|
"v1": {"aaa", "bbb", "configmaps", "ddd", "namespaces", "ooo", "pods", "sss"},
|
2017-09-01 21:39:30 +00:00
|
|
|
},
|
|
|
|
priorities: []string{"namespaces", "configmaps", "pods"},
|
|
|
|
includes: []string{"namespaces", "aaa", "sss"},
|
|
|
|
expected: []string{"namespaces", "aaa", "sss"},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
{
|
2017-09-01 21:39:30 +00:00
|
|
|
name: "excludes are correctly applied",
|
|
|
|
apiResources: map[string][]string{
|
2017-10-26 21:22:39 +00:00
|
|
|
"v1": {"aaa", "bbb", "configmaps", "ddd", "namespaces", "ooo", "pods", "sss"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2017-09-01 21:39:30 +00:00
|
|
|
priorities: []string{"namespaces", "configmaps", "pods"},
|
|
|
|
includes: []string{"*"},
|
|
|
|
excludes: []string{"ooo", "pods"},
|
|
|
|
expected: []string{"namespaces", "configmaps", "aaa", "bbb", "ddd", "sss"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
logger := velerotest.NewLogger()
|
2017-09-14 21:27:31 +00:00
|
|
|
|
2017-09-01 21:39:30 +00:00
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2017-08-24 23:44:01 +00:00
|
|
|
var helperResourceList []*metav1.APIResourceList
|
2017-09-01 21:39:30 +00:00
|
|
|
|
|
|
|
for gv, resources := range test.apiResources {
|
|
|
|
resourceList := &metav1.APIResourceList{GroupVersion: gv}
|
|
|
|
for _, resource := range resources {
|
|
|
|
resourceList.APIResources = append(resourceList.APIResources, metav1.APIResource{Name: resource})
|
|
|
|
}
|
2017-08-24 23:44:01 +00:00
|
|
|
helperResourceList = append(helperResourceList, resourceList)
|
2017-09-01 21:39:30 +00:00
|
|
|
}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
helper := velerotest.NewFakeDiscoveryHelper(true, nil)
|
2017-08-24 23:44:01 +00:00
|
|
|
helper.ResourceList = helperResourceList
|
|
|
|
|
2017-09-01 21:39:30 +00:00
|
|
|
includesExcludes := collections.NewIncludesExcludes().Includes(test.includes...).Excludes(test.excludes...)
|
|
|
|
|
2017-09-14 21:27:31 +00:00
|
|
|
result, err := prioritizeResources(helper, test.priorities, includesExcludes, logger)
|
2017-09-01 21:39:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-09-01 21:39:30 +00:00
|
|
|
require.Equal(t, len(test.expected), len(result))
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-09-01 21:39:30 +00:00
|
|
|
for i := range result {
|
|
|
|
if e, a := test.expected[i], result[i].Resource; e != a {
|
|
|
|
t.Errorf("index %d, expected %s, got %s", i, e, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-10 18:43:53 +00:00
|
|
|
func TestRestoreNamespaceFiltering(t *testing.T) {
|
2017-08-02 17:27:17 +00:00
|
|
|
tests := []struct {
|
2017-10-10 18:43:53 +00:00
|
|
|
name string
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem *velerotest.FakeFileSystem
|
2017-10-10 18:43:53 +00:00
|
|
|
baseDir string
|
|
|
|
restore *api.Restore
|
|
|
|
expectedReadDirs []string
|
|
|
|
prioritizedResources []schema.GroupResource
|
2017-08-02 17:27:17 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "namespacesToRestore having * restores all namespaces",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"),
|
2017-08-02 17:27:17 +00:00
|
|
|
baseDir: "bak",
|
2017-08-27 16:42:10 +00:00
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"},
|
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "nodes"},
|
|
|
|
{Resource: "secrets"},
|
2017-10-10 18:43:53 +00:00
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "namespacesToRestore properly filters",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"),
|
2017-08-02 17:27:17 +00:00
|
|
|
baseDir: "bak",
|
2017-08-27 16:42:10 +00:00
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"b", "c"}}},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"},
|
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "nodes"},
|
|
|
|
{Resource: "secrets"},
|
2017-10-10 18:43:53 +00:00
|
|
|
},
|
2017-08-27 16:42:10 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "namespacesToRestore properly filters with exclusion filter",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"),
|
2017-08-27 16:42:10 +00:00
|
|
|
baseDir: "bak",
|
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}, ExcludedNamespaces: []string{"a"}}},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"},
|
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "nodes"},
|
|
|
|
{Resource: "secrets"},
|
2017-10-10 18:43:53 +00:00
|
|
|
},
|
2017-08-27 16:42:10 +00:00
|
|
|
},
|
|
|
|
{
|
2017-10-20 19:51:54 +00:00
|
|
|
name: "namespacesToRestore properly filters with inclusion & exclusion filters",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"),
|
2017-10-20 19:51:54 +00:00
|
|
|
baseDir: "bak",
|
|
|
|
restore: &api.Restore{
|
|
|
|
Spec: api.RestoreSpec{
|
|
|
|
IncludedNamespaces: []string{"a", "b", "c"},
|
|
|
|
ExcludedNamespaces: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/c"},
|
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "nodes"},
|
|
|
|
{Resource: "secrets"},
|
2017-10-10 18:43:53 +00:00
|
|
|
},
|
2017-08-27 16:42:10 +00:00
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2019-01-25 03:33:07 +00:00
|
|
|
log := velerotest.NewLogger()
|
2017-10-05 23:36:04 +00:00
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient := &velerotest.FakeNamespaceClient{}
|
|
|
|
|
2017-09-12 19:54:08 +00:00
|
|
|
ctx := &context{
|
2017-10-10 18:43:53 +00:00
|
|
|
restore: test.restore,
|
2018-09-07 14:42:57 +00:00
|
|
|
namespaceClient: nsClient,
|
2017-10-10 18:43:53 +00:00
|
|
|
fileSystem: test.fileSystem,
|
2018-09-30 20:45:32 +00:00
|
|
|
log: log,
|
2017-10-10 18:43:53 +00:00
|
|
|
prioritizedResources: test.prioritizedResources,
|
2019-03-28 19:21:56 +00:00
|
|
|
restoreDir: test.baseDir,
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient.On("Get", mock.Anything, metav1.GetOptions{}).Return(&v1.Namespace{}, nil)
|
|
|
|
|
2019-03-28 19:21:56 +00:00
|
|
|
warnings, errors := ctx.restoreFromDir()
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2017-08-02 17:27:17 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, errors.Velero)
|
2017-08-02 17:27:17 +00:00
|
|
|
assert.Empty(t, errors.Cluster)
|
|
|
|
assert.Empty(t, errors.Namespaces)
|
2018-06-07 17:29:59 +00:00
|
|
|
assert.Equal(t, test.expectedReadDirs, test.fileSystem.ReadDirCalls)
|
2017-08-02 17:27:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-10 18:43:53 +00:00
|
|
|
func TestRestorePriority(t *testing.T) {
|
2017-08-02 17:27:17 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem *velerotest.FakeFileSystem
|
2017-08-02 17:27:17 +00:00
|
|
|
restore *api.Restore
|
2017-10-10 18:43:53 +00:00
|
|
|
baseDir string
|
2017-08-02 17:27:17 +00:00
|
|
|
prioritizedResources []schema.GroupResource
|
2019-04-16 18:57:02 +00:00
|
|
|
expectedErrors Result
|
2017-08-02 17:27:17 +00:00
|
|
|
expectedReadDirs []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "cluster test",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"),
|
2017-10-10 18:43:53 +00:00
|
|
|
baseDir: "bak",
|
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
2017-08-02 17:27:17 +00:00
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "a"},
|
|
|
|
{Resource: "b"},
|
|
|
|
{Resource: "c"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/a/cluster", "bak/resources/c/cluster"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "resource priorities are applied",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"),
|
2017-10-10 18:43:53 +00:00
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
|
|
|
baseDir: "bak",
|
2017-08-02 17:27:17 +00:00
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "c"},
|
|
|
|
{Resource: "b"},
|
|
|
|
{Resource: "a"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/c/cluster", "bak/resources/a/cluster"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic namespace",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectory("bak/resources/a/namespaces/ns-1").WithDirectory("bak/resources/c/namespaces/ns-1"),
|
2017-10-10 18:43:53 +00:00
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
|
|
|
baseDir: "bak",
|
2017-08-02 17:27:17 +00:00
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "a"},
|
|
|
|
{Resource: "b"},
|
|
|
|
{Resource: "c"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/a/namespaces", "bak/resources/a/namespaces/ns-1", "bak/resources/c/namespaces", "bak/resources/c/namespaces/ns-1"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error in a single resource doesn't terminate restore immediately, but is returned",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2017-10-10 18:43:53 +00:00
|
|
|
WithFile("bak/resources/a/namespaces/ns-1/invalid-json.json", []byte("invalid json")).
|
|
|
|
WithDirectory("bak/resources/c/namespaces/ns-1"),
|
|
|
|
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
|
|
|
baseDir: "bak",
|
2017-08-02 17:27:17 +00:00
|
|
|
prioritizedResources: []schema.GroupResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "a"},
|
|
|
|
{Resource: "b"},
|
|
|
|
{Resource: "c"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2019-04-16 18:57:02 +00:00
|
|
|
expectedErrors: Result{
|
2017-08-02 17:27:17 +00:00
|
|
|
Namespaces: map[string][]string{
|
2017-10-10 18:43:53 +00:00
|
|
|
"ns-1": {"error decoding \"bak/resources/a/namespaces/ns-1/invalid-json.json\": invalid character 'i' looking for beginning of value"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
},
|
2017-10-10 18:43:53 +00:00
|
|
|
expectedReadDirs: []string{"bak/resources", "bak/resources/a/namespaces", "bak/resources/a/namespaces/ns-1", "bak/resources/c/namespaces", "bak/resources/c/namespaces/ns-1"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2019-01-25 03:33:07 +00:00
|
|
|
log := velerotest.NewLogger()
|
2017-10-05 23:36:04 +00:00
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient := &velerotest.FakeNamespaceClient{}
|
|
|
|
|
2017-09-12 19:54:08 +00:00
|
|
|
ctx := &context{
|
|
|
|
restore: test.restore,
|
2018-09-07 14:42:57 +00:00
|
|
|
namespaceClient: nsClient,
|
2017-09-12 19:54:08 +00:00
|
|
|
fileSystem: test.fileSystem,
|
|
|
|
prioritizedResources: test.prioritizedResources,
|
2018-09-30 20:45:32 +00:00
|
|
|
log: log,
|
2019-03-28 19:21:56 +00:00
|
|
|
restoreDir: test.baseDir,
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient.On("Get", mock.Anything, metav1.GetOptions{}).Return(&v1.Namespace{}, nil)
|
|
|
|
|
2019-03-28 19:21:56 +00:00
|
|
|
warnings, errors := ctx.restoreFromDir()
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2017-08-02 17:27:17 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
|
|
|
assert.Equal(t, test.expectedErrors, errors)
|
|
|
|
|
2018-06-07 17:29:59 +00:00
|
|
|
assert.Equal(t, test.expectedReadDirs, test.fileSystem.ReadDirCalls)
|
2017-08-02 17:27:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-02 17:09:44 +00:00
|
|
|
func TestNamespaceRemapping(t *testing.T) {
|
|
|
|
var (
|
|
|
|
baseDir = "bak"
|
|
|
|
restore = &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}, NamespaceMapping: map[string]string{"ns-1": "ns-2"}}}
|
2017-11-30 00:53:07 +00:00
|
|
|
prioritizedResources = []schema.GroupResource{{Resource: "namespaces"}, {Resource: "configmaps"}}
|
2017-11-02 17:09:44 +00:00
|
|
|
labelSelector = labels.NewSelector()
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem = velerotest.NewFakeFileSystem().
|
2017-11-30 00:53:07 +00:00
|
|
|
WithFile("bak/resources/configmaps/namespaces/ns-1/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()).
|
|
|
|
WithFile("bak/resources/namespaces/cluster/ns-1.json", newTestNamespace("ns-1").ToJSON())
|
|
|
|
expectedNS = "ns-2"
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs = toUnstructured(newTestConfigMap().WithNamespace("ns-2").ConfigMap)
|
2017-11-02 17:09:44 +00:00
|
|
|
)
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
resourceClient := &velerotest.FakeDynamicClient{}
|
2017-11-02 17:09:44 +00:00
|
|
|
for i := range expectedObjs {
|
2018-08-08 23:51:33 +00:00
|
|
|
addRestoreLabels(&expectedObjs[i], "", "")
|
2017-11-02 17:09:44 +00:00
|
|
|
resourceClient.On("Create", &expectedObjs[i]).Return(&expectedObjs[i], nil)
|
|
|
|
}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
2017-11-02 17:09:44 +00:00
|
|
|
resource := metav1.APIResource{Name: "configmaps", Namespaced: true}
|
|
|
|
gv := schema.GroupVersion{Group: "", Version: "v1"}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, resource, expectedNS).Return(resourceClient, nil)
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient := &velerotest.FakeNamespaceClient{}
|
2017-11-02 17:09:44 +00:00
|
|
|
|
|
|
|
ctx := &context{
|
|
|
|
dynamicFactory: dynamicFactory,
|
|
|
|
fileSystem: fileSystem,
|
|
|
|
selector: labelSelector,
|
2018-09-07 14:42:57 +00:00
|
|
|
namespaceClient: nsClient,
|
2017-11-02 17:09:44 +00:00
|
|
|
prioritizedResources: prioritizedResources,
|
|
|
|
restore: restore,
|
|
|
|
backup: &api.Backup{},
|
2019-01-25 03:33:07 +00:00
|
|
|
log: velerotest.NewLogger(),
|
2019-03-28 19:21:56 +00:00
|
|
|
applicableActions: make(map[schema.GroupResource][]resolvedAction),
|
|
|
|
resourceClients: make(map[resourceClientKey]pkgclient.Dynamic),
|
|
|
|
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
|
|
|
restoreDir: baseDir,
|
2017-11-02 17:09:44 +00:00
|
|
|
}
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient.On("Get", "ns-2", metav1.GetOptions{}).Return(&v1.Namespace{}, k8serrors.NewNotFound(schema.GroupResource{Resource: "namespaces"}, "ns-2"))
|
|
|
|
ns := newTestNamespace("ns-2").Namespace
|
|
|
|
nsClient.On("Create", ns).Return(ns, nil)
|
|
|
|
|
2019-03-28 19:21:56 +00:00
|
|
|
warnings, errors := ctx.restoreFromDir()
|
2017-11-02 17:09:44 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2017-11-02 17:09:44 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, errors.Velero)
|
2017-11-02 17:09:44 +00:00
|
|
|
assert.Empty(t, errors.Cluster)
|
|
|
|
assert.Empty(t, errors.Namespaces)
|
|
|
|
|
2017-11-30 00:53:07 +00:00
|
|
|
// ensure the remapped NS (only) was created via the namespaceClient
|
2018-09-07 14:42:57 +00:00
|
|
|
nsClient.AssertExpectations(t)
|
2017-11-30 00:53:07 +00:00
|
|
|
|
|
|
|
// ensure that we did not try to create namespaces via dynamic client
|
|
|
|
dynamicFactory.AssertNotCalled(t, "ClientForGroupVersionResource", gv, metav1.APIResource{Name: "namespaces", Namespaced: true}, "")
|
|
|
|
|
2017-11-02 17:09:44 +00:00
|
|
|
dynamicFactory.AssertExpectations(t)
|
|
|
|
resourceClient.AssertExpectations(t)
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
func TestRestoreResourceForNamespace(t *testing.T) {
|
2017-10-20 19:51:54 +00:00
|
|
|
var (
|
|
|
|
trueVal = true
|
|
|
|
falseVal = false
|
|
|
|
truePtr = &trueVal
|
|
|
|
falsePtr = &falseVal
|
|
|
|
)
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
tests := []struct {
|
2017-10-20 19:51:54 +00:00
|
|
|
name string
|
|
|
|
namespace string
|
|
|
|
resourcePath string
|
|
|
|
labelSelector labels.Selector
|
|
|
|
includeClusterResources *bool
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem *velerotest.FakeFileSystem
|
2017-11-21 17:24:43 +00:00
|
|
|
actions []resolvedAction
|
2019-04-16 18:57:02 +00:00
|
|
|
expectedErrors Result
|
2017-10-20 19:51:54 +00:00
|
|
|
expectedObjs []unstructured.Unstructured
|
2017-08-02 17:27:17 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "basic normal case",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2017-08-02 17:27:17 +00:00
|
|
|
WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()).
|
|
|
|
WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()),
|
|
|
|
expectedObjs: toUnstructured(
|
2018-08-08 23:33:09 +00:00
|
|
|
newNamedTestConfigMap("cm-1").ConfigMap,
|
|
|
|
newNamedTestConfigMap("cm-2").ConfigMap,
|
2017-08-02 17:27:17 +00:00
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "no such directory causes error",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem(),
|
2019-04-16 18:57:02 +00:00
|
|
|
expectedErrors: Result{
|
2017-08-02 17:27:17 +00:00
|
|
|
Namespaces: map[string][]string{
|
|
|
|
"ns-1": {"error reading \"configmaps\" resource directory: open configmaps: file does not exist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "empty directory is no-op",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithDirectory("configmaps"),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unmarshall failure does not cause immediate return",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2017-08-02 17:27:17 +00:00
|
|
|
WithFile("configmaps/cm-1-invalid.json", []byte("this is not valid json")).
|
|
|
|
WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()),
|
2019-04-16 18:57:02 +00:00
|
|
|
expectedErrors: Result{
|
2017-08-02 17:27:17 +00:00
|
|
|
Namespaces: map[string][]string{
|
|
|
|
"ns-1": {"error decoding \"configmaps/cm-1-invalid.json\": invalid character 'h' in literal true (expecting 'r')"},
|
|
|
|
},
|
|
|
|
},
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newNamedTestConfigMap("cm-2").ConfigMap),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "matching label selector correctly includes",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.SelectorFromSet(labels.Set(map[string]string{"foo": "bar"})),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ConfigMap),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "non-matching label selector correctly excludes",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.SelectorFromSet(labels.Set(map[string]string{"foo": "not-bar"})),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "namespace is remapped",
|
|
|
|
namespace: "ns-2",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().WithNamespace("ns-2").ConfigMap),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "custom restorer is correctly used",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
|
2017-11-21 17:24:43 +00:00
|
|
|
actions: []resolvedAction{
|
|
|
|
{
|
2019-03-14 20:35:06 +00:00
|
|
|
RestoreItemAction: newFakeAction("configmaps"),
|
2017-11-21 17:24:43 +00:00
|
|
|
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("configmaps"),
|
|
|
|
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
|
|
|
selector: labels.Everything(),
|
|
|
|
},
|
|
|
|
},
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"fake-restorer": "foo"}).ConfigMap),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "custom restorer for different group/resource is not used",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
|
2017-11-21 17:24:43 +00:00
|
|
|
actions: []resolvedAction{
|
|
|
|
{
|
2019-03-14 20:35:06 +00:00
|
|
|
RestoreItemAction: newFakeAction("foo-resource"),
|
2017-11-21 17:24:43 +00:00
|
|
|
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("foo-resource"),
|
|
|
|
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
|
|
|
selector: labels.Everything(),
|
|
|
|
},
|
|
|
|
},
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2017-10-20 19:51:54 +00:00
|
|
|
{
|
|
|
|
name: "cluster-scoped resources are skipped when IncludeClusterResources=false",
|
|
|
|
namespace: "",
|
|
|
|
resourcePath: "persistentvolumes",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: falsePtr,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "namespaced resources are not skipped when IncludeClusterResources=false",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: falsePtr,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "cluster-scoped resources are not skipped when IncludeClusterResources=true",
|
|
|
|
namespace: "",
|
|
|
|
resourcePath: "persistentvolumes",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: truePtr,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestPV().PersistentVolume),
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "namespaced resources are not skipped when IncludeClusterResources=true",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: truePtr,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "cluster-scoped resources are not skipped when IncludeClusterResources=nil",
|
|
|
|
namespace: "",
|
|
|
|
resourcePath: "persistentvolumes",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: nil,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestPV().PersistentVolume),
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "namespaced resources are not skipped when IncludeClusterResources=nil",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: nil,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
2018-04-11 15:07:43 +00:00
|
|
|
{
|
|
|
|
name: "serviceaccounts are restored",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "serviceaccounts",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: nil,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().WithFile("serviceaccounts/sa-1.json", newTestServiceAccount().ToJSON()),
|
2018-08-08 23:33:09 +00:00
|
|
|
expectedObjs: toUnstructured(newTestServiceAccount().ServiceAccount),
|
2018-04-11 15:07:43 +00:00
|
|
|
},
|
2018-06-28 14:06:55 +00:00
|
|
|
{
|
|
|
|
name: "non-mirror pods are restored",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "pods",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: nil,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2018-06-28 14:06:55 +00:00
|
|
|
WithFile(
|
|
|
|
"pods/pod.json",
|
|
|
|
NewTestUnstructured().
|
|
|
|
WithAPIVersion("v1").
|
|
|
|
WithKind("Pod").
|
|
|
|
WithNamespace("ns-1").
|
|
|
|
WithName("pod1").
|
|
|
|
ToJSON(),
|
|
|
|
),
|
|
|
|
expectedObjs: []unstructured.Unstructured{
|
|
|
|
*(NewTestUnstructured().
|
|
|
|
WithAPIVersion("v1").
|
|
|
|
WithKind("Pod").
|
|
|
|
WithNamespace("ns-1").
|
|
|
|
WithName("pod1").
|
|
|
|
Unstructured),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "mirror pods are not restored",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "pods",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
includeClusterResources: nil,
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2018-06-28 14:06:55 +00:00
|
|
|
WithFile(
|
|
|
|
"pods/pod.json",
|
|
|
|
NewTestUnstructured().
|
|
|
|
WithAPIVersion("v1").
|
|
|
|
WithKind("Pod").
|
|
|
|
WithNamespace("ns-1").
|
|
|
|
WithName("pod1").
|
|
|
|
WithAnnotations(v1.MirrorPodAnnotationKey).
|
|
|
|
ToJSON(),
|
|
|
|
),
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2018-10-16 14:28:05 +00:00
|
|
|
var (
|
|
|
|
client = fake.NewSimpleClientset()
|
|
|
|
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
2019-01-25 03:33:07 +00:00
|
|
|
snapshotLocationLister = sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister()
|
2018-10-16 14:28:05 +00:00
|
|
|
)
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2019-01-25 03:33:07 +00:00
|
|
|
resourceClient := &velerotest.FakeDynamicClient{}
|
2017-08-02 17:27:17 +00:00
|
|
|
for i := range test.expectedObjs {
|
2018-08-08 23:51:33 +00:00
|
|
|
addRestoreLabels(&test.expectedObjs[i], "my-restore", "my-backup")
|
2017-08-02 17:27:17 +00:00
|
|
|
resourceClient.On("Create", &test.expectedObjs[i]).Return(&test.expectedObjs[i], nil)
|
|
|
|
}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
2017-10-02 20:53:08 +00:00
|
|
|
gv := schema.GroupVersion{Group: "", Version: "v1"}
|
2017-11-21 17:24:43 +00:00
|
|
|
|
2018-06-28 14:06:55 +00:00
|
|
|
configMapResource := metav1.APIResource{Name: "configmaps", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, configMapResource, test.namespace).Return(resourceClient, nil)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-10-20 19:51:54 +00:00
|
|
|
pvResource := metav1.APIResource{Name: "persistentvolumes", Namespaced: false}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, pvResource, test.namespace).Return(resourceClient, nil)
|
2018-02-28 01:35:35 +00:00
|
|
|
resourceClient.On("Watch", metav1.ListOptions{}).Return(&fakeWatch{}, nil)
|
2018-09-07 14:42:57 +00:00
|
|
|
if test.resourcePath == "persistentvolumes" {
|
|
|
|
resourceClient.On("Get", mock.Anything, metav1.GetOptions{}).Return(&unstructured.Unstructured{}, k8serrors.NewNotFound(schema.GroupResource{Resource: "persistentvolumes"}, ""))
|
|
|
|
}
|
2017-10-20 19:51:54 +00:00
|
|
|
|
2018-10-25 20:24:57 +00:00
|
|
|
// Assume the persistentvolume doesn't already exist in the cluster.
|
2018-04-11 15:07:43 +00:00
|
|
|
saResource := metav1.APIResource{Name: "serviceaccounts", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, saResource, test.namespace).Return(resourceClient, nil)
|
|
|
|
|
2018-06-28 14:06:55 +00:00
|
|
|
podResource := metav1.APIResource{Name: "pods", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, podResource, test.namespace).Return(resourceClient, nil)
|
|
|
|
|
2017-09-12 19:54:08 +00:00
|
|
|
ctx := &context{
|
|
|
|
dynamicFactory: dynamicFactory,
|
2017-11-21 17:24:43 +00:00
|
|
|
actions: test.actions,
|
2017-09-12 19:54:08 +00:00
|
|
|
fileSystem: test.fileSystem,
|
|
|
|
selector: test.labelSelector,
|
|
|
|
restore: &api.Restore{
|
2017-08-02 17:27:17 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: api.DefaultNamespace,
|
|
|
|
Name: "my-restore",
|
|
|
|
},
|
2017-10-20 19:51:54 +00:00
|
|
|
Spec: api.RestoreSpec{
|
|
|
|
IncludeClusterResources: test.includeClusterResources,
|
2018-08-08 23:51:33 +00:00
|
|
|
BackupName: "my-backup",
|
2017-10-20 19:51:54 +00:00
|
|
|
},
|
2017-09-12 19:54:08 +00:00
|
|
|
},
|
2018-10-16 14:28:05 +00:00
|
|
|
backup: &api.Backup{},
|
2019-01-25 03:33:07 +00:00
|
|
|
log: velerotest.NewLogger(),
|
2018-10-16 14:28:05 +00:00
|
|
|
pvRestorer: &pvRestorer{
|
|
|
|
logger: logging.DefaultLogger(logrus.DebugLevel),
|
2019-03-27 18:22:04 +00:00
|
|
|
volumeSnapshotterGetter: &fakeVolumeSnapshotterGetter{
|
2019-04-24 20:16:03 +00:00
|
|
|
volumeMap: map[velerotest.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"},
|
2018-10-16 14:28:05 +00:00
|
|
|
volumeID: "volume-1",
|
|
|
|
},
|
|
|
|
snapshotLocationLister: snapshotLocationLister,
|
2019-04-23 23:58:59 +00:00
|
|
|
backup: &api.Backup{},
|
|
|
|
},
|
|
|
|
applicableActions: make(map[schema.GroupResource][]resolvedAction),
|
|
|
|
resourceClients: make(map[resourceClientKey]pkgclient.Dynamic),
|
|
|
|
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
warnings, errors := ctx.restoreResource(test.resourcePath, test.namespace, test.resourcePath)
|
|
|
|
|
|
|
|
assert.Empty(t, warnings.Velero)
|
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
|
|
|
assert.Equal(t, test.expectedErrors, errors)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRestoreLabels(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
namespace string
|
|
|
|
resourcePath string
|
|
|
|
backupName string
|
|
|
|
restoreName string
|
|
|
|
labelSelector labels.Selector
|
|
|
|
includeClusterResources *bool
|
|
|
|
fileSystem *velerotest.FakeFileSystem
|
|
|
|
actions []resolvedAction
|
|
|
|
expectedErrors Result
|
|
|
|
expectedObjs []unstructured.Unstructured
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "backup name and restore name less than 63 characters",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
backupName: "less-than-63-characters",
|
|
|
|
restoreName: "less-than-63-characters-12345",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
|
|
|
WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()),
|
|
|
|
expectedObjs: toUnstructured(
|
|
|
|
newNamedTestConfigMap("cm-1").WithLabels(map[string]string{
|
|
|
|
api.BackupNameLabel: "less-than-63-characters",
|
|
|
|
api.RestoreNameLabel: "less-than-63-characters-12345",
|
|
|
|
}).ConfigMap,
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "backup name equal to 63 characters",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
backupName: "the-really-long-kube-service-name-that-is-exactly-63-characters",
|
|
|
|
restoreName: "the-really-long-kube-service-name-that-is-exactly-63-characters-12345",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
|
|
|
WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()),
|
|
|
|
expectedObjs: toUnstructured(
|
|
|
|
newNamedTestConfigMap("cm-1").WithLabels(map[string]string{
|
|
|
|
api.BackupNameLabel: "the-really-long-kube-service-name-that-is-exactly-63-characters",
|
|
|
|
api.RestoreNameLabel: "the-really-long-kube-service-name-that-is-exactly-63-char0871f3",
|
|
|
|
}).ConfigMap,
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "backup name greter than 63 characters",
|
|
|
|
namespace: "ns-1",
|
|
|
|
resourcePath: "configmaps",
|
|
|
|
backupName: "the-really-long-kube-service-name-that-is-much-greater-than-63-characters",
|
|
|
|
restoreName: "the-really-long-kube-service-name-that-is-much-greater-than-63-characters-12345",
|
|
|
|
labelSelector: labels.NewSelector(),
|
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
|
|
|
WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()),
|
|
|
|
expectedObjs: toUnstructured(
|
|
|
|
newNamedTestConfigMap("cm-1").WithLabels(map[string]string{
|
|
|
|
api.BackupNameLabel: "the-really-long-kube-service-name-that-is-much-greater-th8a11b3",
|
|
|
|
api.RestoreNameLabel: "the-really-long-kube-service-name-that-is-much-greater-th1bf26f",
|
|
|
|
}).ConfigMap,
|
|
|
|
),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
client = fake.NewSimpleClientset()
|
|
|
|
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
|
|
|
snapshotLocationLister = sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister()
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
resourceClient := &velerotest.FakeDynamicClient{}
|
|
|
|
for i := range test.expectedObjs {
|
|
|
|
resourceClient.On("Create", &test.expectedObjs[i]).Return(&test.expectedObjs[i], nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
|
|
|
gv := schema.GroupVersion{Group: "", Version: "v1"}
|
|
|
|
|
|
|
|
configMapResource := metav1.APIResource{Name: "configmaps", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, configMapResource, test.namespace).Return(resourceClient, nil)
|
|
|
|
|
|
|
|
ctx := &context{
|
|
|
|
dynamicFactory: dynamicFactory,
|
|
|
|
actions: test.actions,
|
|
|
|
fileSystem: test.fileSystem,
|
|
|
|
selector: test.labelSelector,
|
|
|
|
restore: &api.Restore{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: api.DefaultNamespace,
|
|
|
|
Name: test.restoreName,
|
|
|
|
},
|
|
|
|
Spec: api.RestoreSpec{
|
|
|
|
IncludeClusterResources: test.includeClusterResources,
|
|
|
|
BackupName: test.backupName,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
backup: &api.Backup{},
|
|
|
|
log: velerotest.NewLogger(),
|
|
|
|
pvRestorer: &pvRestorer{
|
|
|
|
logger: logging.DefaultLogger(logrus.DebugLevel),
|
|
|
|
volumeSnapshotterGetter: &fakeVolumeSnapshotterGetter{
|
|
|
|
volumeMap: map[velerotest.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"},
|
|
|
|
volumeID: "volume-1",
|
|
|
|
},
|
|
|
|
snapshotLocationLister: snapshotLocationLister,
|
2018-10-19 18:54:24 +00:00
|
|
|
backup: &api.Backup{},
|
2018-10-16 14:28:05 +00:00
|
|
|
},
|
2019-03-28 19:21:56 +00:00
|
|
|
applicableActions: make(map[schema.GroupResource][]resolvedAction),
|
|
|
|
resourceClients: make(map[resourceClientKey]pkgclient.Dynamic),
|
|
|
|
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
2017-09-12 19:54:08 +00:00
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-10-20 19:51:54 +00:00
|
|
|
warnings, errors := ctx.restoreResource(test.resourcePath, test.namespace, test.resourcePath)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2017-08-02 17:27:17 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
|
|
|
assert.Equal(t, test.expectedErrors, errors)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-11 15:07:43 +00:00
|
|
|
func TestRestoringExistingServiceAccount(t *testing.T) {
|
|
|
|
fromCluster := newTestServiceAccount()
|
|
|
|
fromClusterUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fromCluster.ServiceAccount)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
different := newTestServiceAccount().WithImagePullSecret("image-secret").WithSecret("secret")
|
|
|
|
differentUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(different.ServiceAccount)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
expectedPatch []byte
|
|
|
|
fromBackup *unstructured.Unstructured
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "fromCluster and fromBackup are exactly the same",
|
|
|
|
fromBackup: &unstructured.Unstructured{Object: fromClusterUnstructured},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "fromCluster and fromBackup are different",
|
|
|
|
fromBackup: &unstructured.Unstructured{Object: differentUnstructured},
|
|
|
|
expectedPatch: []byte(`{"imagePullSecrets":[{"name":"image-secret"}],"secrets":[{"name":"secret"}]}`),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2019-01-25 03:33:07 +00:00
|
|
|
resourceClient := &velerotest.FakeDynamicClient{}
|
2018-04-11 15:07:43 +00:00
|
|
|
defer resourceClient.AssertExpectations(t)
|
|
|
|
name := fromCluster.GetName()
|
|
|
|
|
|
|
|
// restoreResource will add the restore label to object provided to create, so we need to make a copy to provide to our expected call
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
for k, v := range test.fromBackup.Object {
|
|
|
|
m[k] = v
|
|
|
|
}
|
|
|
|
fromBackupWithLabel := &unstructured.Unstructured{Object: m}
|
2018-08-08 23:51:33 +00:00
|
|
|
addRestoreLabels(fromBackupWithLabel, "my-restore", "my-backup")
|
2018-04-11 15:07:43 +00:00
|
|
|
// resetMetadataAndStatus will strip the creationTimestamp before calling Create
|
|
|
|
fromBackupWithLabel.SetCreationTimestamp(metav1.Time{Time: time.Time{}})
|
|
|
|
|
|
|
|
resourceClient.On("Create", fromBackupWithLabel).Return(new(unstructured.Unstructured), k8serrors.NewAlreadyExists(kuberesource.ServiceAccounts, name))
|
|
|
|
resourceClient.On("Get", name, metav1.GetOptions{}).Return(&unstructured.Unstructured{Object: fromClusterUnstructured}, nil)
|
|
|
|
|
|
|
|
if len(test.expectedPatch) > 0 {
|
|
|
|
resourceClient.On("Patch", name, test.expectedPatch).Return(test.fromBackup, nil)
|
|
|
|
}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
2018-04-11 15:07:43 +00:00
|
|
|
gv := schema.GroupVersion{Group: "", Version: "v1"}
|
|
|
|
|
|
|
|
resource := metav1.APIResource{Name: "serviceaccounts", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, resource, "ns-1").Return(resourceClient, nil)
|
|
|
|
fromBackupJSON, err := json.Marshal(test.fromBackup)
|
|
|
|
require.NoError(t, err)
|
|
|
|
ctx := &context{
|
|
|
|
dynamicFactory: dynamicFactory,
|
|
|
|
actions: []resolvedAction{},
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2018-04-11 15:07:43 +00:00
|
|
|
WithFile("foo/resources/serviceaccounts/namespaces/ns-1/sa-1.json", fromBackupJSON),
|
|
|
|
selector: labels.NewSelector(),
|
|
|
|
restore: &api.Restore{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: api.DefaultNamespace,
|
|
|
|
Name: "my-restore",
|
|
|
|
},
|
|
|
|
Spec: api.RestoreSpec{
|
|
|
|
IncludeClusterResources: nil,
|
2018-08-08 23:51:33 +00:00
|
|
|
BackupName: "my-backup",
|
2018-04-11 15:07:43 +00:00
|
|
|
},
|
|
|
|
},
|
2019-03-28 19:21:56 +00:00
|
|
|
backup: &api.Backup{},
|
|
|
|
log: velerotest.NewLogger(),
|
|
|
|
applicableActions: make(map[schema.GroupResource][]resolvedAction),
|
|
|
|
resourceClients: make(map[resourceClientKey]pkgclient.Dynamic),
|
|
|
|
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
2018-04-11 15:07:43 +00:00
|
|
|
}
|
|
|
|
warnings, errors := ctx.restoreResource("serviceaccounts", "ns-1", "foo/resources/serviceaccounts/namespaces/ns-1/")
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2018-04-11 15:07:43 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
2019-04-16 18:57:02 +00:00
|
|
|
assert.Equal(t, Result{}, errors)
|
2018-04-11 15:07:43 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-22 19:32:03 +00:00
|
|
|
func TestRestoringPVsWithoutSnapshots(t *testing.T) {
|
|
|
|
pv := `apiVersion: v1
|
|
|
|
kind: PersistentVolume
|
|
|
|
metadata:
|
|
|
|
annotations:
|
|
|
|
EXPORT_block: "\nEXPORT\n{\n\tExport_Id = 1;\n\tPath = /export/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce;\n\tPseudo
|
|
|
|
= /export/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce;\n\tAccess_Type = RW;\n\tSquash
|
|
|
|
= no_root_squash;\n\tSecType = sys;\n\tFilesystem_id = 1.1;\n\tFSAL {\n\t\tName
|
|
|
|
= VFS;\n\t}\n}\n"
|
|
|
|
Export_Id: "1"
|
|
|
|
Project_Id: "0"
|
|
|
|
Project_block: ""
|
|
|
|
Provisioner_Id: 5fdf4025-78a5-11e8-9ece-0242ac110004
|
|
|
|
kubernetes.io/createdby: nfs-dynamic-provisioner
|
|
|
|
pv.kubernetes.io/provisioned-by: example.com/nfs
|
|
|
|
volume.beta.kubernetes.io/mount-options: vers=4.1
|
|
|
|
creationTimestamp: 2018-06-25T18:27:35Z
|
|
|
|
finalizers:
|
|
|
|
- kubernetes.io/pv-protection
|
|
|
|
name: pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
resourceVersion: "2576"
|
|
|
|
selfLink: /api/v1/persistentvolumes/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
uid: 6ecd24e4-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
spec:
|
|
|
|
accessModes:
|
|
|
|
- ReadWriteMany
|
|
|
|
capacity:
|
|
|
|
storage: 1Mi
|
|
|
|
claimRef:
|
|
|
|
apiVersion: v1
|
|
|
|
kind: PersistentVolumeClaim
|
|
|
|
name: nfs
|
|
|
|
namespace: default
|
|
|
|
resourceVersion: "2565"
|
|
|
|
uid: 6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
nfs:
|
|
|
|
path: /export/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
server: 10.103.235.254
|
|
|
|
storageClassName: example-nfs
|
|
|
|
status:
|
|
|
|
phase: Bound`
|
|
|
|
|
|
|
|
pvc := `apiVersion: v1
|
|
|
|
kind: PersistentVolumeClaim
|
|
|
|
metadata:
|
|
|
|
annotations:
|
|
|
|
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"5fdf5572-78a5-11e8-9ece-0242ac110004","leaseDurationSeconds":15,"acquireTime":"2018-06-25T18:27:35Z","renewTime":"2018-06-25T18:27:37Z","leaderTransitions":0}'
|
|
|
|
kubectl.kubernetes.io/last-applied-configuration: |
|
|
|
|
{"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"nfs","namespace":"default"},"spec":{"accessModes":["ReadWriteMany"],"resources":{"requests":{"storage":"1Mi"}},"storageClassName":"example-nfs"}}
|
|
|
|
pv.kubernetes.io/bind-completed: "yes"
|
|
|
|
pv.kubernetes.io/bound-by-controller: "yes"
|
|
|
|
volume.beta.kubernetes.io/storage-provisioner: example.com/nfs
|
|
|
|
creationTimestamp: 2018-06-25T18:27:28Z
|
|
|
|
finalizers:
|
|
|
|
- kubernetes.io/pvc-protection
|
|
|
|
name: nfs
|
|
|
|
namespace: default
|
|
|
|
resourceVersion: "2578"
|
|
|
|
selfLink: /api/v1/namespaces/default/persistentvolumeclaims/nfs
|
|
|
|
uid: 6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
spec:
|
|
|
|
accessModes:
|
|
|
|
- ReadWriteMany
|
|
|
|
resources:
|
|
|
|
requests:
|
|
|
|
storage: 1Mi
|
|
|
|
storageClassName: example-nfs
|
|
|
|
volumeName: pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
status:
|
|
|
|
accessModes:
|
|
|
|
- ReadWriteMany
|
|
|
|
capacity:
|
|
|
|
storage: 1Mi
|
|
|
|
phase: Bound`
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
haveSnapshot bool
|
|
|
|
reclaimPolicy string
|
|
|
|
expectPVCVolumeName bool
|
|
|
|
expectedPVCAnnotationsMissing sets.String
|
|
|
|
expectPVCreation bool
|
2018-10-25 20:24:57 +00:00
|
|
|
expectPVFound bool
|
2018-06-22 19:32:03 +00:00
|
|
|
}{
|
|
|
|
{
|
2019-04-11 19:57:17 +00:00
|
|
|
name: "backup has snapshot, reclaim policy delete, no existing PV found",
|
2018-06-22 19:32:03 +00:00
|
|
|
haveSnapshot: true,
|
|
|
|
reclaimPolicy: "Delete",
|
|
|
|
expectPVCVolumeName: true,
|
|
|
|
expectPVCreation: true,
|
|
|
|
},
|
|
|
|
{
|
2019-04-11 19:57:17 +00:00
|
|
|
name: "backup has snapshot, reclaim policy delete, existing PV found",
|
2018-06-22 19:32:03 +00:00
|
|
|
haveSnapshot: true,
|
2018-10-23 18:44:05 +00:00
|
|
|
reclaimPolicy: "Delete",
|
|
|
|
expectPVCVolumeName: true,
|
2018-10-25 20:24:57 +00:00
|
|
|
expectPVCreation: false,
|
|
|
|
expectPVFound: true,
|
|
|
|
},
|
|
|
|
{
|
2019-04-11 19:57:17 +00:00
|
|
|
name: "backup has snapshot, reclaim policy retain, no existing PV found",
|
2018-10-23 18:44:05 +00:00
|
|
|
haveSnapshot: true,
|
2018-06-22 19:32:03 +00:00
|
|
|
reclaimPolicy: "Retain",
|
|
|
|
expectPVCVolumeName: true,
|
|
|
|
expectPVCreation: true,
|
|
|
|
},
|
|
|
|
{
|
2019-04-11 19:57:17 +00:00
|
|
|
name: "backup has snapshot, reclaim policy retain, existing PV found",
|
2018-10-25 20:24:57 +00:00
|
|
|
haveSnapshot: true,
|
|
|
|
reclaimPolicy: "Retain",
|
|
|
|
expectPVCVolumeName: true,
|
|
|
|
expectPVCreation: false,
|
|
|
|
expectPVFound: true,
|
|
|
|
},
|
|
|
|
{
|
2019-04-11 19:57:17 +00:00
|
|
|
name: "backup has snapshot, reclaim policy retain, existing PV found",
|
2018-10-25 20:24:57 +00:00
|
|
|
haveSnapshot: true,
|
|
|
|
reclaimPolicy: "Retain",
|
|
|
|
expectPVCVolumeName: true,
|
|
|
|
expectPVCreation: false,
|
|
|
|
expectPVFound: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "no snapshot, reclaim policy delete, no existing PV",
|
2018-06-22 19:32:03 +00:00
|
|
|
haveSnapshot: false,
|
|
|
|
reclaimPolicy: "Delete",
|
|
|
|
expectPVCVolumeName: false,
|
|
|
|
expectedPVCAnnotationsMissing: sets.NewString("pv.kubernetes.io/bind-completed", "pv.kubernetes.io/bound-by-controller"),
|
|
|
|
},
|
|
|
|
{
|
2018-10-25 20:24:57 +00:00
|
|
|
name: "no snapshot, reclaim policy retain, no existing PV found",
|
2018-06-22 19:32:03 +00:00
|
|
|
haveSnapshot: false,
|
|
|
|
reclaimPolicy: "Retain",
|
|
|
|
expectPVCVolumeName: true,
|
|
|
|
expectPVCreation: true,
|
|
|
|
},
|
2018-10-25 20:24:57 +00:00
|
|
|
{
|
|
|
|
name: "no snapshot, reclaim policy retain, existing PV found",
|
|
|
|
haveSnapshot: false,
|
|
|
|
reclaimPolicy: "Retain",
|
|
|
|
expectPVCVolumeName: true,
|
|
|
|
expectPVCreation: false,
|
|
|
|
expectPVFound: true,
|
|
|
|
},
|
2018-06-22 19:32:03 +00:00
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2019-01-25 03:33:07 +00:00
|
|
|
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
2018-06-22 19:32:03 +00:00
|
|
|
gv := schema.GroupVersion{Group: "", Version: "v1"}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
pvClient := &velerotest.FakeDynamicClient{}
|
2018-06-22 19:32:03 +00:00
|
|
|
defer pvClient.AssertExpectations(t)
|
|
|
|
|
|
|
|
pvResource := metav1.APIResource{Name: "persistentvolumes", Namespaced: false}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, pvResource, "").Return(pvClient, nil)
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
pvcClient := &velerotest.FakeDynamicClient{}
|
2018-06-22 19:32:03 +00:00
|
|
|
defer pvcClient.AssertExpectations(t)
|
|
|
|
|
|
|
|
pvcResource := metav1.APIResource{Name: "persistentvolumeclaims", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, pvcResource, "default").Return(pvcClient, nil)
|
|
|
|
|
|
|
|
obj, _, err := scheme.Codecs.UniversalDecoder(v1.SchemeGroupVersion).Decode([]byte(pv), nil, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
pvObj, ok := obj.(*v1.PersistentVolume)
|
|
|
|
require.True(t, ok)
|
|
|
|
pvObj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimPolicy(test.reclaimPolicy)
|
|
|
|
pvBytes, err := json.Marshal(pvObj)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
obj, _, err = scheme.Codecs.UniversalDecoder(v1.SchemeGroupVersion).Decode([]byte(pvc), nil, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
pvcObj, ok := obj.(*v1.PersistentVolumeClaim)
|
|
|
|
require.True(t, ok)
|
|
|
|
pvcBytes, err := json.Marshal(pvcObj)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
unstructuredPVCMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pvcObj)
|
|
|
|
require.NoError(t, err)
|
|
|
|
unstructuredPVC := &unstructured.Unstructured{Object: unstructuredPVCMap}
|
|
|
|
|
|
|
|
nsClient := &velerotest.FakeNamespaceClient{}
|
|
|
|
ns := newTestNamespace(pvcObj.Namespace).Namespace
|
|
|
|
nsClient.On("Get", pvcObj.Namespace, mock.Anything).Return(ns, nil)
|
|
|
|
|
2018-06-22 19:32:03 +00:00
|
|
|
backup := &api.Backup{}
|
|
|
|
|
|
|
|
pvRestorer := new(mockPVRestorer)
|
|
|
|
defer pvRestorer.AssertExpectations(t)
|
|
|
|
|
|
|
|
ctx := &context{
|
|
|
|
dynamicFactory: dynamicFactory,
|
|
|
|
actions: []resolvedAction{},
|
2019-01-25 03:33:07 +00:00
|
|
|
fileSystem: velerotest.NewFakeFileSystem().
|
2018-06-22 19:32:03 +00:00
|
|
|
WithFile("foo/resources/persistentvolumes/cluster/pv.json", pvBytes).
|
|
|
|
WithFile("foo/resources/persistentvolumeclaims/default/pvc.json", pvcBytes),
|
|
|
|
selector: labels.NewSelector(),
|
|
|
|
prioritizedResources: []schema.GroupResource{
|
|
|
|
kuberesource.PersistentVolumes,
|
|
|
|
kuberesource.PersistentVolumeClaims,
|
|
|
|
},
|
|
|
|
restore: &api.Restore{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: api.DefaultNamespace,
|
|
|
|
Name: "my-restore",
|
|
|
|
},
|
|
|
|
},
|
2019-03-28 19:21:56 +00:00
|
|
|
backup: backup,
|
|
|
|
log: velerotest.NewLogger(),
|
|
|
|
pvsToProvision: sets.NewString(),
|
|
|
|
pvRestorer: pvRestorer,
|
|
|
|
namespaceClient: nsClient,
|
|
|
|
applicableActions: make(map[schema.GroupResource][]resolvedAction),
|
|
|
|
resourceClients: make(map[resourceClientKey]pkgclient.Dynamic),
|
|
|
|
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
2018-06-22 19:32:03 +00:00
|
|
|
}
|
|
|
|
|
2019-04-11 19:57:17 +00:00
|
|
|
if test.haveSnapshot {
|
2018-10-23 18:44:05 +00:00
|
|
|
ctx.volumeSnapshots = append(ctx.volumeSnapshots, &volume.Snapshot{
|
|
|
|
Spec: volume.SnapshotSpec{
|
|
|
|
PersistentVolumeName: "pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce",
|
|
|
|
},
|
|
|
|
Status: volume.SnapshotStatus{
|
|
|
|
ProviderSnapshotID: "snap",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-22 19:32:03 +00:00
|
|
|
unstructuredPVMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pvObj)
|
|
|
|
require.NoError(t, err)
|
|
|
|
unstructuredPV := &unstructured.Unstructured{Object: unstructuredPVMap}
|
|
|
|
|
2018-10-25 20:24:57 +00:00
|
|
|
if test.expectPVFound {
|
2018-09-07 14:42:57 +00:00
|
|
|
// Copy the PV so that later modifcations don't affect what's returned by our faked calls.
|
|
|
|
inClusterPV := unstructuredPV.DeepCopy()
|
|
|
|
pvClient.On("Get", inClusterPV.GetName(), metav1.GetOptions{}).Return(inClusterPV, nil)
|
|
|
|
pvClient.On("Create", mock.Anything).Return(inClusterPV, k8serrors.NewAlreadyExists(kuberesource.PersistentVolumes, inClusterPV.GetName()))
|
|
|
|
inClusterPVC := unstructuredPVC.DeepCopy()
|
|
|
|
pvcClient.On("Get", pvcObj.Name, mock.Anything).Return(inClusterPVC, nil)
|
2018-10-25 20:24:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only set up the client expectation if the test has the proper prerequisites
|
|
|
|
if test.haveSnapshot || test.reclaimPolicy != "Delete" {
|
2018-11-08 15:56:14 +00:00
|
|
|
pvClient.On("Get", unstructuredPV.GetName(), metav1.GetOptions{}).Return(&unstructured.Unstructured{}, k8serrors.NewNotFound(schema.GroupResource{Resource: "persistentvolumes"}, unstructuredPV.GetName()))
|
2018-10-25 20:24:57 +00:00
|
|
|
}
|
|
|
|
|
2018-06-22 19:32:03 +00:00
|
|
|
pvToRestore := unstructuredPV.DeepCopy()
|
|
|
|
restoredPV := unstructuredPV.DeepCopy()
|
|
|
|
|
|
|
|
if test.expectPVCreation {
|
|
|
|
// just to ensure we have the data flowing correctly
|
|
|
|
restoredPV.Object["foo"] = "bar"
|
|
|
|
pvRestorer.On("executePVAction", pvToRestore).Return(restoredPV, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
resetMetadataAndStatus(unstructuredPV)
|
2018-08-08 23:51:33 +00:00
|
|
|
addRestoreLabels(unstructuredPV, ctx.restore.Name, ctx.restore.Spec.BackupName)
|
2018-06-22 19:32:03 +00:00
|
|
|
unstructuredPV.Object["foo"] = "bar"
|
|
|
|
|
|
|
|
if test.expectPVCreation {
|
|
|
|
createdPV := unstructuredPV.DeepCopy()
|
|
|
|
pvClient.On("Create", unstructuredPV).Return(createdPV, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restore PV
|
|
|
|
warnings, errors := ctx.restoreResource("persistentvolumes", "", "foo/resources/persistentvolumes/cluster/")
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2018-06-22 19:32:03 +00:00
|
|
|
assert.Empty(t, warnings.Namespaces)
|
2019-04-16 18:57:02 +00:00
|
|
|
assert.Equal(t, Result{}, errors)
|
2018-09-07 14:42:57 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
2018-10-25 20:24:57 +00:00
|
|
|
|
2018-06-22 19:32:03 +00:00
|
|
|
// Prep PVC restore
|
|
|
|
// Handle expectations
|
|
|
|
if !test.expectPVCVolumeName {
|
|
|
|
pvcObj.Spec.VolumeName = ""
|
|
|
|
}
|
|
|
|
for _, key := range test.expectedPVCAnnotationsMissing.List() {
|
|
|
|
delete(pvcObj.Annotations, key)
|
|
|
|
}
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
// Recreate the unstructured PVC since the object was edited.
|
|
|
|
unstructuredPVCMap, err = runtime.DefaultUnstructuredConverter.ToUnstructured(pvcObj)
|
2018-06-22 19:32:03 +00:00
|
|
|
require.NoError(t, err)
|
2018-09-07 14:42:57 +00:00
|
|
|
unstructuredPVC = &unstructured.Unstructured{Object: unstructuredPVCMap}
|
2018-06-22 19:32:03 +00:00
|
|
|
|
|
|
|
resetMetadataAndStatus(unstructuredPVC)
|
2018-08-08 23:51:33 +00:00
|
|
|
addRestoreLabels(unstructuredPVC, ctx.restore.Name, ctx.restore.Spec.BackupName)
|
2018-06-22 19:32:03 +00:00
|
|
|
|
|
|
|
createdPVC := unstructuredPVC.DeepCopy()
|
|
|
|
// just to ensure we have the data flowing correctly
|
|
|
|
createdPVC.Object["foo"] = "bar"
|
|
|
|
|
|
|
|
pvcClient.On("Create", unstructuredPVC).Return(createdPVC, nil)
|
|
|
|
|
|
|
|
// Restore PVC
|
|
|
|
warnings, errors = ctx.restoreResource("persistentvolumeclaims", "default", "foo/resources/persistentvolumeclaims/default/")
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
assert.Empty(t, warnings.Velero)
|
2018-06-22 19:32:03 +00:00
|
|
|
assert.Empty(t, warnings.Cluster)
|
|
|
|
assert.Empty(t, warnings.Namespaces)
|
2019-04-16 18:57:02 +00:00
|
|
|
assert.Equal(t, Result{}, errors)
|
2018-06-22 19:32:03 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockPVRestorer struct {
|
|
|
|
mock.Mock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mockPVRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
|
|
|
args := r.Called(obj)
|
|
|
|
return args.Get(0).(*unstructured.Unstructured), args.Error(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockWatch struct {
|
|
|
|
mock.Mock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *mockWatch) Stop() {
|
|
|
|
w.Called()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *mockWatch) ResultChan() <-chan watch.Event {
|
|
|
|
args := w.Called()
|
|
|
|
return args.Get(0).(chan watch.Event)
|
|
|
|
}
|
|
|
|
|
2018-02-28 01:35:35 +00:00
|
|
|
type fakeWatch struct{}
|
|
|
|
|
|
|
|
func (w *fakeWatch) Stop() {}
|
|
|
|
|
|
|
|
func (w *fakeWatch) ResultChan() <-chan watch.Event {
|
|
|
|
return make(chan watch.Event)
|
|
|
|
}
|
|
|
|
|
2017-08-02 20:35:35 +00:00
|
|
|
func TestHasControllerOwner(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
object map[string]interface{}
|
|
|
|
expectOwner bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "missing metadata",
|
|
|
|
object: map[string]interface{}{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "missing ownerReferences",
|
|
|
|
object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{},
|
|
|
|
},
|
|
|
|
expectOwner: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "have ownerReferences, no controller fields",
|
|
|
|
object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"ownerReferences": []interface{}{
|
|
|
|
map[string]interface{}{"foo": "bar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectOwner: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "have ownerReferences, controller=false",
|
|
|
|
object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"ownerReferences": []interface{}{
|
|
|
|
map[string]interface{}{"controller": false},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectOwner: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "have ownerReferences, controller=true",
|
|
|
|
object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"ownerReferences": []interface{}{
|
|
|
|
map[string]interface{}{"controller": false},
|
|
|
|
map[string]interface{}{"controller": false},
|
|
|
|
map[string]interface{}{"controller": true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectOwner: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
u := &unstructured.Unstructured{Object: test.object}
|
|
|
|
hasOwner := hasControllerOwner(u.GetOwnerReferences())
|
|
|
|
assert.Equal(t, test.expectOwner, hasOwner)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
func TestResetMetadataAndStatus(t *testing.T) {
|
|
|
|
tests := []struct {
|
2017-12-21 21:23:48 +00:00
|
|
|
name string
|
|
|
|
obj *unstructured.Unstructured
|
|
|
|
expectedErr bool
|
|
|
|
expectedRes *unstructured.Unstructured
|
2017-11-21 17:24:43 +00:00
|
|
|
}{
|
|
|
|
{
|
2017-12-21 21:23:48 +00:00
|
|
|
name: "no metadata causes error",
|
|
|
|
obj: NewTestUnstructured().Unstructured,
|
|
|
|
expectedErr: true,
|
2017-11-21 17:24:43 +00:00
|
|
|
},
|
|
|
|
{
|
2017-12-21 21:23:48 +00:00
|
|
|
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,
|
2017-11-21 17:24:43 +00:00
|
|
|
},
|
|
|
|
{
|
2017-12-21 21:23:48 +00:00
|
|
|
name: "don't keep status",
|
|
|
|
obj: NewTestUnstructured().WithMetadata().WithStatus().Unstructured,
|
|
|
|
expectedErr: false,
|
|
|
|
expectedRes: NewTestUnstructured().WithMetadata().Unstructured,
|
2017-11-21 17:24:43 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2017-12-21 21:23:48 +00:00
|
|
|
res, err := resetMetadataAndStatus(test.obj)
|
2017-11-21 17:24:43 +00:00
|
|
|
|
|
|
|
if assert.Equal(t, test.expectedErr, err != nil) {
|
|
|
|
assert.Equal(t, test.expectedRes, res)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-26 20:07:50 +00:00
|
|
|
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) {
|
2019-01-25 03:33:07 +00:00
|
|
|
u := velerotest.UnstructuredOrDie(test.content)
|
2018-04-26 20:07:50 +00:00
|
|
|
backup, err := isCompleted(u, test.groupResource)
|
|
|
|
|
|
|
|
if assert.Equal(t, test.expectedErr, err != nil) {
|
|
|
|
assert.Equal(t, test.expected, backup)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-07 14:42:57 +00:00
|
|
|
func TestShouldRestore(t *testing.T) {
|
|
|
|
pv := `apiVersion: v1
|
|
|
|
kind: PersistentVolume
|
|
|
|
metadata:
|
|
|
|
annotations:
|
|
|
|
EXPORT_block: "\nEXPORT\n{\n\tExport_Id = 1;\n\tPath = /export/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce;\n\tPseudo
|
|
|
|
= /export/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce;\n\tAccess_Type = RW;\n\tSquash
|
|
|
|
= no_root_squash;\n\tSecType = sys;\n\tFilesystem_id = 1.1;\n\tFSAL {\n\t\tName
|
|
|
|
= VFS;\n\t}\n}\n"
|
|
|
|
Export_Id: "1"
|
|
|
|
Project_Id: "0"
|
|
|
|
Project_block: ""
|
|
|
|
Provisioner_Id: 5fdf4025-78a5-11e8-9ece-0242ac110004
|
|
|
|
kubernetes.io/createdby: nfs-dynamic-provisioner
|
|
|
|
pv.kubernetes.io/provisioned-by: example.com/nfs
|
|
|
|
volume.beta.kubernetes.io/mount-options: vers=4.1
|
|
|
|
creationTimestamp: 2018-06-25T18:27:35Z
|
|
|
|
finalizers:
|
|
|
|
- kubernetes.io/pv-protection
|
|
|
|
name: pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
resourceVersion: "2576"
|
|
|
|
selfLink: /api/v1/persistentvolumes/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
uid: 6ecd24e4-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
spec:
|
|
|
|
accessModes:
|
|
|
|
- ReadWriteMany
|
|
|
|
capacity:
|
|
|
|
storage: 1Mi
|
|
|
|
claimRef:
|
|
|
|
apiVersion: v1
|
|
|
|
kind: PersistentVolumeClaim
|
|
|
|
name: nfs
|
|
|
|
namespace: default
|
|
|
|
resourceVersion: "2565"
|
|
|
|
uid: 6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
nfs:
|
|
|
|
path: /export/pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
server: 10.103.235.254
|
|
|
|
storageClassName: example-nfs
|
|
|
|
status:
|
|
|
|
phase: Bound`
|
|
|
|
|
|
|
|
pvc := `apiVersion: v1
|
|
|
|
kind: PersistentVolumeClaim
|
|
|
|
metadata:
|
|
|
|
annotations:
|
|
|
|
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"5fdf5572-78a5-11e8-9ece-0242ac110004","leaseDurationSeconds":15,"acquireTime":"2018-06-25T18:27:35Z","renewTime":"2018-06-25T18:27:37Z","leaderTransitions":0}'
|
|
|
|
kubectl.kubernetes.io/last-applied-configuration: |
|
|
|
|
{"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"nfs","namespace":"default"},"spec":{"accessModes":["ReadWriteMany"],"resources":{"requests":{"storage":"1Mi"}},"storageClassName":"example-nfs"}}
|
|
|
|
pv.kubernetes.io/bind-completed: "yes"
|
|
|
|
pv.kubernetes.io/bound-by-controller: "yes"
|
|
|
|
volume.beta.kubernetes.io/storage-provisioner: example.com/nfs
|
|
|
|
creationTimestamp: 2018-06-25T18:27:28Z
|
|
|
|
finalizers:
|
|
|
|
- kubernetes.io/pvc-protection
|
|
|
|
name: nfs
|
|
|
|
namespace: default
|
|
|
|
resourceVersion: "2578"
|
|
|
|
selfLink: /api/v1/namespaces/default/persistentvolumeclaims/nfs
|
|
|
|
uid: 6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
spec:
|
|
|
|
accessModes:
|
|
|
|
- ReadWriteMany
|
|
|
|
resources:
|
|
|
|
requests:
|
|
|
|
storage: 1Mi
|
|
|
|
storageClassName: example-nfs
|
|
|
|
volumeName: pvc-6a74b5af-78a5-11e8-a0d8-e2ad1e9734ce
|
|
|
|
status:
|
|
|
|
accessModes:
|
|
|
|
- ReadWriteMany
|
|
|
|
capacity:
|
|
|
|
storage: 1Mi
|
|
|
|
phase: Bound`
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
expectNSFound bool
|
|
|
|
expectPVFound bool
|
|
|
|
pvPhase string
|
|
|
|
expectPVCFound bool
|
|
|
|
expectPVCGet bool
|
|
|
|
expectPVCDeleting bool
|
|
|
|
expectNSGet bool
|
|
|
|
expectNSDeleting bool
|
|
|
|
nsPhase v1.NamespacePhase
|
|
|
|
expectedResult bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "pv not found, no associated pvc or namespace",
|
|
|
|
expectedResult: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, phase released",
|
|
|
|
pvPhase: string(v1.VolumeReleased),
|
|
|
|
expectPVFound: true,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, has associated pvc and namespace that's aren't deleting",
|
|
|
|
expectPVFound: true,
|
|
|
|
expectPVCGet: true,
|
|
|
|
expectNSGet: true,
|
|
|
|
expectPVCFound: true,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, has associated pvc that's deleting, don't look up namespace",
|
|
|
|
expectPVFound: true,
|
|
|
|
expectPVCGet: true,
|
|
|
|
expectPVCFound: true,
|
|
|
|
expectPVCDeleting: true,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, has associated pvc that's not deleting, has associated namespace that's terminating",
|
|
|
|
expectPVFound: true,
|
|
|
|
expectPVCGet: true,
|
|
|
|
expectPVCFound: true,
|
|
|
|
expectNSGet: true,
|
|
|
|
expectNSFound: true,
|
|
|
|
nsPhase: v1.NamespaceTerminating,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, has associated pvc that's not deleting, has associated namespace that has deletion timestamp",
|
|
|
|
expectPVFound: true,
|
|
|
|
expectPVCGet: true,
|
|
|
|
expectPVCFound: true,
|
|
|
|
expectNSGet: true,
|
|
|
|
expectNSFound: true,
|
|
|
|
expectNSDeleting: true,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, associated pvc not found, namespace not queried",
|
|
|
|
expectPVFound: true,
|
|
|
|
expectPVCGet: true,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pv found, associated pvc found, namespace not found",
|
|
|
|
expectPVFound: true,
|
|
|
|
expectPVCGet: true,
|
|
|
|
expectPVCFound: true,
|
|
|
|
expectNSGet: true,
|
|
|
|
expectedResult: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
|
|
|
gv := schema.GroupVersion{Group: "", Version: "v1"}
|
|
|
|
|
|
|
|
pvClient := &velerotest.FakeDynamicClient{}
|
|
|
|
defer pvClient.AssertExpectations(t)
|
|
|
|
|
|
|
|
pvResource := metav1.APIResource{Name: "persistentvolumes", Namespaced: false}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, pvResource, "").Return(pvClient, nil)
|
|
|
|
|
|
|
|
pvcClient := &velerotest.FakeDynamicClient{}
|
|
|
|
defer pvcClient.AssertExpectations(t)
|
|
|
|
|
|
|
|
pvcResource := metav1.APIResource{Name: "persistentvolumeclaims", Namespaced: true}
|
|
|
|
dynamicFactory.On("ClientForGroupVersionResource", gv, pvcResource, "default").Return(pvcClient, nil)
|
|
|
|
|
|
|
|
obj, _, err := scheme.Codecs.UniversalDecoder(v1.SchemeGroupVersion).Decode([]byte(pv), nil, &unstructured.Unstructured{})
|
|
|
|
pvObj := obj.(*unstructured.Unstructured)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
obj, _, err = scheme.Codecs.UniversalDecoder(v1.SchemeGroupVersion).Decode([]byte(pvc), nil, &unstructured.Unstructured{})
|
|
|
|
pvcObj := obj.(*unstructured.Unstructured)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
nsClient := &velerotest.FakeNamespaceClient{}
|
|
|
|
defer nsClient.AssertExpectations(t)
|
|
|
|
ns := newTestNamespace(pvcObj.GetNamespace()).Namespace
|
|
|
|
|
|
|
|
// Set up test expectations
|
|
|
|
if test.pvPhase != "" {
|
2019-02-11 23:45:04 +00:00
|
|
|
require.NoError(t, unstructured.SetNestedField(pvObj.Object, test.pvPhase, "status", "phase"))
|
2018-09-07 14:42:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if test.expectPVFound {
|
|
|
|
pvClient.On("Get", pvObj.GetName(), metav1.GetOptions{}).Return(pvObj, nil)
|
|
|
|
} else {
|
|
|
|
pvClient.On("Get", pvObj.GetName(), metav1.GetOptions{}).Return(&unstructured.Unstructured{}, k8serrors.NewNotFound(schema.GroupResource{Resource: "persistentvolumes"}, pvObj.GetName()))
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.expectPVCDeleting {
|
|
|
|
pvcObj.SetDeletionTimestamp(&metav1.Time{Time: time.Now()})
|
|
|
|
}
|
|
|
|
|
|
|
|
// the pv needs to be found before moving on to look for pvc/namespace
|
|
|
|
// however, even if the pv is found, we may be testing the PV's phase and not expecting
|
|
|
|
// the pvc/namespace to be looked up
|
|
|
|
if test.expectPVCGet {
|
|
|
|
if test.expectPVCFound {
|
|
|
|
pvcClient.On("Get", pvcObj.GetName(), metav1.GetOptions{}).Return(pvcObj, nil)
|
|
|
|
} else {
|
|
|
|
pvcClient.On("Get", pvcObj.GetName(), metav1.GetOptions{}).Return(&unstructured.Unstructured{}, k8serrors.NewNotFound(schema.GroupResource{Resource: "persistentvolumeclaims"}, pvcObj.GetName()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.nsPhase != "" {
|
|
|
|
ns.Status.Phase = test.nsPhase
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.expectNSDeleting {
|
|
|
|
ns.SetDeletionTimestamp(&metav1.Time{Time: time.Now()})
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.expectNSGet {
|
|
|
|
if test.expectNSFound {
|
|
|
|
nsClient.On("Get", pvcObj.GetNamespace(), mock.Anything).Return(ns, nil)
|
|
|
|
} else {
|
|
|
|
nsClient.On("Get", pvcObj.GetNamespace(), metav1.GetOptions{}).Return(&v1.Namespace{}, k8serrors.NewNotFound(schema.GroupResource{Resource: "namespaces"}, pvcObj.GetNamespace()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := &context{
|
|
|
|
dynamicFactory: dynamicFactory,
|
|
|
|
log: velerotest.NewLogger(),
|
|
|
|
namespaceClient: nsClient,
|
|
|
|
resourceTerminatingTimeout: 1 * time.Millisecond,
|
|
|
|
}
|
|
|
|
|
|
|
|
result, err := ctx.shouldRestore(pvObj.GetName(), pvClient)
|
|
|
|
|
|
|
|
assert.Equal(t, test.expectedResult, result)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-28 19:21:56 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
type testUnstructured struct {
|
|
|
|
*unstructured.Unstructured
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTestUnstructured() *testUnstructured {
|
|
|
|
obj := &testUnstructured{
|
|
|
|
Unstructured: &unstructured.Unstructured{
|
|
|
|
Object: make(map[string]interface{}),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2018-06-28 14:06:55 +00:00
|
|
|
func (obj *testUnstructured) WithAPIVersion(v string) *testUnstructured {
|
|
|
|
obj.Object["apiVersion"] = v
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithKind(k string) *testUnstructured {
|
|
|
|
obj.Object["kind"] = k
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
func (obj *testUnstructured) WithMetadata(fields ...string) *testUnstructured {
|
|
|
|
return obj.withMap("metadata", fields...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithSpec(fields ...string) *testUnstructured {
|
2018-11-21 11:49:27 +00:00
|
|
|
if _, found := obj.Object["spec"]; found {
|
|
|
|
panic("spec already set - you probably didn't mean to do this twice!")
|
|
|
|
}
|
2017-11-21 17:24:43 +00:00
|
|
|
return obj.withMap("spec", fields...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithStatus(fields ...string) *testUnstructured {
|
|
|
|
return obj.withMap("status", fields...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithMetadataField(field string, value interface{}) *testUnstructured {
|
|
|
|
return obj.withMapEntry("metadata", field, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithSpecField(field string, value interface{}) *testUnstructured {
|
|
|
|
return obj.withMapEntry("spec", field, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithStatusField(field string, value interface{}) *testUnstructured {
|
|
|
|
return obj.withMapEntry("status", field, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithAnnotations(fields ...string) *testUnstructured {
|
2018-07-27 08:53:05 +00:00
|
|
|
vals := map[string]string{}
|
2017-11-21 17:24:43 +00:00
|
|
|
for _, field := range fields {
|
2018-07-27 08:53:05 +00:00
|
|
|
vals[field] = "foo"
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj.WithAnnotationValues(vals)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) WithAnnotationValues(fieldVals map[string]string) *testUnstructured {
|
|
|
|
annotations := make(map[string]interface{})
|
|
|
|
for field, val := range fieldVals {
|
|
|
|
annotations[field] = val
|
2017-11-21 17:24:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
obj = obj.WithMetadataField("annotations", annotations)
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2018-06-28 14:06:55 +00:00
|
|
|
func (obj *testUnstructured) WithNamespace(ns string) *testUnstructured {
|
|
|
|
return obj.WithMetadataField("namespace", ns)
|
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
func (obj *testUnstructured) WithName(name string) *testUnstructured {
|
|
|
|
return obj.WithMetadataField("name", name)
|
|
|
|
}
|
|
|
|
|
2018-06-28 14:06:55 +00:00
|
|
|
func (obj *testUnstructured) ToJSON() []byte {
|
|
|
|
bytes, err := json.Marshal(obj.Object)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return bytes
|
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
func (obj *testUnstructured) withMap(name string, fields ...string) *testUnstructured {
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
obj.Object[name] = m
|
|
|
|
|
|
|
|
for _, field := range fields {
|
|
|
|
m[field] = "foo"
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *testUnstructured) withMapEntry(mapName, field string, value interface{}) *testUnstructured {
|
|
|
|
var m map[string]interface{}
|
|
|
|
|
|
|
|
if res, ok := obj.Unstructured.Object[mapName]; !ok {
|
|
|
|
m = make(map[string]interface{})
|
|
|
|
obj.Unstructured.Object[mapName] = m
|
|
|
|
} else {
|
|
|
|
m = res.(map[string]interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
m[field] = value
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
func toUnstructured(objs ...runtime.Object) []unstructured.Unstructured {
|
|
|
|
res := make([]unstructured.Unstructured, 0, len(objs))
|
|
|
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
jsonObj, err := json.Marshal(obj)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var unstructuredObj unstructured.Unstructured
|
|
|
|
|
|
|
|
if err := json.Unmarshal(jsonObj, &unstructuredObj); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
metadata := unstructuredObj.Object["metadata"].(map[string]interface{})
|
|
|
|
|
|
|
|
delete(metadata, "creationTimestamp")
|
|
|
|
|
2017-10-20 19:51:54 +00:00
|
|
|
delete(unstructuredObj.Object, "status")
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
res = append(res, unstructuredObj)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2018-04-11 15:07:43 +00:00
|
|
|
type testServiceAccount struct {
|
|
|
|
*v1.ServiceAccount
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestServiceAccount() *testServiceAccount {
|
|
|
|
return &testServiceAccount{
|
|
|
|
ServiceAccount: &v1.ServiceAccount{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
APIVersion: "v1",
|
|
|
|
Kind: "ServiceAccount",
|
|
|
|
},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: "ns-1",
|
|
|
|
Name: "test-sa",
|
|
|
|
CreationTimestamp: metav1.Time{Time: time.Now()},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sa *testServiceAccount) WithImagePullSecret(name string) *testServiceAccount {
|
|
|
|
secret := v1.LocalObjectReference{Name: name}
|
|
|
|
sa.ImagePullSecrets = append(sa.ImagePullSecrets, secret)
|
|
|
|
return sa
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sa *testServiceAccount) WithSecret(name string) *testServiceAccount {
|
|
|
|
secret := v1.ObjectReference{Name: name}
|
|
|
|
sa.Secrets = append(sa.Secrets, secret)
|
|
|
|
return sa
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sa *testServiceAccount) ToJSON() []byte {
|
|
|
|
bytes, _ := json.Marshal(sa.ServiceAccount)
|
|
|
|
return bytes
|
|
|
|
}
|
|
|
|
|
2017-10-20 19:51:54 +00:00
|
|
|
type testPersistentVolume struct {
|
|
|
|
*v1.PersistentVolume
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestPV() *testPersistentVolume {
|
|
|
|
return &testPersistentVolume{
|
|
|
|
PersistentVolume: &v1.PersistentVolume{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
APIVersion: "v1",
|
|
|
|
Kind: "PersistentVolume",
|
|
|
|
},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "test-pv",
|
|
|
|
},
|
|
|
|
Status: v1.PersistentVolumeStatus{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pv *testPersistentVolume) ToJSON() []byte {
|
|
|
|
bytes, _ := json.Marshal(pv.PersistentVolume)
|
|
|
|
return bytes
|
|
|
|
}
|
|
|
|
|
2017-11-30 00:53:07 +00:00
|
|
|
type testNamespace struct {
|
|
|
|
*v1.Namespace
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestNamespace(name string) *testNamespace {
|
|
|
|
return &testNamespace{
|
|
|
|
Namespace: &v1.Namespace{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: name,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *testNamespace) ToJSON() []byte {
|
|
|
|
bytes, _ := json.Marshal(ns.Namespace)
|
|
|
|
return bytes
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
type testConfigMap struct {
|
|
|
|
*v1.ConfigMap
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestConfigMap() *testConfigMap {
|
|
|
|
return newNamedTestConfigMap("cm-1")
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNamedTestConfigMap(name string) *testConfigMap {
|
|
|
|
return &testConfigMap{
|
|
|
|
ConfigMap: &v1.ConfigMap{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
APIVersion: "v1",
|
|
|
|
Kind: "ConfigMap",
|
|
|
|
},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: "ns-1",
|
|
|
|
Name: name,
|
|
|
|
},
|
|
|
|
Data: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *testConfigMap) WithNamespace(name string) *testConfigMap {
|
|
|
|
cm.Namespace = name
|
|
|
|
return cm
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *testConfigMap) WithLabels(labels map[string]string) *testConfigMap {
|
|
|
|
cm.Labels = labels
|
|
|
|
return cm
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *testConfigMap) WithControllerOwner() *testConfigMap {
|
|
|
|
t := true
|
|
|
|
ownerRef := metav1.OwnerReference{
|
|
|
|
Controller: &t,
|
|
|
|
}
|
|
|
|
cm.ConfigMap.OwnerReferences = append(cm.ConfigMap.OwnerReferences, ownerRef)
|
|
|
|
return cm
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cm *testConfigMap) ToJSON() []byte {
|
|
|
|
bytes, _ := json.Marshal(cm.ConfigMap)
|
|
|
|
return bytes
|
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
type fakeAction struct {
|
|
|
|
resource string
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2019-03-27 18:22:04 +00:00
|
|
|
type fakeVolumeSnapshotterGetter struct {
|
|
|
|
fakeVolumeSnapshotter *velerotest.FakeVolumeSnapshotter
|
2019-04-24 20:16:03 +00:00
|
|
|
volumeMap map[velerotest.VolumeBackupInfo]string
|
2019-03-27 18:22:04 +00:00
|
|
|
volumeID string
|
2018-10-16 14:28:05 +00:00
|
|
|
}
|
|
|
|
|
2019-03-27 18:22:04 +00:00
|
|
|
func (r *fakeVolumeSnapshotterGetter) GetVolumeSnapshotter(provider string) (velero.VolumeSnapshotter, error) {
|
|
|
|
if r.fakeVolumeSnapshotter == nil {
|
|
|
|
r.fakeVolumeSnapshotter = &velerotest.FakeVolumeSnapshotter{
|
2018-10-16 14:28:05 +00:00
|
|
|
RestorableVolumes: r.volumeMap,
|
|
|
|
VolumeID: r.volumeID,
|
|
|
|
}
|
|
|
|
}
|
2019-03-27 18:22:04 +00:00
|
|
|
return r.fakeVolumeSnapshotter, nil
|
2018-10-16 14:28:05 +00:00
|
|
|
}
|
|
|
|
|
2017-11-21 17:24:43 +00:00
|
|
|
func newFakeAction(resource string) *fakeAction {
|
|
|
|
return &fakeAction{resource}
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:35:06 +00:00
|
|
|
func (r *fakeAction) AppliesTo() (velero.ResourceSelector, error) {
|
|
|
|
return velero.ResourceSelector{
|
2017-11-21 17:24:43 +00:00
|
|
|
IncludedResources: []string{r.resource},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-14 20:35:06 +00:00
|
|
|
func (r *fakeAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
|
2018-12-05 13:22:04 +00:00
|
|
|
labels, found, err := unstructured.NestedMap(input.Item.UnstructuredContent(), "metadata", "labels")
|
2017-08-02 17:27:17 +00:00
|
|
|
if err != nil {
|
2018-12-05 13:22:04 +00:00
|
|
|
return nil, err
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2019-01-07 22:07:53 +00:00
|
|
|
if !found {
|
|
|
|
labels = make(map[string]interface{})
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2019-01-07 22:07:53 +00:00
|
|
|
labels["fake-restorer"] = "foo"
|
|
|
|
|
2018-12-05 13:22:04 +00:00
|
|
|
if err := unstructured.SetNestedField(input.Item.UnstructuredContent(), labels, "metadata", "labels"); err != nil {
|
|
|
|
return nil, err
|
2019-01-07 22:07:53 +00:00
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2018-12-05 13:22:04 +00:00
|
|
|
unstructuredObj, ok := input.Item.(*unstructured.Unstructured)
|
2017-11-21 17:24:43 +00:00
|
|
|
if !ok {
|
2018-12-05 13:22:04 +00:00
|
|
|
return nil, errors.New("Unexpected type")
|
2017-11-21 17:24:43 +00:00
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
// want the baseline functionality too
|
2017-12-21 21:23:48 +00:00
|
|
|
res, err := resetMetadataAndStatus(unstructuredObj)
|
2017-11-21 17:24:43 +00:00
|
|
|
if err != nil {
|
2018-12-05 13:22:04 +00:00
|
|
|
return nil, err
|
2017-11-21 17:24:43 +00:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:35:06 +00:00
|
|
|
return velero.NewRestoreItemActionExecuteOutput(res), nil
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type fakeNamespaceClient struct {
|
2017-11-30 00:53:07 +00:00
|
|
|
createdNamespaces []*v1.Namespace
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
corev1.NamespaceInterface
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nsc *fakeNamespaceClient) Create(ns *v1.Namespace) (*v1.Namespace, error) {
|
2017-11-30 00:53:07 +00:00
|
|
|
nsc.createdNamespaces = append(nsc.createdNamespaces, ns)
|
2017-08-02 17:27:17 +00:00
|
|
|
return ns, nil
|
|
|
|
}
|