mirror of
https://git.vectorsigma.ru/public/k3s.git
synced 2026-08-03 13:30:05 +00:00
* Update libraries and codegen for k8s 1.32 Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> Signed-off-by: Brad Davidson <brad.davidson@rancher.com> * Fixes for 1.32 Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> Signed-off-by: Brad Davidson <brad.davidson@rancher.com> * Disable tests with down-rev agents These are broken by AuthorizeNodeWithSelectors being on by default. All agents must be upgraded to v1.32 or newer to work properly, until we backport RBAC changes to older branches. Signed-off-by: Brad Davidson <brad.davidson@rancher.com> --------- Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> Signed-off-by: Brad Davidson <brad.davidson@rancher.com> Co-authored-by: Brad Davidson <brad.davidson@rancher.com>
1744 lines
47 KiB
Go
1744 lines
47 KiB
Go
package s3
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/k3s-io/k3s/pkg/daemons/config"
|
|
"github.com/k3s-io/k3s/pkg/etcd/snapshot"
|
|
"github.com/rancher/dynamiclistener/cert"
|
|
"github.com/rancher/wrangler/v3/pkg/generated/controllers/core"
|
|
corev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1"
|
|
"github.com/rancher/wrangler/v3/pkg/generic/fake"
|
|
"github.com/sirupsen/logrus"
|
|
"go.uber.org/mock/gomock"
|
|
v1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/utils/lru"
|
|
)
|
|
|
|
var gmt = time.FixedZone("GMT", 0)
|
|
|
|
func Test_UnitControllerGetClient(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Dummy server with http and https listeners as a simple S3 mock
|
|
server := &http.Server{Handler: s3Router(t)}
|
|
|
|
// Create temp cert/key
|
|
cert, key, _ := cert.GenerateSelfSignedCertKey("localhost", []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")}, nil)
|
|
tempDir := t.TempDir()
|
|
certFile := filepath.Join(tempDir, "test.crt")
|
|
keyFile := filepath.Join(tempDir, "test.key")
|
|
os.WriteFile(certFile, cert, 0600)
|
|
os.WriteFile(keyFile, key, 0600)
|
|
|
|
listener, _ := net.Listen("tcp", ":0")
|
|
listenerTLS, _ := net.Listen("tcp", ":0")
|
|
|
|
_, port, _ := net.SplitHostPort(listener.Addr().String())
|
|
listenerAddr := net.JoinHostPort("localhost", port)
|
|
_, port, _ = net.SplitHostPort(listenerTLS.Addr().String())
|
|
listenerTLSAddr := net.JoinHostPort("localhost", port)
|
|
|
|
go server.Serve(listener)
|
|
go server.ServeTLS(listenerTLS, certFile, keyFile)
|
|
go func() {
|
|
<-ctx.Done()
|
|
server.Close()
|
|
}()
|
|
|
|
type fields struct {
|
|
clusterID string
|
|
tokenHash string
|
|
nodeName string
|
|
clientCache *lru.Cache
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
etcdS3 *config.EtcdS3
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
setup func(t *testing.T, a args, f fields, c *Client) (core.Interface, error)
|
|
want *Client
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Fail to get client with nil config",
|
|
args: args{
|
|
ctx: ctx,
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to get client when bucket not set",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to get client when bucket does not exist",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "badbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to get client with missing Secret",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
Endpoint: defaultEtcdS3.Endpoint,
|
|
Region: defaultEtcdS3.Region,
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
coreMock.v1.secret.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-config-secret", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.Secret, error) {
|
|
return nil, errorNotFound("secret", name)
|
|
})
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create client for config from secret",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
Endpoint: defaultEtcdS3.Endpoint,
|
|
Region: defaultEtcdS3.Region,
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
coreMock.v1.secret.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-config-secret", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.Secret, error) {
|
|
return &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
Type: v1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
"etcd-s3-access-key": []byte("test"),
|
|
"etcd-s3-bucket": []byte("testbucket"),
|
|
"etcd-s3-endpoint": []byte(listenerTLSAddr),
|
|
"etcd-s3-region": []byte("us-west-2"),
|
|
"etcd-s3-timeout": []byte("1m"),
|
|
"etcd-s3-endpoint-ca": cert,
|
|
},
|
|
}, nil
|
|
})
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create client for config from secret with CA in configmap",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
Endpoint: defaultEtcdS3.Endpoint,
|
|
Region: defaultEtcdS3.Region,
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
coreMock.v1.secret.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-config-secret", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.Secret, error) {
|
|
return &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
Type: v1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
"etcd-s3-access-key": []byte("test"),
|
|
"etcd-s3-bucket": []byte("testbucket"),
|
|
"etcd-s3-endpoint": []byte(listenerTLSAddr),
|
|
"etcd-s3-region": []byte("us-west-2"),
|
|
"etcd-s3-timeout": []byte("1m"),
|
|
"etcd-s3-endpoint-ca-name": []byte("my-etcd-s3-ca"),
|
|
"etcd-s3-skip-ssl-verify": []byte("false"),
|
|
},
|
|
}, nil
|
|
})
|
|
coreMock.v1.configMap.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-ca", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.ConfigMap, error) {
|
|
return &v1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
Data: map[string]string{
|
|
"dummy-ca": string(cert),
|
|
},
|
|
BinaryData: map[string][]byte{
|
|
"dummy-ca-binary": cert,
|
|
},
|
|
}, nil
|
|
})
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to create client for config from secret with CA in missing configmap",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
Endpoint: defaultEtcdS3.Endpoint,
|
|
Region: defaultEtcdS3.Region,
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
coreMock.v1.secret.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-config-secret", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.Secret, error) {
|
|
return &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
Type: v1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
"etcd-s3-access-key": []byte("test"),
|
|
"etcd-s3-bucket": []byte("testbucket"),
|
|
"etcd-s3-endpoint": []byte(listenerTLSAddr),
|
|
"etcd-s3-region": []byte("us-west-2"),
|
|
"etcd-s3-timeout": []byte("invalid"),
|
|
"etcd-s3-endpoint-ca": []byte("invalid"),
|
|
"etcd-s3-endpoint-ca-name": []byte("my-etcd-s3-ca"),
|
|
"etcd-s3-skip-ssl-verify": []byte("invalid"),
|
|
"etcd-s3-insecure": []byte("invalid"),
|
|
},
|
|
}, nil
|
|
})
|
|
coreMock.v1.configMap.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-ca", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.ConfigMap, error) {
|
|
return nil, errorNotFound("configmap", name)
|
|
})
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create insecure client for config from cli when secret is also set",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create skip-ssl-verify client for config from cli when secret is also set",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerTLSAddr,
|
|
SkipSSLVerify: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create client for config from cli when secret is not set",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Get cached client for config from secret",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
Endpoint: defaultEtcdS3.Endpoint,
|
|
Region: defaultEtcdS3.Region,
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
want: &Client{},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
c.etcdS3 = &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
}
|
|
f.clientCache.Add(*c.etcdS3, c)
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
coreMock.v1.secret.EXPECT().Get(metav1.NamespaceSystem, "my-etcd-s3-config-secret", gomock.Any()).AnyTimes().DoAndReturn(func(namespace, name string, _ metav1.GetOptions) (*v1.Secret, error) {
|
|
return &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
Type: v1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
"etcd-s3-access-key": []byte("test"),
|
|
"etcd-s3-bucket": []byte("testbucket"),
|
|
"etcd-s3-endpoint": []byte(listenerAddr),
|
|
"etcd-s3-insecure": []byte("true"),
|
|
},
|
|
}, nil
|
|
})
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Get cached client for config from cli",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
want: &Client{},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
c.etcdS3 = a.etcdS3
|
|
f.clientCache.Add(*c.etcdS3, c)
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create client for config from cli with proxy",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
Proxy: "http://" + listenerAddr,
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to create client for config from cli with invalid proxy",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
Proxy: "http://%invalid",
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to create client for config from cli with no proxy scheme",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
Proxy: "/proxy",
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Create client for config from cli with CA path",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerTLSAddr,
|
|
EndpointCA: certFile,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to create client for config from cli with invalid CA path",
|
|
args: args{
|
|
ctx: ctx,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Region: "us-west-2",
|
|
ConfigSecret: "my-etcd-s3-config-secret",
|
|
Endpoint: listenerTLSAddr,
|
|
EndpointCA: "/does/not/exist",
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
fields: fields{
|
|
clusterID: "1234",
|
|
tokenHash: "abcd",
|
|
nodeName: "server01",
|
|
clientCache: lru.New(5),
|
|
},
|
|
wantErr: true,
|
|
setup: func(t *testing.T, a args, f fields, c *Client) (core.Interface, error) {
|
|
coreMock := newCoreMock(gomock.NewController(t))
|
|
return coreMock, nil
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
core, err := tt.setup(t, tt.args, tt.fields, tt.want)
|
|
if err != nil {
|
|
t.Errorf("Setup for Controller.GetClient() failed = %v", err)
|
|
return
|
|
}
|
|
c := &Controller{
|
|
clusterID: tt.fields.clusterID,
|
|
tokenHash: tt.fields.tokenHash,
|
|
nodeName: tt.fields.nodeName,
|
|
clientCache: tt.fields.clientCache,
|
|
core: core,
|
|
}
|
|
got, err := c.GetClient(tt.args.ctx, tt.args.etcdS3)
|
|
t.Logf("Got client=%#v err=%v", got, err)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Controller.GetClient() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.want != nil && !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Controller.GetClient() = %+v\nWant = %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_UnitClientUpload(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Dummy server with http listener as a simple S3 mock
|
|
server := &http.Server{Handler: s3Router(t)}
|
|
|
|
listener, _ := net.Listen("tcp", ":0")
|
|
|
|
_, port, _ := net.SplitHostPort(listener.Addr().String())
|
|
listenerAddr := net.JoinHostPort("localhost", port)
|
|
|
|
go server.Serve(listener)
|
|
go func() {
|
|
<-ctx.Done()
|
|
server.Close()
|
|
}()
|
|
|
|
controller, err := Start(ctx, &config.Control{ClusterReset: true})
|
|
if err != nil {
|
|
t.Errorf("Start() for Client.Upload() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
tempDir := t.TempDir()
|
|
metadataDir := filepath.Join(tempDir, ".metadata")
|
|
snapshotDir := filepath.Join(tempDir, "snapshots")
|
|
snapshotPath := filepath.Join(snapshotDir, "snapshot-01")
|
|
metadataPath := filepath.Join(metadataDir, "snapshot-01")
|
|
if err := os.Mkdir(snapshotDir, 0700); err != nil {
|
|
t.Errorf("Mkdir() failed = %v", err)
|
|
return
|
|
}
|
|
if err := os.Mkdir(metadataDir, 0700); err != nil {
|
|
t.Errorf("Mkdir() failed = %v", err)
|
|
return
|
|
}
|
|
if err := os.WriteFile(snapshotPath, []byte("test snapshot file\n"), 0600); err != nil {
|
|
t.Errorf("WriteFile() failed = %v", err)
|
|
return
|
|
}
|
|
if err := os.WriteFile(metadataPath, []byte("test snapshot metadata\n"), 0600); err != nil {
|
|
t.Errorf("WriteFile() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
t.Logf("Using snapshot = %s, metadata = %s", snapshotPath, metadataPath)
|
|
|
|
type fields struct {
|
|
controller *Controller
|
|
etcdS3 *config.EtcdS3
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
snapshotPath string
|
|
extraMetadata *v1.ConfigMap
|
|
now time.Time
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want *snapshot.File
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Successful Upload",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotPath: snapshotPath,
|
|
extraMetadata: &v1.ConfigMap{Data: map[string]string{"foo": "bar"}},
|
|
now: time.Now(),
|
|
},
|
|
},
|
|
{
|
|
name: "Successful Upload with Prefix",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Folder: "testfolder",
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotPath: snapshotPath,
|
|
extraMetadata: &v1.ConfigMap{Data: map[string]string{"foo": "bar"}},
|
|
now: time.Now(),
|
|
},
|
|
},
|
|
{
|
|
name: "Fails Upload to Nonexistent Bucket",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "badbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotPath: snapshotPath,
|
|
extraMetadata: &v1.ConfigMap{Data: map[string]string{"foo": "bar"}},
|
|
now: time.Now(),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Fails Upload to Unauthorized Bucket",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "authbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotPath: snapshotPath,
|
|
extraMetadata: &v1.ConfigMap{Data: map[string]string{"foo": "bar"}},
|
|
now: time.Now(),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c, err := tt.fields.controller.GetClient(tt.args.ctx, tt.fields.etcdS3)
|
|
if err != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("GetClient for Client.Upload() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
return
|
|
}
|
|
got, err := c.Upload(tt.args.ctx, tt.args.snapshotPath, tt.args.extraMetadata, tt.args.now)
|
|
t.Logf("Got File=%#v err=%v", got, err)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Client.Upload() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.want != nil && !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Client.Upload() = %+v\nWant = %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_UnitClientDownload(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Dummy server with http listener as a simple S3 mock
|
|
server := &http.Server{Handler: s3Router(t)}
|
|
|
|
listener, _ := net.Listen("tcp", ":0")
|
|
|
|
_, port, _ := net.SplitHostPort(listener.Addr().String())
|
|
listenerAddr := net.JoinHostPort("localhost", port)
|
|
|
|
go server.Serve(listener)
|
|
go func() {
|
|
<-ctx.Done()
|
|
server.Close()
|
|
}()
|
|
|
|
controller, err := Start(ctx, &config.Control{ClusterReset: true})
|
|
if err != nil {
|
|
t.Errorf("Start() for Client.Download() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
snapshotName := "snapshot-01"
|
|
tempDir := t.TempDir()
|
|
metadataDir := filepath.Join(tempDir, ".metadata")
|
|
snapshotDir := filepath.Join(tempDir, "snapshots")
|
|
if err := os.Mkdir(snapshotDir, 0700); err != nil {
|
|
t.Errorf("Mkdir() failed = %v", err)
|
|
return
|
|
}
|
|
if err := os.Mkdir(metadataDir, 0700); err != nil {
|
|
t.Errorf("Mkdir() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
type fields struct {
|
|
etcdS3 *config.EtcdS3
|
|
controller *Controller
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
snapshotName string
|
|
snapshotDir string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Successful Download",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotName: snapshotName,
|
|
snapshotDir: snapshotDir,
|
|
},
|
|
},
|
|
{
|
|
name: "Unauthorizied Download",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "authbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotName: snapshotName,
|
|
snapshotDir: snapshotDir,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Nonexistent Bucket",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "badbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotName: snapshotName,
|
|
snapshotDir: snapshotDir,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Nonexistent Snapshot",
|
|
fields: fields{
|
|
controller: controller,
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
snapshotName: "badfile-1",
|
|
snapshotDir: snapshotDir,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c, err := tt.fields.controller.GetClient(tt.args.ctx, tt.fields.etcdS3)
|
|
if err != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("GetClient for Client.Upload() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
return
|
|
}
|
|
got, err := c.Download(tt.args.ctx, tt.args.snapshotName, tt.args.snapshotDir)
|
|
t.Logf("Got snapshotPath=%#v err=%v", got, err)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Client.Download() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.want != "" && got != tt.want {
|
|
t.Errorf("Client.Download() = %+v\nWant = %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_UnitClientListSnapshots(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Dummy server with http listener as a simple S3 mock
|
|
server := &http.Server{Handler: s3Router(t)}
|
|
|
|
listener, _ := net.Listen("tcp", ":0")
|
|
|
|
_, port, _ := net.SplitHostPort(listener.Addr().String())
|
|
listenerAddr := net.JoinHostPort("localhost", port)
|
|
|
|
go server.Serve(listener)
|
|
go func() {
|
|
<-ctx.Done()
|
|
server.Close()
|
|
}()
|
|
|
|
controller, err := Start(ctx, &config.Control{ClusterReset: true})
|
|
if err != nil {
|
|
t.Errorf("Start() for Client.Download() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
type fields struct {
|
|
etcdS3 *config.EtcdS3
|
|
controller *Controller
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want map[string]snapshot.File
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "List Snapshots",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
},
|
|
},
|
|
{
|
|
name: "List Snapshots with Prefix",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Folder: "testfolder",
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
},
|
|
},
|
|
{
|
|
name: "Fail to List Snapshots from Nonexistent Bucket",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "badbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Fail to List Snapshots from Unauthorized Bucket",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "authbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c, err := tt.fields.controller.GetClient(tt.args.ctx, tt.fields.etcdS3)
|
|
if err != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("GetClient for Client.Upload() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
return
|
|
}
|
|
got, err := c.ListSnapshots(tt.args.ctx)
|
|
t.Logf("Got snapshots=%#v err=%v", got, err)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Client.ListSnapshots() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.want != nil && !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Client.ListSnapshots() = %+v\nWant = %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_UnitClientDeleteSnapshot(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Dummy server with http listener as a simple S3 mock
|
|
server := &http.Server{Handler: s3Router(t)}
|
|
|
|
listener, _ := net.Listen("tcp", ":0")
|
|
|
|
_, port, _ := net.SplitHostPort(listener.Addr().String())
|
|
listenerAddr := net.JoinHostPort("localhost", port)
|
|
|
|
go server.Serve(listener)
|
|
go func() {
|
|
<-ctx.Done()
|
|
server.Close()
|
|
}()
|
|
|
|
controller, err := Start(ctx, &config.Control{ClusterReset: true})
|
|
if err != nil {
|
|
t.Errorf("Start() for Client.Download() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
type fields struct {
|
|
etcdS3 *config.EtcdS3
|
|
controller *Controller
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
key string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Delete Snapshot",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
key: "snapshot-01",
|
|
},
|
|
},
|
|
{
|
|
name: "Fails to Delete from Nonexistent Bucket",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "badbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
key: "snapshot-01",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Fails to Delete from Unauthorized Bucket",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "authbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
key: "snapshot-01",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Fails to Delete Nonexistent Snapshot",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
key: "badfile-1",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c, err := tt.fields.controller.GetClient(tt.args.ctx, tt.fields.etcdS3)
|
|
if err != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("GetClient for Client.DeleteSnapshot() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
return
|
|
}
|
|
err = c.DeleteSnapshot(tt.args.ctx, tt.args.key)
|
|
t.Logf("DeleteSnapshot got error=%v", err)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Client.DeleteSnapshot() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_UnitClientSnapshotRetention(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Dummy server with http listener as a simple S3 mock
|
|
server := &http.Server{Handler: s3Router(t)}
|
|
|
|
listener, _ := net.Listen("tcp", ":0")
|
|
|
|
_, port, _ := net.SplitHostPort(listener.Addr().String())
|
|
listenerAddr := net.JoinHostPort("localhost", port)
|
|
|
|
go server.Serve(listener)
|
|
go func() {
|
|
<-ctx.Done()
|
|
server.Close()
|
|
}()
|
|
|
|
controller, err := Start(ctx, &config.Control{ClusterReset: true})
|
|
if err != nil {
|
|
t.Errorf("Start() for Client.Download() failed = %v", err)
|
|
return
|
|
}
|
|
|
|
type fields struct {
|
|
etcdS3 *config.EtcdS3
|
|
controller *Controller
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
retention int
|
|
prefix string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Prune Snapshots - keep all, no folder",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 10,
|
|
prefix: "snapshot-",
|
|
},
|
|
},
|
|
{
|
|
name: "Prune Snapshots keep 2 of 3, no folder",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 2,
|
|
prefix: "snapshot-",
|
|
},
|
|
want: []string{"snapshot-03"},
|
|
},
|
|
{
|
|
name: "Prune Snapshots - keep 1 of 3, no folder",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 1,
|
|
prefix: "snapshot-",
|
|
},
|
|
want: []string{"snapshot-02", "snapshot-03"},
|
|
},
|
|
{
|
|
name: "Prune Snapshots - keep all, with folder",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Folder: "testfolder",
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 10,
|
|
prefix: "snapshot-",
|
|
},
|
|
},
|
|
{
|
|
name: "Prune Snapshots keep 2 of 3, with folder",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Folder: "testfolder",
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 2,
|
|
prefix: "snapshot-",
|
|
},
|
|
want: []string{"snapshot-06"},
|
|
},
|
|
{
|
|
name: "Prune Snapshots - keep 1 of 3, with folder",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "testbucket",
|
|
Endpoint: listenerAddr,
|
|
Folder: "testfolder",
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 1,
|
|
prefix: "snapshot-",
|
|
},
|
|
want: []string{"snapshot-05", "snapshot-06"},
|
|
},
|
|
{
|
|
name: "Fail to Prune from Unauthorized Bucket",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "authbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 1,
|
|
prefix: "snapshot-",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Fail to Prune from Nonexistent Bucket",
|
|
fields: fields{
|
|
etcdS3: &config.EtcdS3{
|
|
AccessKey: "test",
|
|
Bucket: "badbucket",
|
|
Endpoint: listenerAddr,
|
|
Insecure: true,
|
|
Region: defaultEtcdS3.Region,
|
|
Timeout: *defaultEtcdS3.Timeout.DeepCopy(),
|
|
},
|
|
controller: controller,
|
|
},
|
|
args: args{
|
|
ctx: ctx,
|
|
retention: 1,
|
|
prefix: "snapshot-",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c, err := tt.fields.controller.GetClient(tt.args.ctx, tt.fields.etcdS3)
|
|
if err != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("GetClient for Client.SnapshotRetention() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
return
|
|
}
|
|
got, err := c.SnapshotRetention(tt.args.ctx, tt.args.retention, tt.args.prefix)
|
|
t.Logf("Got snapshots=%#v err=%v", got, err)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Client.SnapshotRetention() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Client.SnapshotRetention() = %+v\nWant = %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mocks so that we can call Runtime.Core.Core().V1() without a functioning apiserver
|
|
//
|
|
|
|
// explicit interface check for core mock
|
|
var _ core.Interface = &coreMock{}
|
|
|
|
type coreMock struct {
|
|
v1 *v1Mock
|
|
}
|
|
|
|
func newCoreMock(c *gomock.Controller) *coreMock {
|
|
return &coreMock{
|
|
v1: newV1Mock(c),
|
|
}
|
|
}
|
|
|
|
func (m *coreMock) V1() corev1.Interface {
|
|
return m.v1
|
|
}
|
|
|
|
// explicit interface check for core v1 mock
|
|
var _ corev1.Interface = &v1Mock{}
|
|
|
|
type v1Mock struct {
|
|
configMap *fake.MockControllerInterface[*v1.ConfigMap, *v1.ConfigMapList]
|
|
endpoints *fake.MockControllerInterface[*v1.Endpoints, *v1.EndpointsList]
|
|
event *fake.MockControllerInterface[*v1.Event, *v1.EventList]
|
|
namespace *fake.MockNonNamespacedControllerInterface[*v1.Namespace, *v1.NamespaceList]
|
|
node *fake.MockNonNamespacedControllerInterface[*v1.Node, *v1.NodeList]
|
|
persistentVolume *fake.MockNonNamespacedControllerInterface[*v1.PersistentVolume, *v1.PersistentVolumeList]
|
|
persistentVolumeClaim *fake.MockControllerInterface[*v1.PersistentVolumeClaim, *v1.PersistentVolumeClaimList]
|
|
pod *fake.MockControllerInterface[*v1.Pod, *v1.PodList]
|
|
secret *fake.MockControllerInterface[*v1.Secret, *v1.SecretList]
|
|
service *fake.MockControllerInterface[*v1.Service, *v1.ServiceList]
|
|
serviceAccount *fake.MockControllerInterface[*v1.ServiceAccount, *v1.ServiceAccountList]
|
|
}
|
|
|
|
func newV1Mock(c *gomock.Controller) *v1Mock {
|
|
return &v1Mock{
|
|
configMap: fake.NewMockControllerInterface[*v1.ConfigMap, *v1.ConfigMapList](c),
|
|
endpoints: fake.NewMockControllerInterface[*v1.Endpoints, *v1.EndpointsList](c),
|
|
event: fake.NewMockControllerInterface[*v1.Event, *v1.EventList](c),
|
|
namespace: fake.NewMockNonNamespacedControllerInterface[*v1.Namespace, *v1.NamespaceList](c),
|
|
node: fake.NewMockNonNamespacedControllerInterface[*v1.Node, *v1.NodeList](c),
|
|
persistentVolume: fake.NewMockNonNamespacedControllerInterface[*v1.PersistentVolume, *v1.PersistentVolumeList](c),
|
|
persistentVolumeClaim: fake.NewMockControllerInterface[*v1.PersistentVolumeClaim, *v1.PersistentVolumeClaimList](c),
|
|
pod: fake.NewMockControllerInterface[*v1.Pod, *v1.PodList](c),
|
|
secret: fake.NewMockControllerInterface[*v1.Secret, *v1.SecretList](c),
|
|
service: fake.NewMockControllerInterface[*v1.Service, *v1.ServiceList](c),
|
|
serviceAccount: fake.NewMockControllerInterface[*v1.ServiceAccount, *v1.ServiceAccountList](c),
|
|
}
|
|
}
|
|
|
|
func (m *v1Mock) ConfigMap() corev1.ConfigMapController {
|
|
return m.configMap
|
|
}
|
|
|
|
func (m *v1Mock) Endpoints() corev1.EndpointsController {
|
|
return m.endpoints
|
|
}
|
|
|
|
func (m *v1Mock) Event() corev1.EventController {
|
|
return m.event
|
|
}
|
|
|
|
func (m *v1Mock) Namespace() corev1.NamespaceController {
|
|
return m.namespace
|
|
}
|
|
|
|
func (m *v1Mock) Node() corev1.NodeController {
|
|
return m.node
|
|
}
|
|
|
|
func (m *v1Mock) PersistentVolume() corev1.PersistentVolumeController {
|
|
return m.persistentVolume
|
|
}
|
|
|
|
func (m *v1Mock) PersistentVolumeClaim() corev1.PersistentVolumeClaimController {
|
|
return m.persistentVolumeClaim
|
|
}
|
|
|
|
func (m *v1Mock) Pod() corev1.PodController {
|
|
return m.pod
|
|
}
|
|
|
|
func (m *v1Mock) Secret() corev1.SecretController {
|
|
return m.secret
|
|
}
|
|
|
|
func (m *v1Mock) Service() corev1.ServiceController {
|
|
return m.service
|
|
}
|
|
|
|
func (m *v1Mock) ServiceAccount() corev1.ServiceAccountController {
|
|
return m.serviceAccount
|
|
}
|
|
|
|
func errorNotFound(gv, name string) error {
|
|
return apierrors.NewNotFound(schema.ParseGroupResource(gv), name)
|
|
}
|
|
|
|
//
|
|
// ListObjects response body template
|
|
//
|
|
|
|
var listObjectsV2ResponseTemplate = `
|
|
{{- /* */ -}}
|
|
{{ with $b := . -}}
|
|
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<Name>{{$b.Name}}</Name>
|
|
{{ if $b.Prefix }}<Prefix>{{$b.Prefix}}</Prefix>{{ else }}<Prefix/>{{ end }}
|
|
<KeyCount>{{ len $b.Objects }}</KeyCount>
|
|
<MaxKeys>1000</MaxKeys>
|
|
<Delimiter/>
|
|
<IsTruncated>false</IsTruncated>
|
|
{{- range $o := $b.Objects }}
|
|
<Contents>
|
|
<Key>{{ $o.Key }}</Key>
|
|
<LastModified>{{ $o.LastModified }}</LastModified>
|
|
<ETag>{{ printf "%q" $o.ETag }}</ETag>
|
|
<Size>{{ $o.Size }}</Size>
|
|
<Owner>
|
|
<ID>0</ID>
|
|
<DisplayName>test</DisplayName>
|
|
</Owner>
|
|
<StorageClass>STANDARD</StorageClass>
|
|
</Contents>
|
|
{{- end }}
|
|
<EncodingType>url</EncodingType>
|
|
</ListBucketResult>
|
|
{{- end }}
|
|
`
|
|
|
|
func s3Router(t *testing.T) http.Handler {
|
|
var listResponse = template.Must(template.New("listObjectsV2").Parse(listObjectsV2ResponseTemplate))
|
|
|
|
type object struct {
|
|
Key string
|
|
LastModified string
|
|
ETag string
|
|
Size int
|
|
}
|
|
|
|
type bucket struct {
|
|
Name string
|
|
Prefix string
|
|
Objects []object
|
|
}
|
|
|
|
snapshotId := 0
|
|
objects := []object{}
|
|
timestamp := time.Now().Format(time.RFC3339)
|
|
for _, prefix := range []string{"", "testfolder", "testfolder/netsted", "otherfolder"} {
|
|
for idx := range []int{0, 1, 2} {
|
|
snapshotId++
|
|
objects = append(objects, object{
|
|
Key: path.Join(prefix, fmt.Sprintf("snapshot-%02d", snapshotId)),
|
|
LastModified: timestamp,
|
|
ETag: "0000",
|
|
Size: 100,
|
|
})
|
|
if idx != 0 {
|
|
objects = append(objects, object{
|
|
Key: path.Join(prefix, fmt.Sprintf(".metadata/snapshot-%02d", snapshotId)),
|
|
LastModified: timestamp,
|
|
ETag: "0000",
|
|
Size: 10,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// badbucket returns 404 for all requests
|
|
// authbucket returns 200 for HeadBucket, 403 for all others
|
|
// others return 200 for objects with name prefix snapshot, 404 for all others
|
|
router := mux.NewRouter().SkipClean(true)
|
|
// HeadBucket
|
|
router.Path("/{bucket}/").Methods(http.MethodHead).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
}
|
|
})
|
|
// ListObjectsV2
|
|
router.Path("/{bucket}/").Methods(http.MethodGet).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
prefix := r.URL.Query().Get("prefix")
|
|
filtered := []object{}
|
|
for _, object := range objects {
|
|
if strings.HasPrefix(object.Key, prefix) {
|
|
filtered = append(filtered, object)
|
|
}
|
|
}
|
|
if err := listResponse.Execute(rw, bucket{Name: vars["bucket"], Prefix: prefix, Objects: filtered}); err != nil {
|
|
t.Errorf("Failed to generate ListObjectsV2 response, error = %v", err)
|
|
rw.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
}
|
|
})
|
|
// HeadObject - snapshot
|
|
router.Path("/{bucket}/{prefix:.*}snapshot-{snapshot}").Methods(http.MethodHead).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
rw.Header().Add("last-modified", time.Now().In(gmt).Format(time.RFC1123))
|
|
}
|
|
})
|
|
// GetObject - snapshot
|
|
router.Path("/{bucket}/{prefix:.*}snapshot-{snapshot}").Methods(http.MethodGet).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
rw.Header().Add("last-modified", time.Now().In(gmt).Format(time.RFC1123))
|
|
rw.Write([]byte("test snapshot file\n"))
|
|
}
|
|
})
|
|
// PutObject/DeleteObject - snapshot
|
|
router.Path("/{bucket}/{prefix:.*}snapshot-{snapshot}").Methods(http.MethodPut, http.MethodDelete).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
if r.Method == http.MethodDelete {
|
|
rw.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
})
|
|
// HeadObject - snapshot metadata
|
|
router.Path("/{bucket}/{prefix:.*}.metadata/snapshot-{snapshot}").Methods(http.MethodHead).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
rw.Header().Add("last-modified", time.Now().In(gmt).Format(time.RFC1123))
|
|
}
|
|
})
|
|
// GetObject - snapshot metadata
|
|
router.Path("/{bucket}/{prefix:.*}.metadata/snapshot-{snapshot}").Methods(http.MethodGet).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
rw.Header().Add("last-modified", time.Now().In(gmt).Format(time.RFC1123))
|
|
rw.Write([]byte("test snapshot metadata\n"))
|
|
}
|
|
})
|
|
// PutObject/DeleteObject - snapshot metadata
|
|
router.Path("/{bucket}/{prefix:.*}.metadata/snapshot-{snapshot}").Methods(http.MethodPut, http.MethodDelete).HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
switch vars["bucket"] {
|
|
case "badbucket":
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
case "authbucket":
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
default:
|
|
if r.Method == http.MethodDelete {
|
|
rw.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
})
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
scheme := "http"
|
|
if r.TLS != nil {
|
|
scheme = "https"
|
|
}
|
|
logrus.Infof("%s %s://%s %s", r.Method, scheme, r.Host, r.URL)
|
|
router.ServeHTTP(rw, r)
|
|
})
|
|
}
|