initial structure for refactored pkg/restore tests

Signed-off-by: Steve Kriss <krisss@vmware.com>
This commit is contained in:
Steve Kriss
2019-06-20 09:06:58 -06:00
parent 0735ee7218
commit d421fcd85c
3 changed files with 310 additions and 12 deletions

78
pkg/restore/builder.go Normal file
View File

@@ -0,0 +1,78 @@
/*
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 (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
)
// Builder is a helper for concisely constructing Restore API objects.
type Builder struct {
restore velerov1api.Restore
}
// NewBuilder returns a Builder for a Restore with no namespace/name.
func NewBuilder() *Builder {
return NewNamedBuilder("", "")
}
// NewNamedBuilder returns a Builder for a Restore with the specified namespace
// and name.
func NewNamedBuilder(namespace, name string) *Builder {
return &Builder{
restore: velerov1api.Restore{
TypeMeta: metav1.TypeMeta{
APIVersion: velerov1api.SchemeGroupVersion.String(),
Kind: "Restore",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
},
}
}
// Restore returns the built Restore API object.
func (b *Builder) Restore() *velerov1api.Restore {
return &b.restore
}
// Backup sets the Restore's backup name.
func (b *Builder) Backup(name string) *Builder {
b.restore.Spec.BackupName = name
return b
}
// NamespaceMappings sets the Restore's namespace mappings.
func (b *Builder) NamespaceMappings(mapping ...string) *Builder {
if b.restore.Spec.NamespaceMapping == nil {
b.restore.Spec.NamespaceMapping = make(map[string]string)
}
if len(mapping)%2 != 0 {
panic("mapping must contain an even number of values")
}
for i := 0; i < len(mapping); i += 2 {
b.restore.Spec.NamespaceMapping[mapping[i]] = mapping[i+1]
}
return b
}

View File

@@ -0,0 +1,232 @@
/*
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"
"encoding/json"
"fmt"
"io"
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
"github.com/heptio/velero/pkg/backup"
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/discovery"
"github.com/heptio/velero/pkg/test"
"github.com/heptio/velero/pkg/util/encode"
testutil "github.com/heptio/velero/pkg/util/test"
)
func TestRestoreNew(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: "base case - restore a single resource",
restore: defaultRestore().Backup("backup-1").Restore(),
backup: backup.NewNamedBuilder(velerov1api.DefaultNamespace, "backup-1").Backup(),
tarball: newTarWriter(t).
add("metadata/version", []byte("1")).
add("resources/pods/namespaces/ns-1/pod-1.json", test.NewPod("ns-1", "pod-1")).
done(),
apiResources: []*test.APIResource{
test.Pods(),
},
want: map[*test.APIResource][]string{
test.Pods(): {"ns-1/pod-1"},
},
},
{
name: "restore a resource to a remapped namespace",
restore: defaultRestore().Backup("backup-1").NamespaceMappings("ns-1", "ns-2").Restore(),
backup: backup.NewNamedBuilder(velerov1api.DefaultNamespace, "backup-1").Backup(),
tarball: newTarWriter(t).
add("metadata/version", []byte("1")).
add("resources/pods/namespaces/ns-1/pod-1.json", test.NewPod("ns-1", "pod-1")).
done(),
apiResources: []*test.APIResource{
test.Pods(),
},
want: map[*test.APIResource][]string{
test.Pods(): {"ns-2/pod-1"},
},
},
}
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())
warnings, errs := h.restorer.Restore(
h.log,
tc.restore,
tc.backup,
nil, // volume snapshots
tc.tarball,
nil, // actions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
assertEmptyResults(t, warnings, errs)
assertAPIContents(t, h, tc.want)
})
}
}
func defaultRestore() *Builder {
return NewNamedBuilder(velerov1api.DefaultNamespace, "restore-1")
}
// 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) {
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
}
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,
}
}

View File

@@ -36,7 +36,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
api "github.com/heptio/velero/pkg/apis/velero/v1"
pkgclient "github.com/heptio/velero/pkg/client"
@@ -1962,14 +1961,3 @@ func (r *fakeAction) Execute(input *velero.RestoreItemActionExecuteInput) (*vele
return velero.NewRestoreItemActionExecuteOutput(res), nil
}
type fakeNamespaceClient struct {
createdNamespaces []*v1.Namespace
corev1.NamespaceInterface
}
func (nsc *fakeNamespaceClient) Create(ns *v1.Namespace) (*v1.Namespace, error) {
nsc.createdNamespaces = append(nsc.createdNamespaces, ns)
return ns, nil
}