// This file is part of MinIO Console Server // Copyright (c) 2021 MinIO, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package operatorapi import ( "bytes" "context" "encoding/json" "errors" "io" "io/ioutil" "net/http" "reflect" "testing" "time" "github.com/stretchr/testify/assert" xhttp "github.com/minio/console/pkg/http" "github.com/minio/console/operatorapi/operations/operator_api" "github.com/go-openapi/swag" "github.com/minio/console/models" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/fake" ) var ( opClientTenantDeleteMock func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error opClientTenantGetMock func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) opClientTenantPatchMock func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) opClientTenantUpdateMock func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) ) var ( opClientTenantListMock func(ctx context.Context, namespace string, opts metav1.ListOptions) (*miniov2.TenantList, error) httpClientGetMock func(url string) (resp *http.Response, err error) httpClientPostMock func(url, contentType string, body io.Reader) (resp *http.Response, err error) httpClientDoMock func(req *http.Request) (*http.Response, error) k8sclientGetSecretMock func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) k8sclientGetServiceMock func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) ) // mock function of TenantDelete() func (ac opClientMock) TenantDelete(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return opClientTenantDeleteMock(ctx, namespace, tenantName, options) } // mock function of TenantGet() func (ac opClientMock) TenantGet(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return opClientTenantGetMock(ctx, namespace, tenantName, options) } // mock function of TenantPatch() func (ac opClientMock) TenantPatch(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return opClientTenantPatchMock(ctx, namespace, tenantName, pt, data, options) } // mock function of TenantUpdate() func (ac opClientMock) TenantUpdate(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) { return opClientTenantUpdateMock(ctx, tenant, opts) } // mock function of TenantList() func (ac opClientMock) TenantList(ctx context.Context, namespace string, opts metav1.ListOptions) (*miniov2.TenantList, error) { return opClientTenantListMock(ctx, namespace, opts) } // mock function of get() func (h httpClientMock) Get(url string) (resp *http.Response, err error) { return httpClientGetMock(url) } // mock function of post() func (h httpClientMock) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) { return httpClientPostMock(url, contentType, body) } // mock function of Do() func (h httpClientMock) Do(req *http.Request) (*http.Response, error) { return httpClientDoMock(req) } func (c k8sClientMock) getSecret(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return k8sclientGetSecretMock(ctx, namespace, secretName, opts) } func (c k8sClientMock) getService(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { return k8sclientGetServiceMock(ctx, namespace, serviceName, opts) } func Test_TenantInfoTenantAdminClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() kClient := k8sClientMock{} type args struct { ctx context.Context client K8sClientI tenant miniov2.Tenant serviceURL string } tests := []struct { name string args args wantErr bool mockGetSecret func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) mockGetService func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) }{ { name: "Return Tenant Admin, no errors using legacy credentials", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, Spec: miniov2.TenantSpec{ CredsSecret: &corev1.LocalObjectReference{ Name: "secret-name", }, }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { vals := make(map[string][]byte) vals["secretkey"] = []byte("secret") vals["accesskey"] = []byte("access") sec := &corev1.Secret{ Data: vals, } return sec, nil }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { serv := &corev1.Service{ Spec: corev1.ServiceSpec{ ClusterIP: "10.1.1.2", }, } return serv, nil }, wantErr: false, }, { name: "Return Tenant Admin, no errors using credentials from configuration file", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, Spec: miniov2.TenantSpec{ CredsSecret: &corev1.LocalObjectReference{ Name: "secret-name", }, Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration", }, }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { vals := make(map[string][]byte) vals["config.env"] = []byte(` export MINIO_ROOT_USER=minio export MINIO_ROOT_PASSWORD=minio123 `) sec := &corev1.Secret{ Data: vals, } return sec, nil }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { serv := &corev1.Service{ Spec: corev1.ServiceSpec{ ClusterIP: "10.1.1.2", }, } return serv, nil }, wantErr: false, }, { name: "Return Tenant Admin, no errors using credentials from configuration file 2", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, Spec: miniov2.TenantSpec{ CredsSecret: &corev1.LocalObjectReference{ Name: "secret-name", }, Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration", }, }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { vals := make(map[string][]byte) vals["config.env"] = []byte(` export MINIO_ACCESS_KEY=minio export MINIO_SECRET_KEY=minio123 `) sec := &corev1.Secret{ Data: vals, } return sec, nil }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { serv := &corev1.Service{ Spec: corev1.ServiceSpec{ ClusterIP: "10.1.1.2", }, } return serv, nil }, wantErr: false, }, { name: "Access key not stored on secrets", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { vals := make(map[string][]byte) vals["secretkey"] = []byte("secret") sec := &corev1.Secret{ Data: vals, } return sec, nil }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { serv := &corev1.Service{ Spec: corev1.ServiceSpec{ ClusterIP: "10.1.1.2", }, } return serv, nil }, wantErr: true, }, { name: "Secret key not stored on secrets", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { vals := make(map[string][]byte) vals["accesskey"] = []byte("access") sec := &corev1.Secret{ Data: vals, } return sec, nil }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { serv := &corev1.Service{ Spec: corev1.ServiceSpec{ ClusterIP: "10.1.1.2", }, } return serv, nil }, wantErr: true, }, { name: "Handle error on getService", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { vals := make(map[string][]byte) vals["accesskey"] = []byte("access") vals["secretkey"] = []byte("secret") sec := &corev1.Secret{ Data: vals, } return sec, nil }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { return nil, errors.New("error") }, wantErr: true, }, { name: "Handle error on getSecret", args: args{ ctx: ctx, client: kClient, tenant: miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "tenant-1", }, }, serviceURL: "http://service-1.default.svc.cluster.local:80", }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return nil, errors.New("error") }, mockGetService: func(ctx context.Context, namespace, serviceName string, opts metav1.GetOptions) (*corev1.Service, error) { serv := &corev1.Service{ Spec: corev1.ServiceSpec{ ClusterIP: "10.1.1.2", }, } return serv, nil }, wantErr: true, }, } for _, tt := range tests { k8sclientGetSecretMock = tt.mockGetSecret k8sclientGetServiceMock = tt.mockGetService t.Run(tt.name, func(t *testing.T) { got, err := getTenantAdminClient(tt.args.ctx, tt.args.client, &tt.args.tenant, tt.args.serviceURL) if err != nil { if tt.wantErr { return } t.Errorf("getTenantAdminClient() error = %v, wantErr %v", err, tt.wantErr) } if got == nil { t.Errorf("getTenantAdminClient() expected type: *madmin.AdminClient, got: nil") } }) } } func NoTestTenantInfo(t *testing.T) { testTimeStamp := metav1.Now() type args struct { minioTenant *miniov2.Tenant } tests := []struct { name string args args want *models.Tenant }{ { name: "Get tenant Info", args: args{ minioTenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ CreationTimestamp: testTimeStamp, Name: "tenant1", Namespace: "minio-ns", }, Spec: miniov2.TenantSpec{ Pools: []miniov2.Pool{ { Name: "pool1", Servers: int32(2), VolumesPerServer: 4, VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ Spec: corev1.PersistentVolumeClaimSpec{ Resources: corev1.ResourceRequirements{ Requests: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceStorage: resource.MustParse("1Mi"), }, }, StorageClassName: swag.String("standard"), }, }, }, }, Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", }, Status: miniov2.TenantStatus{ CurrentState: "ready", }, }, }, want: &models.Tenant{ CreationDate: testTimeStamp.Format(time.RFC3339), Name: "tenant1", TotalSize: int64(8388608), CurrentState: "ready", Pools: []*models.Pool{ { Name: "pool1", Servers: swag.Int64(int64(2)), VolumesPerServer: swag.Int32(4), VolumeConfiguration: &models.PoolVolumeConfiguration{ StorageClassName: "standard", Size: swag.Int64(1024 * 1024), }, }, }, Namespace: "minio-ns", Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", EnablePrometheus: false, }, }, { // Description if DeletionTimeStamp is present, value should be returned as string // If Prometheus annotations are present, EnablePrometheus should be returned as true // All three annotations should be defined to consider Prometheus enabled name: "Get tenant Info w DeletionTimeStamp and Prometheus", args: args{ minioTenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ CreationTimestamp: testTimeStamp, Name: "tenant1", Namespace: "minio-ns", DeletionTimestamp: &testTimeStamp, Annotations: map[string]string{ prometheusPath: "some/path", prometheusPort: "other/path", prometheusScrape: "other/path", }, }, Spec: miniov2.TenantSpec{ Pools: []miniov2.Pool{ { Name: "pool1", Servers: int32(2), VolumesPerServer: 4, VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ Spec: corev1.PersistentVolumeClaimSpec{ Resources: corev1.ResourceRequirements{ Requests: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceStorage: resource.MustParse("1Mi"), }, }, StorageClassName: swag.String("standard"), }, }, }, }, Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", }, Status: miniov2.TenantStatus{ CurrentState: "ready", }, }, }, want: &models.Tenant{ CreationDate: testTimeStamp.Format(time.RFC3339), DeletionDate: testTimeStamp.Format(time.RFC3339), Name: "tenant1", TotalSize: int64(8388608), CurrentState: "ready", Pools: []*models.Pool{ { Name: "pool1", Servers: swag.Int64(int64(2)), VolumesPerServer: swag.Int32(4), VolumeConfiguration: &models.PoolVolumeConfiguration{ StorageClassName: "standard", Size: swag.Int64(1024 * 1024), }, }, }, Namespace: "minio-ns", Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", EnablePrometheus: true, }, }, { // If Prometheus annotations are present, EnablePrometheus should be returned as true // All three annotations should be defined to consider Prometheus enabled name: "Get tenant Info, not all Prometheus annotations", args: args{ minioTenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ CreationTimestamp: testTimeStamp, Name: "tenant1", Namespace: "minio-ns", Annotations: map[string]string{ prometheusPath: "some/path", prometheusScrape: "other/path", }, }, Spec: miniov2.TenantSpec{ Pools: []miniov2.Pool{}, Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", }, Status: miniov2.TenantStatus{ CurrentState: "ready", }, }, }, want: &models.Tenant{ CreationDate: testTimeStamp.Format(time.RFC3339), Name: "tenant1", CurrentState: "ready", Namespace: "minio-ns", Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", EnablePrometheus: false, }, }, { // If console image is set, it should be returned on tenant info name: "Get tenant Info, Console image set", args: args{ minioTenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ CreationTimestamp: testTimeStamp, Name: "tenant1", Namespace: "minio-ns", Annotations: map[string]string{ prometheusPath: "some/path", prometheusScrape: "other/path", }, }, Spec: miniov2.TenantSpec{ Pools: []miniov2.Pool{}, Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", }, Status: miniov2.TenantStatus{ CurrentState: "ready", }, }, }, want: &models.Tenant{ CreationDate: testTimeStamp.Format(time.RFC3339), Name: "tenant1", CurrentState: "ready", Namespace: "minio-ns", Image: "minio/minio:RELEASE.2020-06-14T18-32-17Z", EnablePrometheus: false, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := getTenantInfo(tt.args.minioTenant) if !reflect.DeepEqual(got, tt.want) { ji, _ := json.Marshal(got) vi, _ := json.Marshal(tt.want) t.Errorf("got %s want %s", ji, vi) } }) } } func Test_deleteTenantAction(t *testing.T) { opClient := opClientMock{} type args struct { ctx context.Context operatorClient OperatorClientI tenant *miniov2.Tenant deletePvcs bool objs []runtime.Object mockTenantDelete func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error } tests := []struct { name string args args wantErr bool }{ { name: "Success", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: "minio-tenant", }, }, deletePvcs: false, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return nil }, }, wantErr: false, }, { name: "Error", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: "minio-tenant", }, }, deletePvcs: false, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return errors.New("something happened") }, }, wantErr: true, }, { // Delete only PVCs of the defined tenant on the specific namespace name: "Delete PVCs on Tenant Deletion", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant1", Namespace: "minio-tenant", }, }, deletePvcs: true, objs: []runtime.Object{ &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "PVC1", Namespace: "minio-tenant", Labels: map[string]string{ miniov2.TenantLabel: "tenant1", miniov2.PoolLabel: "pool-1", }, }, }, }, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return nil }, }, wantErr: false, }, { // Do not delete underlying pvcs name: "Don't Delete PVCs on Tenant Deletion", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant1", Namespace: "minio-tenant", }, }, deletePvcs: false, objs: []runtime.Object{ &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "PVC1", Namespace: "minio-tenant", Labels: map[string]string{ miniov2.TenantLabel: "tenant1", miniov2.PoolLabel: "pool-1", }, }, }, }, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return nil }, }, wantErr: false, }, { // If error is different than NotFound, PVC deletion should not continue name: "Don't delete pvcs if error Deleting Tenant, return", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant1", Namespace: "minio-tenant", }, }, deletePvcs: true, objs: []runtime.Object{ &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "PVC1", Namespace: "minio-tenant", Labels: map[string]string{ miniov2.TenantLabel: "tenant1", miniov2.PoolLabel: "pool-1", }, }, }, }, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return errors.New("error returned") }, }, wantErr: true, }, { // If error is NotFound while trying to Delete Tenant, PVC deletion should continue name: "Delete pvcs if tenant not found", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant1", Namespace: "minio-tenant", }, }, deletePvcs: true, objs: []runtime.Object{ &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "PVC1", Namespace: "minio-tenant", Labels: map[string]string{ miniov2.TenantLabel: "tenant1", miniov2.PoolLabel: "pool-1", }, }, }, }, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return k8sErrors.NewNotFound(schema.GroupResource{}, "tenant1") }, }, wantErr: false, }, { // If error is NotFound while trying to Delete Tenant and pvcdeletion=false, // error should be returned name: "Don't delete pvcs and return error if tenant not found", args: args{ ctx: context.Background(), operatorClient: opClient, tenant: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant1", Namespace: "minio-tenant", }, }, deletePvcs: false, objs: []runtime.Object{ &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "PVC1", Namespace: "minio-tenant", Labels: map[string]string{ miniov2.TenantLabel: "tenant1", miniov2.PoolLabel: "pool-1", }, }, }, }, mockTenantDelete: func(ctx context.Context, namespace string, tenantName string, options metav1.DeleteOptions) error { return k8sErrors.NewNotFound(schema.GroupResource{}, "tenant1") }, }, wantErr: true, }, } for _, tt := range tests { opClientTenantDeleteMock = tt.args.mockTenantDelete kubeClient := fake.NewSimpleClientset(tt.args.objs...) t.Run(tt.name, func(t *testing.T) { if err := deleteTenantAction(tt.args.ctx, tt.args.operatorClient, kubeClient.CoreV1(), tt.args.tenant, tt.args.deletePvcs); (err != nil) != tt.wantErr { t.Errorf("deleteTenantAction() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_TenantAddPool(t *testing.T) { opClient := opClientMock{} type args struct { ctx context.Context operatorClient OperatorClientI nameSpace string mockTenantPatch func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) params operator_api.TenantAddPoolParams } tests := []struct { name string args args wantErr bool }{ { name: "Add pool, no errors", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, params: operator_api.TenantAddPoolParams{ Body: &models.Pool{ Name: "pool-1", Servers: swag.Int64(int64(4)), VolumeConfiguration: &models.PoolVolumeConfiguration{ Size: swag.Int64(2147483648), StorageClassName: "standard", }, VolumesPerServer: swag.Int32(4), }, }, }, wantErr: false, }, { name: "Add pool, error size", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, params: operator_api.TenantAddPoolParams{ Body: &models.Pool{ Name: "pool-1", Servers: swag.Int64(int64(4)), VolumeConfiguration: &models.PoolVolumeConfiguration{ Size: swag.Int64(0), StorageClassName: "standard", }, VolumesPerServer: swag.Int32(4), }, }, }, wantErr: true, }, { name: "Add pool, error servers negative", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, params: operator_api.TenantAddPoolParams{ Body: &models.Pool{ Name: "pool-1", Servers: swag.Int64(int64(-1)), VolumeConfiguration: &models.PoolVolumeConfiguration{ Size: swag.Int64(2147483648), StorageClassName: "standard", }, VolumesPerServer: swag.Int32(4), }, }, }, wantErr: true, }, { name: "Add pool, error volumes per server negative", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, params: operator_api.TenantAddPoolParams{ Body: &models.Pool{ Name: "pool-1", Servers: swag.Int64(int64(4)), VolumeConfiguration: &models.PoolVolumeConfiguration{ Size: swag.Int64(2147483648), StorageClassName: "standard", }, VolumesPerServer: swag.Int32(-1), }, }, }, wantErr: true, }, { name: "Error on patch, handle error", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return nil, errors.New("errors") }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, params: operator_api.TenantAddPoolParams{ Body: &models.Pool{ Name: "pool-1", Servers: swag.Int64(int64(4)), }, }, }, wantErr: true, }, { name: "Error on get, handle error", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return nil, errors.New("errors") }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return nil, errors.New("errors") }, params: operator_api.TenantAddPoolParams{ Body: &models.Pool{ Name: "pool-1", Servers: swag.Int64(int64(4)), }, }, }, wantErr: true, }, } for _, tt := range tests { opClientTenantGetMock = tt.args.mockTenantGet opClientTenantPatchMock = tt.args.mockTenantPatch t.Run(tt.name, func(t *testing.T) { if err := addTenantPool(tt.args.ctx, tt.args.operatorClient, tt.args.params); (err != nil) != tt.wantErr { t.Errorf("addTenantPool() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_UpdateTenantAction(t *testing.T) { opClient := opClientMock{} httpClientM := httpClientMock{} type args struct { ctx context.Context operatorClient OperatorClientI httpCl xhttp.ClientI nameSpace string tenantName string mockTenantPatch func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) mockHTTPClientGet func(url string) (resp *http.Response, err error) params operator_api.UpdateTenantParams } tests := []struct { name string args args objs []runtime.Object wantErr bool }{ { name: "Update minio version no errors", args: args{ ctx: context.Background(), operatorClient: opClient, httpCl: httpClientM, nameSpace: "default", tenantName: "minio-tenant", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockHTTPClientGet: func(url string) (resp *http.Response, err error) { return &http.Response{}, nil }, params: operator_api.UpdateTenantParams{ Body: &models.UpdateTenantRequest{ Image: "minio/minio:RELEASE.2020-06-03T22-13-49Z", }, }, }, wantErr: false, }, { name: "Error occurs getting minioTenant", args: args{ ctx: context.Background(), operatorClient: opClient, httpCl: httpClientM, nameSpace: "default", tenantName: "minio-tenant", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return nil, errors.New("error-get") }, mockHTTPClientGet: func(url string) (resp *http.Response, err error) { return &http.Response{}, nil }, params: operator_api.UpdateTenantParams{ Body: &models.UpdateTenantRequest{ Image: "minio/minio:RELEASE.2020-06-03T22-13-49Z", }, }, }, wantErr: true, }, { name: "Error occurs patching minioTenant", args: args{ ctx: context.Background(), operatorClient: opClient, httpCl: httpClientM, nameSpace: "default", tenantName: "minio-tenant", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return nil, errors.New("error-get") }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockHTTPClientGet: func(url string) (resp *http.Response, err error) { return &http.Response{}, nil }, params: operator_api.UpdateTenantParams{ Tenant: "minio-tenant", Body: &models.UpdateTenantRequest{ Image: "minio/minio:RELEASE.2020-06-03T22-13-49Z", }, }, }, wantErr: true, }, { name: "Empty image should patch correctly with latest image", args: args{ ctx: context.Background(), operatorClient: opClient, httpCl: httpClientM, nameSpace: "default", tenantName: "minio-tenant", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockHTTPClientGet: func(url string) (resp *http.Response, err error) { r := ioutil.NopCloser(bytes.NewReader([]byte(`./minio.RELEASE.2020-06-18T02-23-35Z"`))) return &http.Response{ Body: r, }, nil }, params: operator_api.UpdateTenantParams{ Tenant: "minio-tenant", Body: &models.UpdateTenantRequest{ Image: "", }, }, }, wantErr: false, }, { name: "Empty image input Error retrieving latest image, nothing happens", args: args{ ctx: context.Background(), operatorClient: opClient, httpCl: httpClientM, nameSpace: "default", tenantName: "minio-tenant", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockHTTPClientGet: func(url string) (resp *http.Response, err error) { return nil, errors.New("error") }, params: operator_api.UpdateTenantParams{ Tenant: "minio-tenant", Body: &models.UpdateTenantRequest{ Image: "", }, }, }, wantErr: false, }, { name: "Update minio image pull secrets no errors", args: args{ ctx: context.Background(), operatorClient: opClient, httpCl: httpClientM, nameSpace: "default", tenantName: "minio-tenant", mockTenantPatch: func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockHTTPClientGet: func(url string) (resp *http.Response, err error) { return nil, errors.New("use default minio") }, params: operator_api.UpdateTenantParams{ Body: &models.UpdateTenantRequest{ ImagePullSecret: "minio-regcred", }, }, }, wantErr: false, }, } for _, tt := range tests { opClientTenantGetMock = tt.args.mockTenantGet opClientTenantPatchMock = tt.args.mockTenantPatch httpClientGetMock = tt.args.mockHTTPClientGet cnsClient := fake.NewSimpleClientset(tt.objs...) t.Run(tt.name, func(t *testing.T) { if err := updateTenantAction(tt.args.ctx, tt.args.operatorClient, cnsClient.CoreV1(), tt.args.httpCl, tt.args.nameSpace, tt.args.params); (err != nil) != tt.wantErr { t.Errorf("updateTenantAction() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_UpdateDomainsResponse(t *testing.T) { opClient := opClientMock{} type args struct { ctx context.Context operatorClient OperatorClientI nameSpace string tenantName string mockTenantUpdate func(ctx context.Context, tenant *miniov2.Tenant, options metav1.UpdateOptions) (*miniov2.Tenant, error) mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) domains *models.DomainsConfiguration } tests := []struct { name string args args objs []runtime.Object wantErr bool }{ { name: "Update console & minio domains OK", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", tenantName: "minio-tenant", mockTenantUpdate: func(ctx context.Context, tenant *miniov2.Tenant, options metav1.UpdateOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, domains: &models.DomainsConfiguration{ Console: "http://console.min.io", Minio: []string{"http://domain1.min.io", "http://domain2.min.io", "http://domain3.min.io"}, }, }, wantErr: false, }, { name: "Error occurs getting minioTenant", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", tenantName: "minio-tenant", mockTenantUpdate: func(ctx context.Context, tenant *miniov2.Tenant, options metav1.UpdateOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return nil, errors.New("error-getting-tenant-info") }, domains: &models.DomainsConfiguration{ Console: "http://console.min.io", Minio: []string{"http://domain1.min.io", "http://domain2.min.io", "http://domain3.min.io"}, }, }, wantErr: true, }, { name: "Tenant already has domains", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", tenantName: "minio-tenant", mockTenantUpdate: func(ctx context.Context, tenant *miniov2.Tenant, options metav1.UpdateOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { domains := miniov2.TenantDomains{ Console: "http://onerandomdomain.min.io", Minio: []string{ "http://oneDomain.min.io", "http://twoDomains.min.io", }, } features := miniov2.Features{ Domains: &domains, } return &miniov2.Tenant{ Spec: miniov2.TenantSpec{Features: &features}, }, nil }, domains: &models.DomainsConfiguration{ Console: "http://console.min.io", Minio: []string{"http://domain1.min.io", "http://domain2.min.io", "http://domain3.min.io"}, }, }, wantErr: false, }, { name: "Tenant features only have BucketDNS", args: args{ ctx: context.Background(), operatorClient: opClient, nameSpace: "default", tenantName: "minio-tenant", mockTenantUpdate: func(ctx context.Context, tenant *miniov2.Tenant, options metav1.UpdateOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{}, nil }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { features := miniov2.Features{ BucketDNS: true, } return &miniov2.Tenant{ Spec: miniov2.TenantSpec{Features: &features}, }, nil }, domains: &models.DomainsConfiguration{ Console: "http://console.min.io", Minio: []string{"http://domain1.min.io", "http://domain2.min.io", "http://domain3.min.io"}, }, }, wantErr: false, }, } for _, tt := range tests { opClientTenantGetMock = tt.args.mockTenantGet opClientTenantUpdateMock = tt.args.mockTenantUpdate t.Run(tt.name, func(t *testing.T) { if err := updateTenantDomains(tt.args.ctx, tt.args.operatorClient, tt.args.nameSpace, tt.args.tenantName, tt.args.domains); (err != nil) != tt.wantErr { t.Errorf("updateTenantDomains() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_parseTenantCertificates(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() kClient := k8sClientMock{} type args struct { ctx context.Context clientSet K8sClientI namespace string secrets []*miniov2.LocalCertificateReference } tests := []struct { name string args args want []*models.CertificateInfo mockGetSecret func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) wantErr bool }{ { name: "empty secrets list", args: args{ ctx: ctx, clientSet: kClient, secrets: nil, }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return nil, nil }, want: nil, wantErr: false, }, { name: "error getting secret", args: args{ ctx: ctx, clientSet: kClient, secrets: []*miniov2.LocalCertificateReference{ { Name: "certificate-1", }, }, }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return nil, errors.New("error getting secret") }, want: nil, wantErr: true, }, { name: "error getting certificate because of missing public key", args: args{ ctx: ctx, clientSet: kClient, secrets: []*miniov2.LocalCertificateReference{ { Name: "certificate-1", Type: "Opaque", }, }, }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { certificateSecret := &corev1.Secret{ Data: map[string][]byte{ "eaeaeae": []byte(` -----BEGIN CERTIFICATE----- MIIBUDCCAQKgAwIBAgIRALdFZh8hLU348ho9wYzlZbAwBQYDK2VwMBIxEDAOBgNV BAoTB0FjbWUgQ28wHhcNMjIwODE4MjAxMzUzWhcNMjMwODE4MjAxMzUzWjASMRAw DgYDVQQKEwdBY21lIENvMCowBQYDK2VwAyEAct5c3dzzbNOTi+C62w7QHoSivEWD MYAheDXZWHC55tGjbTBrMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAKBggrBgEF BQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs0At8sTSLCjiM24AZhxFY a2CswjAUBgNVHREEDTALgglsb2NhbGhvc3QwBQYDK2VwA0EABan+d16CeN8UD+QF a8HBhPAiOpaZeEF6+EqTlq9VfL3eSVd7CLRI+/KtY7ptwomuTeYzuV73adKdE9N2 ZrJuAw== -----END CERTIFICATE----- `), }, Type: "Opaque", } return certificateSecret, nil }, want: nil, wantErr: true, }, { name: "return certificate from existing secret", args: args{ ctx: ctx, clientSet: kClient, secrets: []*miniov2.LocalCertificateReference{ { Name: "certificate-1", Type: "Opaque", }, }, }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { certificateSecret := &corev1.Secret{ Data: map[string][]byte{ "public.crt": []byte(` -----BEGIN CERTIFICATE----- MIIBUDCCAQKgAwIBAgIRALdFZh8hLU348ho9wYzlZbAwBQYDK2VwMBIxEDAOBgNV BAoTB0FjbWUgQ28wHhcNMjIwODE4MjAxMzUzWhcNMjMwODE4MjAxMzUzWjASMRAw DgYDVQQKEwdBY21lIENvMCowBQYDK2VwAyEAct5c3dzzbNOTi+C62w7QHoSivEWD MYAheDXZWHC55tGjbTBrMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAKBggrBgEF BQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs0At8sTSLCjiM24AZhxFY a2CswjAUBgNVHREEDTALgglsb2NhbGhvc3QwBQYDK2VwA0EABan+d16CeN8UD+QF a8HBhPAiOpaZeEF6+EqTlq9VfL3eSVd7CLRI+/KtY7ptwomuTeYzuV73adKdE9N2 ZrJuAw== -----END CERTIFICATE----- `), }, Type: "Opaque", } return certificateSecret, nil }, want: []*models.CertificateInfo{ { SerialNumber: "243609062983998893460787085129017550256", Name: "certificate-1", Expiry: "2023-08-18T20:13:53Z", Domains: []string{"localhost"}, }, }, wantErr: false, }, } for _, tt := range tests { k8sclientGetSecretMock = tt.mockGetSecret t.Run(tt.name, func(t *testing.T) { got, err := parseTenantCertificates(tt.args.ctx, tt.args.clientSet, tt.args.namespace, tt.args.secrets) if (err != nil) != tt.wantErr { t.Errorf("parseTenantCertificates(%v, %v, %v, %v)error = %v, wantErr %v", tt.args.ctx, tt.args.clientSet, tt.args.namespace, tt.args.secrets, err, tt.wantErr) } assert.Equalf(t, tt.want, got, "parseTenantCertificates(%v, %v, %v, %v)", tt.args.ctx, tt.args.clientSet, tt.args.namespace, tt.args.secrets) }) } } func Test_getTenant(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() opClient := opClientMock{} type args struct { ctx context.Context operatorClient OperatorClientI namespace string tenantName string mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) } tests := []struct { name string args args want *miniov2.Tenant wantErr bool }{ { name: "error getting tenant information", args: args{ ctx: ctx, operatorClient: opClient, namespace: "default", tenantName: "test", mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return nil, errors.New("error getting tenant information") }, }, want: nil, wantErr: true, }, { name: "success getting tenant information", args: args{ ctx: ctx, operatorClient: opClient, namespace: "default", tenantName: "test", mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "default", }, }, nil }, }, want: &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "default", }, }, wantErr: false, }, } for _, tt := range tests { opClientTenantGetMock = tt.args.mockTenantGet t.Run(tt.name, func(t *testing.T) { got, err := getTenant(tt.args.ctx, tt.args.operatorClient, tt.args.namespace, tt.args.tenantName) if (err != nil) != tt.wantErr { t.Errorf("getTenant(%v, %v, %v, %v)", tt.args.ctx, tt.args.operatorClient, tt.args.namespace, tt.args.tenantName) } assert.Equalf(t, tt.want, got, "getTenant(%v, %v, %v, %v)", tt.args.ctx, tt.args.operatorClient, tt.args.namespace, tt.args.tenantName) }) } } func Test_updateTenantConfigurationFile(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() opClient := opClientMock{} kClient := k8sClientMock{} type args struct { ctx context.Context operatorClient OperatorClientI client K8sClientI namespace string params operator_api.UpdateTenantConfigurationParams mockTenantGet func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) mockGetSecret func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) mockUpdateSecret func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) mockDeletePodCollection func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error } tests := []struct { name string args args wantErr bool }{ { name: "error getting tenant information", wantErr: true, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return nil, errors.New("error getting tenant") }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return nil, nil }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, nil }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return nil }, }, }, { name: "error during getting tenant configuration", wantErr: true, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: miniov2.TenantSpec{ Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration-secret", }, }, }, nil }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return nil, errors.New("error getting tenant configuration") }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, nil }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return nil }, }, }, { name: "error updating tenant configuration because of missing configuration secret", wantErr: true, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", Body: &models.UpdateTenantConfigurationRequest{}, }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, }, nil }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return &corev1.Secret{Data: map[string][]byte{ "config.env": []byte(` export MINIO_ROOT_USER=minio export MINIO_ROOT_PASSWORD=minio123 export MINIO_CONSOLE_ADDRESS=:8080 export MINIO_IDENTITY_LDAP_SERVER_ADDR=localhost:389 export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN="cn=admin,dc=min,dc=io" export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD="admin" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN="dc=min,dc=io" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER="(uid=%s)" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="ou=swengg,dc=min,dc=io" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(member=%d))" export MINIO_IDENTITY_LDAP_SERVER_INSECURE="on" `), }}, nil }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, nil }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return nil }, }, }, { name: "error because tenant configuration secret is nil", wantErr: true, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", Body: &models.UpdateTenantConfigurationRequest{}, }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: miniov2.TenantSpec{ Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration-secret", }, }, }, nil }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return nil, nil }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, nil }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return nil }, }, }, { name: "error updating tenant configuration because of k8s issue", wantErr: true, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", Body: &models.UpdateTenantConfigurationRequest{}, }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: miniov2.TenantSpec{ Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration-secret", }, }, }, nil }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return &corev1.Secret{Data: map[string][]byte{ "config.env": []byte(` export MINIO_ROOT_USER=minio export MINIO_ROOT_PASSWORD=minio123 export MINIO_CONSOLE_ADDRESS=:8080 export MINIO_IDENTITY_LDAP_SERVER_ADDR=localhost:389 export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN="cn=admin,dc=min,dc=io" export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD="admin" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN="dc=min,dc=io" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER="(uid=%s)" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="ou=swengg,dc=min,dc=io" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(member=%d))" export MINIO_IDENTITY_LDAP_SERVER_INSECURE="on" `), }}, nil }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, errors.New("error updating configuration secret") }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return nil }, }, }, { name: "error during deleting pod collection", wantErr: true, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", Body: &models.UpdateTenantConfigurationRequest{}, }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: miniov2.TenantSpec{ Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration-secret", }, }, }, nil }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return &corev1.Secret{Data: map[string][]byte{ "config.env": []byte(` export MINIO_ROOT_USER=minio export MINIO_ROOT_PASSWORD=minio123 export MINIO_CONSOLE_ADDRESS=:8080 export MINIO_IDENTITY_LDAP_SERVER_ADDR=localhost:389 export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN="cn=admin,dc=min,dc=io" export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD="admin" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN="dc=min,dc=io" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER="(uid=%s)" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="ou=swengg,dc=min,dc=io" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(member=%d))" export MINIO_IDENTITY_LDAP_SERVER_INSECURE="on" `), }}, nil }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, nil }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return errors.New("error deleting minio pods") }, }, }, { name: "success updating tenant configuration secret", wantErr: false, args: args{ ctx: ctx, operatorClient: opClient, client: kClient, namespace: "default", params: operator_api.UpdateTenantConfigurationParams{ Namespace: "default", Tenant: "test", Body: &models.UpdateTenantConfigurationRequest{}, }, mockTenantGet: func(ctx context.Context, namespace string, tenantName string, options metav1.GetOptions) (*miniov2.Tenant, error) { return &miniov2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "test", }, Spec: miniov2.TenantSpec{ Configuration: &corev1.LocalObjectReference{ Name: "tenant-configuration-secret", }, }, }, nil }, mockGetSecret: func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) { return &corev1.Secret{Data: map[string][]byte{ "config.env": []byte(` export MINIO_ROOT_USER=minio export MINIO_ROOT_PASSWORD=minio123 export MINIO_CONSOLE_ADDRESS=:8080 export MINIO_IDENTITY_LDAP_SERVER_ADDR=localhost:389 export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN="cn=admin,dc=min,dc=io" export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD="admin" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN="dc=min,dc=io" export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER="(uid=%s)" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="ou=swengg,dc=min,dc=io" export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(member=%d))" export MINIO_IDENTITY_LDAP_SERVER_INSECURE="on" `), }}, nil }, mockUpdateSecret: func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { return nil, nil }, mockDeletePodCollection: func(ctx context.Context, namespace string, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { return nil }, }, }, } for _, tt := range tests { k8sclientGetSecretMock = tt.args.mockGetSecret opClientTenantGetMock = tt.args.mockTenantGet UpdateSecretMock = tt.args.mockUpdateSecret DeletePodCollectionMock = tt.args.mockDeletePodCollection t.Run(tt.name, func(t *testing.T) { err := updateTenantConfigurationFile(tt.args.ctx, tt.args.operatorClient, tt.args.client, tt.args.namespace, tt.args.params) if (err != nil) != tt.wantErr { t.Errorf("updateTenantConfigurationFile(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.operatorClient, tt.args.client, tt.args.namespace, tt.args.params) } }) } }