diff --git a/pkg/splunk/enterprise/clustermanager.go b/pkg/splunk/enterprise/clustermanager.go index 269753c5c..b097ca019 100644 --- a/pkg/splunk/enterprise/clustermanager.go +++ b/pkg/splunk/enterprise/clustermanager.go @@ -159,6 +159,13 @@ func ApplyClusterManager(ctx context.Context, client splcommon.ControllerClient, return result, err } + // Remove owner reference from KVService CR (delete if last owner) + err = DeleteKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "DeleteKVServiceCR", fmt.Sprintf("remove KVService owner reference failed %s", err.Error())) + return result, err + } + DeleteOwnerReferencesForResources(ctx, client, cr, SplunkClusterManager) terminating, err := splctrl.CheckForDeletion(ctx, cr, client) @@ -180,6 +187,13 @@ func ApplyClusterManager(ctx context.Context, client splcommon.ControllerClient, return result, err } + // create or update KVService CR with owner reference + err = ApplyKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "ApplyKVServiceCR", fmt.Sprintf("apply KVService CR failed %s", err.Error())) + return result, err + } + // create or update statefulset for the cluster manager statefulSet, err := getClusterManagerStatefulSet(ctx, client, cr) if err != nil { diff --git a/pkg/splunk/enterprise/clustermanager_test.go b/pkg/splunk/enterprise/clustermanager_test.go index 586adb316..71058fdd7 100644 --- a/pkg/splunk/enterprise/clustermanager_test.go +++ b/pkg/splunk/enterprise/clustermanager_test.go @@ -67,6 +67,7 @@ func TestApplyClusterManager(t *testing.T) { {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-cluster-manager-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-cluster-manager-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-cluster-manager"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -85,6 +86,7 @@ func TestApplyClusterManager(t *testing.T) { {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-cluster-manager-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-cluster-manager-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-cluster-manager"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, @@ -108,8 +110,8 @@ func TestApplyClusterManager(t *testing.T) { } listmockCall := []spltest.MockFuncCall{ {ListOpts: listOpts}} - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[6], funcCalls[10], funcCalls[5]}, "List": {listmockCall[0]}, "Update": {funcCalls[0]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[5]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[7], funcCalls[11], funcCalls[6]}, "List": {listmockCall[0]}, "Update": {funcCalls[0]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[6]}, "List": {listmockCall[0]}} current := enterpriseApi.ClusterManager{ TypeMeta: metav1.TypeMeta{ @@ -564,6 +566,7 @@ func TestApplyClusterManagerWithSmartstore(t *testing.T) { {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-cluster-manager-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-cluster-manager-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-cluster-manager"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -589,6 +592,7 @@ func TestApplyClusterManagerWithSmartstore(t *testing.T) { {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-cluster-manager-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-cluster-manager-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-cluster-manager"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, @@ -617,8 +621,8 @@ func TestApplyClusterManagerWithSmartstore(t *testing.T) { {ListOpts: listOpts}, {ListOpts: listOpts1}, } - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[7], funcCalls[8], funcCalls[12], funcCalls[14]}, "List": {listmockCall[0], listmockCall[0], listmockCall[1]}, "Update": {funcCalls[0], funcCalls[3], funcCalls[15]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[9]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[7], funcCalls[8], funcCalls[9], funcCalls[13], funcCalls[15]}, "List": {listmockCall[0], listmockCall[0], listmockCall[1]}, "Update": {funcCalls[0], funcCalls[3], funcCalls[16]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[10]}, "List": {listmockCall[0]}} current := enterpriseApi.ClusterManager{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/splunk/enterprise/finalizers_test.go b/pkg/splunk/enterprise/finalizers_test.go index 92c46f1e0..5d3d019f2 100644 --- a/pkg/splunk/enterprise/finalizers_test.go +++ b/pkg/splunk/enterprise/finalizers_test.go @@ -144,6 +144,7 @@ func splunkDeletionTester(t *testing.T, cr splcommon.MetaObject, delete func(spl {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-standalone-stack1-configmap"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, {MetaName: "*v4.Standalone-test-stack1"}, @@ -176,6 +177,7 @@ func splunkDeletionTester(t *testing.T, cr splcommon.MetaObject, delete func(spl {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-license-manager-stack1-configmap"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-license-manager"}, {MetaName: "*v4.LicenseManager-test-stack1"}, @@ -192,6 +194,7 @@ func splunkDeletionTester(t *testing.T, cr splcommon.MetaObject, delete func(spl {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-search-head-stack1-configmap"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-search-head"}, {MetaName: "*v4.SearchHeadCluster-test-stack1"}, @@ -229,6 +232,7 @@ func splunkDeletionTester(t *testing.T, cr splcommon.MetaObject, delete func(spl {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-cluster-manager-stack1-configmap"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-cluster-manager"}, {MetaName: "*v4.ClusterManager-test-stack1"}, @@ -256,6 +260,7 @@ func splunkDeletionTester(t *testing.T, cr splcommon.MetaObject, delete func(spl {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-monitoring-console-stack1-configmap"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v4.MonitoringConsole-test-stack1"}, {MetaName: "*v4.MonitoringConsole-test-stack1"}, } @@ -301,6 +306,7 @@ func splunkDeletionTester(t *testing.T, cr splcommon.MetaObject, delete func(spl {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-indexer-stack1-configmap"}, {MetaName: "*v4.ClusterManager-test-manager1"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-indexer"}, {MetaName: "*v4.IndexerCluster-test-stack1"}, diff --git a/pkg/splunk/enterprise/indexercluster.go b/pkg/splunk/enterprise/indexercluster.go index 2d135d84f..f08cb3bf2 100644 --- a/pkg/splunk/enterprise/indexercluster.go +++ b/pkg/splunk/enterprise/indexercluster.go @@ -127,6 +127,13 @@ func ApplyIndexerClusterManager(ctx context.Context, client splcommon.Controller // check if deletion has been requested if cr.ObjectMeta.DeletionTimestamp != nil { + // Remove owner reference from KVService CR (delete if last owner) + err = DeleteKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "DeleteKVServiceCR", fmt.Sprintf("remove KVService owner reference failed %s", err.Error())) + return result, err + } + DeleteOwnerReferencesForResources(ctx, client, cr, SplunkIndexer) terminating, err := splctrl.CheckForDeletion(ctx, cr, client) @@ -155,6 +162,13 @@ func ApplyIndexerClusterManager(ctx context.Context, client splcommon.Controller return result, err } + // create or update KVService CR with owner reference + err = ApplyKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "ApplyKVServiceCR", fmt.Sprintf("apply KVService CR failed %s", err.Error())) + return result, err + } + // create or update statefulset for the indexers statefulSet, err := getIndexerStatefulSet(ctx, client, cr) if err != nil { diff --git a/pkg/splunk/enterprise/indexercluster_test.go b/pkg/splunk/enterprise/indexercluster_test.go index 92f562c5a..50bc96e89 100644 --- a/pkg/splunk/enterprise/indexercluster_test.go +++ b/pkg/splunk/enterprise/indexercluster_test.go @@ -159,6 +159,7 @@ func TestApplyIndexerCluster(t *testing.T) { {MetaName: "*v4.ClusterManager-test-manager1"}, {MetaName: "*v1.Service-test-splunk-stack1-indexer-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-indexer-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-indexer"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -176,6 +177,7 @@ func TestApplyIndexerCluster(t *testing.T) { {MetaName: "*v4.ClusterManager-test-manager1"}, {MetaName: "*v1.Service-test-splunk-stack1-indexer-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-indexer-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-indexer"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, @@ -200,7 +202,7 @@ func TestApplyIndexerCluster(t *testing.T) { {ListOpts: listOpts}, {ListOpts: listOpts1}, } - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[5], funcCalls[6], funcCalls[10], funcCalls[12]}, "Update": {funcCalls[0]}, "List": {listmockCall[0], listmockCall[1]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[5], funcCalls[6], funcCalls[7], funcCalls[11], funcCalls[13]}, "Update": {funcCalls[0]}, "List": {listmockCall[0], listmockCall[1]}} updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "List": {listmockCall[0], listmockCall[1]}} current := enterpriseApi.IndexerCluster{ diff --git a/pkg/splunk/enterprise/licensemanager.go b/pkg/splunk/enterprise/licensemanager.go index d603cbc9f..4dfc32045 100644 --- a/pkg/splunk/enterprise/licensemanager.go +++ b/pkg/splunk/enterprise/licensemanager.go @@ -109,6 +109,13 @@ func ApplyLicenseManager(ctx context.Context, client splcommon.ControllerClient, } } + // Remove owner reference from KVService CR (delete if last owner) + err = DeleteKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "DeleteKVServiceCR", fmt.Sprintf("remove KVService owner reference failed %s", err.Error())) + return result, err + } + DeleteOwnerReferencesForResources(ctx, client, cr, SplunkLicenseManager) terminating, err := splctrl.CheckForDeletion(ctx, cr, client) @@ -129,6 +136,13 @@ func ApplyLicenseManager(ctx context.Context, client splcommon.ControllerClient, return result, err } + // create or update KVService CR with owner reference + err = ApplyKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "ApplyKVServiceCR", fmt.Sprintf("apply KVService CR failed %s", err.Error())) + return result, err + } + // create or update statefulset statefulSet, err := getLicenseManagerStatefulSet(ctx, client, cr) if err != nil { diff --git a/pkg/splunk/enterprise/licensemanager_test.go b/pkg/splunk/enterprise/licensemanager_test.go index ae5afb98a..0f3184d96 100644 --- a/pkg/splunk/enterprise/licensemanager_test.go +++ b/pkg/splunk/enterprise/licensemanager_test.go @@ -53,6 +53,7 @@ func TestApplyLicenseManager(t *testing.T) { {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.ConfigMap-test-splunk-license-manager-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-license-manager-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-license-manager"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -76,9 +77,9 @@ func TestApplyLicenseManager(t *testing.T) { listmockCall := []spltest.MockFuncCall{ {ListOpts: listOpts}, } - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[6], funcCalls[10], funcCalls[11]}, "Update": {funcCalls[0]}, "List": {listmockCall[0]}} - updateFuncCalls := []spltest.MockFuncCall{funcCalls[0], funcCalls[1], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[9], funcCalls[10], funcCalls[11], funcCalls[12], funcCalls[11], funcCalls[13], funcCalls[13]} - updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[5]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[7], funcCalls[11], funcCalls[12]}, "Update": {funcCalls[0]}, "List": {listmockCall[0]}} + updateFuncCalls := []spltest.MockFuncCall{funcCalls[0], funcCalls[1], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[7], funcCalls[10], funcCalls[11], funcCalls[12], funcCalls[13], funcCalls[12], funcCalls[14], funcCalls[14]} + updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[6]}, "List": {listmockCall[0]}} current := enterpriseApi.LicenseManager{ TypeMeta: metav1.TypeMeta{ Kind: "LicenseManager", diff --git a/pkg/splunk/enterprise/monitoringconsole.go b/pkg/splunk/enterprise/monitoringconsole.go index 64de4a2de..eaa76bbc9 100644 --- a/pkg/splunk/enterprise/monitoringconsole.go +++ b/pkg/splunk/enterprise/monitoringconsole.go @@ -112,6 +112,13 @@ func ApplyMonitoringConsole(ctx context.Context, client splcommon.ControllerClie } } + // Remove owner reference from KVService CR (delete if last owner) + err = DeleteKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "DeleteKVServiceCR", fmt.Sprintf("remove KVService owner reference failed %s", err.Error())) + return result, err + } + terminating, err := splctrl.CheckForDeletion(ctx, cr, client) if terminating && err != nil { // don't bother if no error, since it will just be removed immmediately after cr.Status.Phase = enterpriseApi.PhaseTerminating @@ -135,6 +142,13 @@ func ApplyMonitoringConsole(ctx context.Context, client splcommon.ControllerClie return result, err } + // create or update KVService CR with owner reference + err = ApplyKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "ApplyKVServiceCR", fmt.Sprintf("apply KVService CR failed %s", err.Error())) + return result, err + } + // create or update statefulset statefulSet, err := getMonitoringConsoleStatefulSet(ctx, client, cr) if err != nil { diff --git a/pkg/splunk/enterprise/monitoringconsole_test.go b/pkg/splunk/enterprise/monitoringconsole_test.go index 3108c7d6a..2df11140a 100644 --- a/pkg/splunk/enterprise/monitoringconsole_test.go +++ b/pkg/splunk/enterprise/monitoringconsole_test.go @@ -15,6 +15,12 @@ package enterprise import ( "context" + "os" + "path/filepath" + "runtime/debug" + "testing" + "time" + enterpriseApi "github.com/splunk/splunk-operator/api/v4" splclient "github.com/splunk/splunk-operator/pkg/splunk/client" splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" @@ -27,13 +33,8 @@ import ( "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "os" - "path/filepath" - "runtime/debug" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "testing" - "time" ) func init() { @@ -60,6 +61,7 @@ func TestApplyMonitoringConsole(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-monitoring-console-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-monitoring-console-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-monitoring-console-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-monitoring-console"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -79,6 +81,7 @@ func TestApplyMonitoringConsole(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-monitoring-console-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-monitoring-console-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-monitoring-console-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-monitoring-console"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, @@ -106,8 +109,8 @@ func TestApplyMonitoringConsole(t *testing.T) { {ListOpts: listOpts}, {ListOpts: listOpts2}, } - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[9], funcCalls[11], funcCalls[12], funcCalls[6]}, "Update": {funcCalls[0], funcCalls[12]}, "List": {listmockCall[0]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {updateFuncCalls[5]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[10], funcCalls[12], funcCalls[13], funcCalls[7]}, "Update": {funcCalls[0], funcCalls[13]}, "List": {listmockCall[0]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {updateFuncCalls[6]}, "List": {listmockCall[0]}} current := enterpriseApi.MonitoringConsole{ TypeMeta: metav1.TypeMeta{ Kind: "MonitoringConsole", diff --git a/pkg/splunk/enterprise/searchheadcluster.go b/pkg/splunk/enterprise/searchheadcluster.go index d5b4fd12f..95a409b8c 100644 --- a/pkg/splunk/enterprise/searchheadcluster.go +++ b/pkg/splunk/enterprise/searchheadcluster.go @@ -130,6 +130,13 @@ func ApplySearchHeadCluster(ctx context.Context, client splcommon.ControllerClie } } + // Remove owner reference from KVService CR (delete if last owner) + err = DeleteKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "DeleteKVServiceCR", fmt.Sprintf("remove KVService owner reference failed %s", err.Error())) + return result, err + } + DeleteOwnerReferencesForResources(ctx, client, cr, SplunkSearchHead) terminating, err := splctrl.CheckForDeletion(ctx, cr, client) @@ -163,6 +170,13 @@ func ApplySearchHeadCluster(ctx context.Context, client splcommon.ControllerClie return result, err } + // create or update KVService CR with owner reference + err = ApplyKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "ApplyKVServiceCR", fmt.Sprintf("apply KVService CR failed %s", err.Error())) + return result, err + } + // create or update statefulset for the deployer statefulSet, err := getDeployerStatefulSet(ctx, client, cr) if err != nil { diff --git a/pkg/splunk/enterprise/searchheadcluster_test.go b/pkg/splunk/enterprise/searchheadcluster_test.go index 569d0be8a..b59b9c0a2 100644 --- a/pkg/splunk/enterprise/searchheadcluster_test.go +++ b/pkg/splunk/enterprise/searchheadcluster_test.go @@ -79,6 +79,7 @@ func TestApplySearchHeadCluster(t *testing.T) { {MetaName: "*v1.Service-test-splunk-stack1-search-head-service"}, {MetaName: "*v1.Service-test-splunk-stack1-deployer-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-deployer"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -110,6 +111,7 @@ func TestApplySearchHeadCluster(t *testing.T) { {MetaName: "*v1.Service-test-splunk-stack1-search-head-service"}, {MetaName: "*v1.Service-test-splunk-stack1-deployer-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-deployer"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -142,8 +144,8 @@ func TestApplySearchHeadCluster(t *testing.T) { listmockCall := []spltest.MockFuncCall{ {ListOpts: listOpts}} - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[10], funcCalls[12], funcCalls[13], funcCalls[17], funcCalls[19]}, "Update": {funcCalls[0]}, "List": {listmockCall[0], listmockCall[0]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": createFuncCalls, "Update": {createFuncCalls[6], createFuncCalls[18]}, "List": {listmockCall[0], listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[7], funcCalls[11], funcCalls[13], funcCalls[14], funcCalls[18], funcCalls[20]}, "Update": {funcCalls[0]}, "List": {listmockCall[0], listmockCall[0]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": createFuncCalls, "Update": {createFuncCalls[7], createFuncCalls[19]}, "List": {listmockCall[0], listmockCall[0]}} statefulSet := enterpriseApi.SearchHeadCluster{ TypeMeta: metav1.TypeMeta{ Kind: "SearchHeadCluster", diff --git a/pkg/splunk/enterprise/standalone.go b/pkg/splunk/enterprise/standalone.go index dbfa17051..e606764e5 100644 --- a/pkg/splunk/enterprise/standalone.go +++ b/pkg/splunk/enterprise/standalone.go @@ -139,6 +139,13 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr } } + // Remove owner reference from KVService CR (delete if last owner) + err = DeleteKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "DeleteKVServiceCR", fmt.Sprintf("remove KVService owner reference failed %s", err.Error())) + return result, err + } + DeleteOwnerReferencesForResources(ctx, client, cr, SplunkStandalone) terminating, err := splctrl.CheckForDeletion(ctx, cr, client) @@ -165,6 +172,13 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr return result, err } + // create or update KVService CR with owner reference + err = ApplyKVServiceCR(ctx, client, cr) + if err != nil { + eventPublisher.Warning(ctx, "ApplyKVServiceCR", fmt.Sprintf("apply KVService CR failed %s", err.Error())) + return result, err + } + // If we are using appFramework and are scaling up, we should re-populate the // configMap with all the appSource entries. This is done so that the new pods // that come up now will have the complete list of all the apps and then can diff --git a/pkg/splunk/enterprise/standalone_test.go b/pkg/splunk/enterprise/standalone_test.go index acdb07515..59464974e 100644 --- a/pkg/splunk/enterprise/standalone_test.go +++ b/pkg/splunk/enterprise/standalone_test.go @@ -68,6 +68,7 @@ func TestApplyStandalone(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-standalone-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -87,6 +88,7 @@ func TestApplyStandalone(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-standalone-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, @@ -95,7 +97,6 @@ func TestApplyStandalone(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-stack1-standalone-smartstore"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, - //{MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, } deltaCalls := []spltest.MockFuncCall{ {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, @@ -115,8 +116,8 @@ func TestApplyStandalone(t *testing.T) { listmockCall := []spltest.MockFuncCall{ {ListOpts: listOpts}} - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[8], funcCalls[11], funcCalls[14]}, "Update": {funcCalls[0]}, "List": {listmockCall[0]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[14]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[9], funcCalls[12], funcCalls[15]}, "Update": {funcCalls[0]}, "List": {listmockCall[0]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": updateFuncCalls, "Update": {funcCalls[15]}, "List": {listmockCall[0]}} current := enterpriseApi.Standalone{ TypeMeta: metav1.TypeMeta{ Kind: "Standalone", @@ -213,6 +214,7 @@ func TestApplyStandaloneWithSmartstore(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-standalone-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, @@ -237,6 +239,7 @@ func TestApplyStandaloneWithSmartstore(t *testing.T) { {MetaName: "*v1.ConfigMap-test-splunk-standalone-stack1-configmap"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-headless"}, {MetaName: "*v1.Service-test-splunk-stack1-standalone-service"}, + {MetaName: "*v4.KVService-test-splunk-test-kvservice"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-standalone"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, {MetaName: "*v1.ConfigMap-test-splunk-test-probe-configmap"}, @@ -262,8 +265,8 @@ func TestApplyStandaloneWithSmartstore(t *testing.T) { listmockCall := []spltest.MockFuncCall{ {ListOpts: listOpts}} - createCalls := map[string][]spltest.MockFuncCall{"Get": createFuncCalls, "Create": {funcCalls[2], funcCalls[6], funcCalls[7], funcCalls[8], funcCalls[10], funcCalls[12], funcCalls[15]}, "Update": {funcCalls[0]}, "List": {listmockCall[0]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Update": {funcCalls[9]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": createFuncCalls, "Create": {funcCalls[2], funcCalls[6], funcCalls[7], funcCalls[8], funcCalls[9], funcCalls[11], funcCalls[13], funcCalls[16]}, "Update": {funcCalls[0]}, "List": {listmockCall[0]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Update": {funcCalls[10]}, "List": {listmockCall[0]}} current := enterpriseApi.Standalone{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/splunk/enterprise/util.go b/pkg/splunk/enterprise/util.go index abd96482a..0054d364c 100644 --- a/pkg/splunk/enterprise/util.go +++ b/pkg/splunk/enterprise/util.go @@ -2494,6 +2494,158 @@ func changeAnnotations(ctx context.Context, c splcommon.ControllerClient, image return err } +// ApplyKVServiceCR ensures the KVService CR exists with the current CR as an owner. +// - If KVService doesn't exist, create it with the current CR as owner +// - If KVService exists but current CR is not an owner, add owner reference +func ApplyKVServiceCR(ctx context.Context, c splcommon.ControllerClient, cr splcommon.MetaObject) error { + reqLogger := log.FromContext(ctx) + scopedLog := reqLogger.WithName("ApplyKVServiceCR").WithValues( + "kind", cr.GetObjectKind().GroupVersionKind().Kind, + "name", cr.GetName(), + "namespace", cr.GetNamespace()) + + // Use namespace-scoped KVService name + kvServiceName := GetKVServiceName(cr.GetNamespace()) + namespacedName := types.NamespacedName{ + Namespace: cr.GetNamespace(), + Name: kvServiceName, + } + + var existingKVService enterpriseApi.KVService + if err := c.Get(ctx, namespacedName, &existingKVService); err != nil { + if !k8serrors.IsNotFound(err) { + scopedLog.Error(err, "Failed to get KVService CR") + return err + } + + // KVService doesn't exist, create it + scopedLog.Info("Creating KVService CR") + kvService := &enterpriseApi.KVService{ + TypeMeta: metav1.TypeMeta{ + APIVersion: enterpriseApi.GroupVersion.String(), + Kind: "KVService", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: kvServiceName, + Namespace: cr.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{ + splcommon.AsOwner(cr, false), // Not controller, just owner + }, + }, + Spec: enterpriseApi.KVServiceSpec{}, + } + + err = splutil.CreateResource(ctx, c, kvService) + if err != nil { + scopedLog.Error(err, "Failed to create KVService CR") + return err + } + scopedLog.Info("Successfully created KVService CR") + return nil + } + + // KVService exists, check if current CR is already an owner + if hasOwnerReference(existingKVService.GetOwnerReferences(), cr) { + scopedLog.Info("KVService CR already has owner reference for this CR") + return nil + } + + // Add owner reference for this CR + scopedLog.Info("Adding owner reference to existing KVService CR") + ownerRefs := existingKVService.GetOwnerReferences() + ownerRefs = append(ownerRefs, splcommon.AsOwner(cr, false)) + existingKVService.SetOwnerReferences(ownerRefs) + + err := c.Update(ctx, &existingKVService) + if err != nil { + scopedLog.Error(err, "Failed to add owner reference to KVService CR") + return err + } + scopedLog.Info("Successfully added owner reference to KVService CR") + return nil +} + +// DeleteKVServiceCR removes the owner reference for the current CR from KVService. +// If this is the last owner, the KVService CR is deleted. +func DeleteKVServiceCR(ctx context.Context, c splcommon.ControllerClient, cr splcommon.MetaObject) error { + reqLogger := log.FromContext(ctx) + scopedLog := reqLogger.WithName("DeleteKVServiceCR").WithValues( + "kind", cr.GetObjectKind().GroupVersionKind().Kind, + "name", cr.GetName(), + "namespace", cr.GetNamespace()) + + // Use namespace-scoped KVService name + kvServiceName := GetKVServiceName(cr.GetNamespace()) + namespacedName := types.NamespacedName{ + Namespace: cr.GetNamespace(), + Name: kvServiceName, + } + + var existingKVService enterpriseApi.KVService + err := c.Get(ctx, namespacedName, &existingKVService) + if err != nil { + if k8serrors.IsNotFound(err) { + // KVService doesn't exist, nothing to do + scopedLog.Info("KVService CR not found, nothing to clean up") + return nil + } + scopedLog.Error(err, "Failed to get KVService CR for deletion cleanup") + return err + } + + // Remove owner reference for this CR + ownerRefs := existingKVService.GetOwnerReferences() + newOwnerRefs := removeOwnerReference(ownerRefs, cr) + + if len(newOwnerRefs) == 0 { + // This was the last owner, delete the KVService CR + scopedLog.Info("Deleting KVService CR as this is the last owner") + err = c.Delete(ctx, &existingKVService) + if err != nil && !k8serrors.IsNotFound(err) { + scopedLog.Error(err, "Failed to delete KVService CR") + return err + } + scopedLog.Info("Successfully deleted KVService CR") + return nil + } + + // Update with remaining owner references + existingKVService.SetOwnerReferences(newOwnerRefs) + err = c.Update(ctx, &existingKVService) + if err != nil { + scopedLog.Error(err, "Failed to update KVService CR owner references") + return err + } + scopedLog.Info("Removed owner reference from KVService CR") + return nil +} + +// GetKVServiceName returns the KVService CR name for a namespace +func GetKVServiceName(namespace string) string { + return fmt.Sprintf("splunk-%s-kvservice", namespace) +} + +// hasOwnerReference checks if the given CR is already an owner +func hasOwnerReference(ownerRefs []metav1.OwnerReference, cr splcommon.MetaObject) bool { + for _, ref := range ownerRefs { + if ref.UID == cr.GetUID() { + return true + } + } + return false +} + +// removeOwnerReference removes the owner reference for the given CR and returns the updated list +func removeOwnerReference(ownerRefs []metav1.OwnerReference, cr splcommon.MetaObject) []metav1.OwnerReference { + newRefs := make([]metav1.OwnerReference, 0, len(ownerRefs)) + for _, ref := range ownerRefs { + if ref.UID != cr.GetUID() { + newRefs = append(newRefs, ref) + } + } + return newRefs +} + // loadFixture loads a JSON fixture file from the testdata/fixtures directory // and returns it as compact JSON (minified, single-line) func loadFixture(t *testing.T, filename string) string { diff --git a/pkg/splunk/enterprise/util_test.go b/pkg/splunk/enterprise/util_test.go index e717e82da..8eb778861 100644 --- a/pkg/splunk/enterprise/util_test.go +++ b/pkg/splunk/enterprise/util_test.go @@ -3278,3 +3278,261 @@ func TestGetCurrentImage(t *testing.T) { } } + +func TestApplyKVServiceCR(t *testing.T) { + ctx := context.TODO() + + // Setup scheme + sch := pkgruntime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(sch)) + utilruntime.Must(enterpriseApi.AddToScheme(sch)) + + // Create a Standalone CR to use as owner + standalone := &enterpriseApi.Standalone{ + TypeMeta: metav1.TypeMeta{ + Kind: "Standalone", + APIVersion: "enterprise.splunk.com/v4", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-standalone", + Namespace: "test", + UID: "test-uid-1", + }, + } + + t.Run("creates KVService when it doesn't exist", func(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(sch).Build() + + err := ApplyKVServiceCR(ctx, client, standalone) + if err != nil { + t.Errorf("ApplyKVServiceCR should not return error: %v", err) + } + + // Verify KVService was created + var kvService enterpriseApi.KVService + kvServiceName := GetKVServiceName(standalone.GetNamespace()) + err = client.Get(ctx, types.NamespacedName{Name: kvServiceName, Namespace: standalone.GetNamespace()}, &kvService) + if err != nil { + t.Errorf("KVService should exist: %v", err) + } + + // Verify owner reference + if len(kvService.GetOwnerReferences()) != 1 { + t.Errorf("KVService should have 1 owner reference, got %d", len(kvService.GetOwnerReferences())) + } + if kvService.GetOwnerReferences()[0].UID != standalone.GetUID() { + t.Errorf("Owner reference UID mismatch") + } + }) + + t.Run("adds owner reference when KVService exists", func(t *testing.T) { + // Create another CR + standalone2 := &enterpriseApi.Standalone{ + TypeMeta: metav1.TypeMeta{ + Kind: "Standalone", + APIVersion: "enterprise.splunk.com/v4", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-standalone-2", + Namespace: "test", + UID: "test-uid-2", + }, + } + + // Pre-create KVService with first owner + kvServiceName := GetKVServiceName(standalone.GetNamespace()) + existingKVService := &enterpriseApi.KVService{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvServiceName, + Namespace: standalone.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "enterprise.splunk.com/v4", + Kind: "Standalone", + Name: standalone.GetName(), + UID: standalone.GetUID(), + }, + }, + }, + } + client := fake.NewClientBuilder().WithScheme(sch).WithObjects(existingKVService).Build() + + // Add second owner + err := ApplyKVServiceCR(ctx, client, standalone2) + if err != nil { + t.Errorf("ApplyKVServiceCR should not return error: %v", err) + } + + // Verify both owner references exist + var kvService enterpriseApi.KVService + err = client.Get(ctx, types.NamespacedName{Name: kvServiceName, Namespace: standalone.GetNamespace()}, &kvService) + if err != nil { + t.Errorf("KVService should exist: %v", err) + } + if len(kvService.GetOwnerReferences()) != 2 { + t.Errorf("KVService should have 2 owner references, got %d", len(kvService.GetOwnerReferences())) + } + }) + + t.Run("does not duplicate owner reference", func(t *testing.T) { + // Pre-create KVService with owner already set + kvServiceName := GetKVServiceName(standalone.GetNamespace()) + existingKVService := &enterpriseApi.KVService{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvServiceName, + Namespace: standalone.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "enterprise.splunk.com/v4", + Kind: "Standalone", + Name: standalone.GetName(), + UID: standalone.GetUID(), + }, + }, + }, + } + client := fake.NewClientBuilder().WithScheme(sch).WithObjects(existingKVService).Build() + + // Call again with same owner + err := ApplyKVServiceCR(ctx, client, standalone) + if err != nil { + t.Errorf("ApplyKVServiceCR should not return error: %v", err) + } + + // Verify still only 1 owner reference + var kvService enterpriseApi.KVService + err = client.Get(ctx, types.NamespacedName{Name: kvServiceName, Namespace: standalone.GetNamespace()}, &kvService) + if err != nil { + t.Errorf("KVService should exist: %v", err) + } + if len(kvService.GetOwnerReferences()) != 1 { + t.Errorf("KVService should still have 1 owner reference, got %d", len(kvService.GetOwnerReferences())) + } + }) +} + +func TestDeleteKVServiceCR(t *testing.T) { + ctx := context.TODO() + + // Setup scheme + sch := pkgruntime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(sch)) + utilruntime.Must(enterpriseApi.AddToScheme(sch)) + + standalone := &enterpriseApi.Standalone{ + TypeMeta: metav1.TypeMeta{ + Kind: "Standalone", + APIVersion: "enterprise.splunk.com/v4", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-standalone", + Namespace: "test", + UID: "test-uid-1", + }, + } + + standalone2 := &enterpriseApi.Standalone{ + TypeMeta: metav1.TypeMeta{ + Kind: "Standalone", + APIVersion: "enterprise.splunk.com/v4", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-standalone-2", + Namespace: "test", + UID: "test-uid-2", + }, + } + + t.Run("removes owner reference when multiple owners exist", func(t *testing.T) { + kvServiceName := GetKVServiceName(standalone.GetNamespace()) + existingKVService := &enterpriseApi.KVService{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvServiceName, + Namespace: standalone.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "enterprise.splunk.com/v4", + Kind: "Standalone", + Name: standalone.GetName(), + UID: standalone.GetUID(), + }, + { + APIVersion: "enterprise.splunk.com/v4", + Kind: "Standalone", + Name: standalone2.GetName(), + UID: standalone2.GetUID(), + }, + }, + }, + } + client := fake.NewClientBuilder().WithScheme(sch).WithObjects(existingKVService).Build() + + // Remove first owner + err := DeleteKVServiceCR(ctx, client, standalone) + if err != nil { + t.Errorf("DeleteKVServiceCR should not return error: %v", err) + } + + // Verify KVService still exists with 1 owner + var kvService enterpriseApi.KVService + err = client.Get(ctx, types.NamespacedName{Name: kvServiceName, Namespace: standalone.GetNamespace()}, &kvService) + if err != nil { + t.Errorf("KVService should still exist: %v", err) + } + if len(kvService.GetOwnerReferences()) != 1 { + t.Errorf("KVService should have 1 owner reference, got %d", len(kvService.GetOwnerReferences())) + } + if kvService.GetOwnerReferences()[0].UID != standalone2.GetUID() { + t.Errorf("Remaining owner should be standalone2") + } + }) + + t.Run("deletes KVService when last owner is removed", func(t *testing.T) { + kvServiceName := GetKVServiceName(standalone.GetNamespace()) + existingKVService := &enterpriseApi.KVService{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvServiceName, + Namespace: standalone.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "enterprise.splunk.com/v4", + Kind: "Standalone", + Name: standalone.GetName(), + UID: standalone.GetUID(), + }, + }, + }, + } + client := fake.NewClientBuilder().WithScheme(sch).WithObjects(existingKVService).Build() + + // Remove last owner + err := DeleteKVServiceCR(ctx, client, standalone) + if err != nil { + t.Errorf("DeleteKVServiceCR should not return error: %v", err) + } + + // Verify KVService was deleted + var kvService enterpriseApi.KVService + err = client.Get(ctx, types.NamespacedName{Name: kvServiceName, Namespace: standalone.GetNamespace()}, &kvService) + if err == nil { + t.Errorf("KVService should be deleted") + } + }) + + t.Run("no error when KVService doesn't exist", func(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(sch).Build() + + err := DeleteKVServiceCR(ctx, client, standalone) + if err != nil { + t.Errorf("DeleteKVServiceCR should not return error when KVService doesn't exist: %v", err) + } + }) +} + +func TestGetKVServiceName(t *testing.T) { + name := GetKVServiceName("test-namespace") + expected := "splunk-test-namespace-kvservice" + if name != expected { + t.Errorf("GetKVServiceName returned %s, expected %s", name, expected) + } +} diff --git a/pkg/splunk/test/controller.go b/pkg/splunk/test/controller.go index 6e5871cc4..55d007b86 100644 --- a/pkg/splunk/test/controller.go +++ b/pkg/splunk/test/controller.go @@ -71,6 +71,8 @@ func enterpriseObjCopier(dst, src *client.Object) bool { *dstP.(*enterpriseApi.SearchHeadCluster) = *srcP.(*enterpriseApi.SearchHeadCluster) case *enterpriseApi.MonitoringConsole: *dstP.(*enterpriseApi.MonitoringConsole) = *srcP.(*enterpriseApi.MonitoringConsole) + case *enterpriseApi.KVService: + *dstP.(*enterpriseApi.KVService) = *srcP.(*enterpriseApi.KVService) default: return false }