diff --git a/transport/stunnel/server_test.go b/transport/stunnel/server_test.go new file mode 100644 index 0000000..fd40ef1 --- /dev/null +++ b/transport/stunnel/server_test.go @@ -0,0 +1,216 @@ +package stunnel + +import ( + "context" + "fmt" + "github.com/backube/pvc-transfer/endpoint" + "github.com/backube/pvc-transfer/transport" + logrtesting "github.com/go-logr/logr/testing" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "strings" + "testing" +) + +func fakeClientWithObjects(objs ...client.Object) client.WithWatch { + scheme := runtime.NewScheme() + AddToScheme(scheme) + return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).Build() +} + +func testOwnerReferences() []metav1.OwnerReference { + return []metav1.OwnerReference{metav1.OwnerReference{ + APIVersion: "api.foo", + Kind: "Test", + Name: "bar", + UID: "123", + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }} +} + +type fakeEndpoint struct { + nn types.NamespacedName + hostname string +} + +func (f fakeEndpoint) NamespacedName() types.NamespacedName { + return f.nn +} + +func (f fakeEndpoint) Hostname() string { + return f.hostname +} + +func (f fakeEndpoint) BackendPort() int32 { + return 1234 +} + +func (f fakeEndpoint) IngressPort() int32 { + return 1234 +} + +func (f fakeEndpoint) IsHealthy(ctx context.Context, c client.Client) (bool, error) { + return true, nil +} + +func (f fakeEndpoint) MarkForCleanup(ctx context.Context, c client.Client, key, value string) error { + return nil +} + +func newFakeEndpoint() endpoint.Endpoint { + return fakeEndpoint{ + nn: types.NamespacedName{Name: "foo", Namespace: "bar"}, + hostname: "foo.bar", + } +} + +func TestNew(t *testing.T) { + tests := []struct { + name string + namespacedName types.NamespacedName + endpoint endpoint.Endpoint + labels map[string]string + ownerReferences []metav1.OwnerReference + wantErr bool + objects []client.Object + }{ + { + name: "test with no stunnel server objects", + namespacedName: types.NamespacedName{Namespace: "bar", Name: "foo"}, + endpoint: newFakeEndpoint(), + labels: map[string]string{"test": "me"}, + ownerReferences: testOwnerReferences(), + wantErr: false, + objects: []client.Object{}, + }, + { + name: "test stunnel server, valid secret exists but no configmap", + namespacedName: types.NamespacedName{Namespace: "bar", Name: "foo"}, + endpoint: newFakeEndpoint(), + labels: map[string]string{"test": "me"}, + ownerReferences: testOwnerReferences(), + wantErr: false, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-server-stunnel-secret", + Namespace: "bar", + }, + Data: map[string][]byte{"tls.key": []byte(`key`), "tls.crt": []byte(`crt`)}, + }, + }, + }, + { + name: "test stunnel server, invalid secret exists but no configmap", + namespacedName: types.NamespacedName{Namespace: "bar", Name: "foo"}, + endpoint: newFakeEndpoint(), + labels: map[string]string{"test": "me"}, + ownerReferences: testOwnerReferences(), + wantErr: false, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-server-stunnel-secret", + Namespace: "bar", + }, + Data: map[string][]byte{"tls.crt": []byte(`crt`)}, + }, + }, + }, + { + name: "test stunnel server, valid configmap but no secret", + namespacedName: types.NamespacedName{Namespace: "bar", Name: "foo"}, + endpoint: newFakeEndpoint(), + labels: map[string]string{"test": "me"}, + ownerReferences: testOwnerReferences(), + wantErr: false, + objects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-server-stunnel-config", + Namespace: "bar", + }, + Data: map[string]string{"stunnel.conf": "foreground = yes"}, + }, + }, + }, + { + name: "test stunnel server, invalid configmap but no secret", + namespacedName: types.NamespacedName{Namespace: "bar", Name: "foo"}, + endpoint: newFakeEndpoint(), + labels: map[string]string{"test": "me"}, + ownerReferences: testOwnerReferences(), + wantErr: false, + objects: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-server-stunnel-config", + Namespace: "bar", + }, + Data: map[string]string{"stunnel.conf": "foreground = no"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient := fakeClientWithObjects(tt.objects...) + ctx := context.WithValue(context.Background(), "test", tt.name) + fakeLogger := logrtesting.TestLogger{t} + stunnelServer, err := New(ctx, fakeClient, fakeLogger, tt.namespacedName, tt.endpoint, &transport.Options{Labels: tt.labels, Owners: tt.ownerReferences}) + if (err != nil) != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + return + } + cm := &corev1.ConfigMap{} + err = fakeClient.Get(context.Background(), types.NamespacedName{ + Namespace: "bar", + Name: "foo-server-" + stunnelConfig, + }, cm) + if err != nil { + panic(fmt.Errorf("%#v should not be getting error from fake client", err)) + } + + configdata, ok := cm.Data["stunnel.conf"] + if !ok { + t.Error("unable to find stunnel config data in configmap") + } + if !strings.Contains(configdata, "foreground = yes") { + t.Error("configmap data does not contain the right data") + } + + secret := &corev1.Secret{} + err = fakeClient.Get(context.Background(), types.NamespacedName{ + Namespace: "bar", + Name: "foo-server-" + stunnelSecret, + }, secret) + if err != nil { + panic(fmt.Errorf("%#v should not be getting error from fake client", err)) + } + + _, ok = secret.Data["tls.key"] + if !ok { + t.Error("unable to find tls.key in stunnel secret") + } + + _, ok = secret.Data["tls.crt"] + if !ok { + t.Error("unable to find tls.crt in stunnel secret") + } + + if len(stunnelServer.Volumes()) == 0 { + t.Error("stunnel server volumes not set properly") + } + + if len(stunnelServer.Containers()) == 0 { + t.Error("stunnel server containers not set properly") + } + }) + } +}