diff --git a/.github/workflows/jobs.yaml b/.github/workflows/jobs.yaml index f1bb359f3..2263992fd 100644 --- a/.github/workflows/jobs.yaml +++ b/.github/workflows/jobs.yaml @@ -1527,7 +1527,7 @@ jobs: go tool cover -func=all.out | grep total > tmp2 result=`cat tmp2 | awk 'END {print $3}'` result=${result%\%} - threshold=56.9 + threshold=58.7 echo "Result:" echo "$result%" if (( $(echo "$result >= $threshold" |bc -l) )); then diff --git a/operatorapi/tenant_logs_handlers.go b/operatorapi/tenant_logs_handlers.go index 8b3c8ab7c..f072c5793 100644 --- a/operatorapi/tenant_logs_handlers.go +++ b/operatorapi/tenant_logs_handlers.go @@ -72,10 +72,14 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen if err != nil { return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantLogs) } + return getTenantLogsInfo(minTenant), nil +} + +func getTenantLogsInfo(minTenant *miniov2.Tenant) *models.TenantLogs { if minTenant.Spec.Log == nil { return &models.TenantLogs{ Disabled: true, - }, nil + } } annotations := []*models.Annotation{} for k, v := range minTenant.Spec.Log.Annotations { @@ -157,7 +161,7 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen tenantLoggingConfiguration.LogDBCPURequest = requestedDBCPU tenantLoggingConfiguration.LogDBMemRequest = requestedDBMem } - return tenantLoggingConfiguration, nil + return tenantLoggingConfiguration } // setTenantLogsResponse updates the Audit Log and Log DB configuration for the tenant @@ -177,7 +181,11 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen if err != nil { return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage) } + return setTenantLogs(ctx, minTenant, opClient, params) +} +func setTenantLogs(ctx context.Context, minTenant *miniov2.Tenant, opClient OperatorClientI, params operator_api.SetTenantLogsParams) (bool, *models.Error) { + var err error labels := make(map[string]string) if params.Data.Labels != nil { for i := 0; i < len(params.Data.Labels); i++ { @@ -315,7 +323,11 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen logSearchStorageClass := "standard" logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent) - + resources := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: *logSearchDiskSpace, + }, + } minTenant.Spec.Log.Db = &miniov2.LogDbConfig{ VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ @@ -325,11 +337,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen AccessModes: []corev1.PersistentVolumeAccessMode{ corev1.ReadWriteOnce, }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: *logSearchDiskSpace, - }, - }, + Resources: resources, StorageClassName: &logSearchStorageClass, }, }, @@ -339,7 +347,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen Image: params.Data.DbImage, ServiceAccountName: params.Data.DbServiceAccountName, Resources: corev1.ResourceRequirements{ - Requests: minTenant.Spec.Log.Db.Resources.Requests, + Requests: resources.Requests, }, } } else { @@ -382,6 +390,10 @@ func enableTenantLoggingResponse(session *models.Principal, params operator_api. if err != nil { return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage) } + return enableTenantLogging(ctx, minTenant, opClient, params.Tenant) +} + +func enableTenantLogging(ctx context.Context, minTenant *miniov2.Tenant, opClient OperatorClientI, tenantName string) (bool, *models.Error) { minTenant.EnsureDefaults() // Default class name for Log search diskSpaceFromAPI := int64(5) * humanize.GiByte // Default is 5Gi @@ -399,7 +411,7 @@ func enableTenantLoggingResponse(session *models.Principal, params operator_api. Db: &miniov2.LogDbConfig{ VolumeClaimTemplate: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ - Name: params.Tenant + "-log", + Name: tenantName + "-log", }, Spec: corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{ @@ -416,7 +428,7 @@ func enableTenantLoggingResponse(session *models.Principal, params operator_api. }, } - _, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{}) + _, err := opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{}) if err != nil { return false, restapi.ErrorWithContext(ctx, err) } diff --git a/operatorapi/tenant_logs_handlers_test.go b/operatorapi/tenant_logs_handlers_test.go new file mode 100644 index 000000000..b0556e195 --- /dev/null +++ b/operatorapi/tenant_logs_handlers_test.go @@ -0,0 +1,331 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2023 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 ( + "context" + "errors" + "net/http" + "testing" + + "github.com/minio/console/models" + "github.com/minio/console/operatorapi/operations" + "github.com/minio/console/operatorapi/operations/operator_api" + miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type TenantLogsTestSuite struct { + suite.Suite + assert *assert.Assertions + opClient opClientMock +} + +func (suite *TenantLogsTestSuite) SetupSuite() { + suite.assert = assert.New(suite.T()) + suite.opClient = opClientMock{} +} + +func (suite *TenantLogsTestSuite) SetupTest() { +} + +func (suite *TenantLogsTestSuite) TearDownSuite() { +} + +func (suite *TenantLogsTestSuite) TearDownTest() { +} + +func (suite *TenantLogsTestSuite) TestRegisterTenantLogsHandlers() { + api := &operations.OperatorAPI{} + suite.assertHandlersAreNil(api) + registerTenantLogsHandlers(api) + suite.assertHandlersAreNotNil(api) +} + +func (suite *TenantLogsTestSuite) assertHandlersAreNil(api *operations.OperatorAPI) { + suite.assert.Nil(api.OperatorAPIGetTenantLogsHandler) + suite.assert.Nil(api.OperatorAPISetTenantLogsHandler) + suite.assert.Nil(api.OperatorAPIEnableTenantLoggingHandler) + suite.assert.Nil(api.OperatorAPIDisableTenantLoggingHandler) +} + +func (suite *TenantLogsTestSuite) assertHandlersAreNotNil(api *operations.OperatorAPI) { + suite.assert.NotNil(api.OperatorAPIGetTenantLogsHandler) + suite.assert.NotNil(api.OperatorAPISetTenantLogsHandler) + suite.assert.NotNil(api.OperatorAPIEnableTenantLoggingHandler) + suite.assert.NotNil(api.OperatorAPIDisableTenantLoggingHandler) +} + +func (suite *TenantLogsTestSuite) TestGetTenantLogsHandlerWithError() { + params, api := suite.initGetTenantLogsRequest() + response := api.OperatorAPIGetTenantLogsHandler.Handle(params, &models.Principal{}) + _, ok := response.(*operator_api.GetTenantLogsDefault) + suite.assert.True(ok) +} + +func (suite *TenantLogsTestSuite) initGetTenantLogsRequest() (params operator_api.GetTenantLogsParams, api operations.OperatorAPI) { + registerTenantLogsHandlers(&api) + params.HTTPRequest = &http.Request{} + params.Namespace = "mock-namespace" + params.Tenant = "mock-name" + return params, api +} + +func (suite *TenantLogsTestSuite) TestGetTenantLogsInfoWithoutLogAndNoError() { + tenant := suite.createMockTenant(false, true) + tenantLogs := getTenantLogsInfo(tenant) + suite.assert.True(tenantLogs.Disabled) +} + +func (suite *TenantLogsTestSuite) TestGetTenantLogsInfoWithLogAndNoError() { + tenant := suite.createMockTenant(true, true) + tenantLogs := getTenantLogsInfo(tenant) + suite.assert.False(tenantLogs.Disabled) + suite.assert.NotNil(tenantLogs.Labels) + suite.assert.NotNil(tenantLogs.Annotations) +} + +func (suite *TenantLogsTestSuite) TestSetTenantLogsWithoutError() { + opClientTenantUpdateMock = func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) { + return tenant, nil + } + tenant := suite.createMockTenant(true, true) + success, err := setTenantLogs(context.Background(), tenant, suite.opClient, suite.createMockParams()) + suite.assert.True(success) + suite.assert.Nil(err) +} + +func (suite *TenantLogsTestSuite) TestSetTenantLogsWithoutDBAndError() { + opClientTenantUpdateMock = func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) { + return tenant, nil + } + tenant := suite.createMockTenant(true, false) + params := suite.createMockParams() + params.Data.LogDBCPURequest = "" + params.Data.LogDBMemRequest = "" + success, err := setTenantLogs(context.Background(), tenant, suite.opClient, params) + suite.assert.True(success) + suite.assert.Nil(err) +} + +func (suite *TenantLogsTestSuite) TestSetTenantLogsWithError() { + opClientTenantUpdateMock = func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) { + return nil, errors.New("mock-error") + } + tenant := suite.createMockTenant(true, true) + success, err := setTenantLogs(context.Background(), tenant, suite.opClient, suite.createMockParams()) + suite.assert.False(success) + suite.assert.NotNil(err) +} + +func (suite *TenantLogsTestSuite) TestEnableTenantLoggingWithoutError() { + opClientTenantUpdateMock = func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) { + return tenant, nil + } + tenant := suite.createMockTenant(true, true) + success, err := enableTenantLogging(context.Background(), tenant, suite.opClient, "mock-tenant") + suite.assert.True(success) + suite.assert.Nil(err) +} + +func (suite *TenantLogsTestSuite) TestEnableTenantLoggingWithError() { + opClientTenantUpdateMock = func(ctx context.Context, tenant *miniov2.Tenant, opts metav1.UpdateOptions) (*miniov2.Tenant, error) { + return nil, errors.New("mock-error") + } + tenant := suite.createMockTenant(true, true) + success, err := enableTenantLogging(context.Background(), tenant, suite.opClient, "mock-tenant") + suite.assert.False(success) + suite.assert.NotNil(err) +} + +func (suite *TenantLogsTestSuite) createMockParams() operator_api.SetTenantLogsParams { + runAsUser := "1000" + runAsGroup := "1000" + fsGroup := "1000" + return operator_api.SetTenantLogsParams{ + Data: &models.TenantLogs{ + Labels: []*models.Label{ + { + Key: "key", + Value: "value", + }, + }, + Annotations: []*models.Annotation{ + { + Key: "key", + Value: "value", + }, + }, + NodeSelector: []*models.NodeSelector{ + { + Key: "key", + Value: "value", + }, + }, + LogCPURequest: "5Gi", + LogMemRequest: "5Gi", + DbLabels: []*models.Label{ + { + Key: "key", + Value: "value", + }, + }, + DbAnnotations: []*models.Annotation{ + { + Key: "key", + Value: "value", + }, + }, + DbNodeSelector: []*models.NodeSelector{ + { + Key: "key", + Value: "value", + }, + }, + LogDBCPURequest: "5Gi", + LogDBMemRequest: "5Gi", + Image: "mock-image", + SecurityContext: &models.SecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &runAsGroup, + FsGroup: fsGroup, + }, + DiskCapacityGB: "10", + ServiceAccountName: "mock-service-account-name", + DbImage: "mock-db-image", + DbSecurityContext: &models.SecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &runAsGroup, + FsGroup: fsGroup, + }, + }, + } +} + +func (suite *TenantLogsTestSuite) createMockTenant(withSpecLog, withDB bool) *miniov2.Tenant { + cap := 10 + runAsUser := int64(1000) + runAsGroup := int64(1000) + fsGroup := int64(1000) + tenant := &miniov2.Tenant{} + if withSpecLog { + tenant.Spec.Log = &miniov2.LogConfig{ + Annotations: map[string]string{ + "key": "value", + }, + Labels: map[string]string{ + "key": "value", + }, + NodeSelector: map[string]string{ + "key": "value", + }, + Db: &miniov2.LogDbConfig{ + Annotations: map[string]string{ + "key": "value", + }, + Labels: map[string]string{ + "key": "value", + }, + NodeSelector: map[string]string{ + "key": "value", + }, + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &runAsGroup, + FSGroup: &fsGroup, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &runAsGroup, + FSGroup: &fsGroup, + }, + Audit: &miniov2.AuditConfig{ + DiskCapacityGB: &cap, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + } + if !withDB { + tenant.Spec.Log.Db = nil + } + } + return tenant +} + +func (suite *TenantLogsTestSuite) TestSetTenantLogsHandlerWithError() { + params, api := suite.initSetTenantLogsRequest() + response := api.OperatorAPISetTenantLogsHandler.Handle(params, &models.Principal{}) + _, ok := response.(*operator_api.SetTenantLogsDefault) + suite.assert.True(ok) +} + +func (suite *TenantLogsTestSuite) initSetTenantLogsRequest() (params operator_api.SetTenantLogsParams, api operations.OperatorAPI) { + registerTenantLogsHandlers(&api) + params.HTTPRequest = &http.Request{} + params.Namespace = "mock-namespace" + params.Tenant = "mock-name" + return params, api +} + +func (suite *TenantLogsTestSuite) TestEnableTenantLoggingHandlerWithError() { + params, api := suite.initEnableTenantLoggingRequest() + response := api.OperatorAPIEnableTenantLoggingHandler.Handle(params, &models.Principal{}) + _, ok := response.(*operator_api.EnableTenantLoggingDefault) + suite.assert.True(ok) +} + +func (suite *TenantLogsTestSuite) initEnableTenantLoggingRequest() (params operator_api.EnableTenantLoggingParams, api operations.OperatorAPI) { + registerTenantLogsHandlers(&api) + params.HTTPRequest = &http.Request{} + params.Namespace = "mock-namespace" + params.Tenant = "mock-name" + return params, api +} + +func (suite *TenantLogsTestSuite) TestDisableTenantLoggingHandlerWithError() { + params, api := suite.initDisableTenantLoggingRequest() + response := api.OperatorAPIDisableTenantLoggingHandler.Handle(params, &models.Principal{}) + _, ok := response.(*operator_api.DisableTenantLoggingDefault) + suite.assert.True(ok) +} + +func (suite *TenantLogsTestSuite) initDisableTenantLoggingRequest() (params operator_api.DisableTenantLoggingParams, api operations.OperatorAPI) { + registerTenantLogsHandlers(&api) + params.HTTPRequest = &http.Request{} + params.Namespace = "mock-namespace" + params.Tenant = "mock-name" + return params, api +} + +func TestTenantLogs(t *testing.T) { + suite.Run(t, new(TenantLogsTestSuite)) +}