Skip to content

Commit f338ecc

Browse files
jzywieckialpashko
authored andcommitted
Extract app framework into reusable engine for Standalone architecture (#1778)
* Extract App Framework pipeline into reusable internal engine Signed-off-by: jzywieck <jzywiecki@splunk.com> * Extract app framework pipeline into engine for standalone Signed-off-by: jzywieck <jzywiecki@splunk.com> * Add doc comments to the app engine Signed-off-by: jzywieck <jzywiecki@splunk.com> --------- Signed-off-by: jzywieck <jzywiecki@splunk.com>
1 parent 85b1dcc commit f338ecc

9 files changed

Lines changed: 124 additions & 39 deletions

File tree

api/v4/common_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ const (
4747
RepoStatePassive
4848
)
4949

50+
// ScopeType represent the scope of the App Source
51+
type ScopeType string
52+
5053
// Values to represent the App Source scope
5154
const (
5255
ScopeLocal = "local"

internal/controller/standalone_controller.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func (r *StandaloneReconciler) Reconcile(ctx context.Context, req ctrl.Request)
113113
// Pass event recorder through context
114114
ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder)
115115

116-
result, err := ApplyStandalone(ctx, r.Client, instance)
116+
result, err := ApplyStandalone(ctx, r.Client, instance, enterprise.NewAppFrameworkEngine())
117117
if result.Requeue && result.RequeueAfter != 0 {
118118
reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second))
119119
}
@@ -122,8 +122,8 @@ func (r *StandaloneReconciler) Reconcile(ctx context.Context, req ctrl.Request)
122122
}
123123

124124
// ApplyStandalone adding to handle unit test case
125-
var ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone) (reconcile.Result, error) {
126-
return enterprise.ApplyStandalone(ctx, client, instance)
125+
var ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone, appEngine enterprise.AppEngine) (reconcile.Result, error) {
126+
return enterprise.ApplyStandalone(ctx, client, instance, appEngine)
127127
}
128128

129129
// SetupWithManager sets up the controller with the Manager.

internal/controller/standalone_controller_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"github.com/splunk/splunk-operator/internal/controller/testutils"
7+
enterprise "github.com/splunk/splunk-operator/pkg/splunk/enterprise"
78

89
enterpriseApi "github.com/splunk/splunk-operator/api/v4"
910

@@ -38,7 +39,7 @@ var _ = Describe("Standalone Controller", func() {
3839

3940
It("Get Standalone custom resource should failed", func() {
4041
namespace := "ns-splunk-st-1"
41-
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone) (reconcile.Result, error) {
42+
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone, appEngine enterprise.AppEngine) (reconcile.Result, error) {
4243
return reconcile.Result{}, nil
4344
}
4445
nsSpecs := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
@@ -51,7 +52,7 @@ var _ = Describe("Standalone Controller", func() {
5152

5253
It("Create Standalone custom resource with annotations should pause", func() {
5354
namespace := "ns-splunk-st-2"
54-
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone) (reconcile.Result, error) {
55+
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone, appEngine enterprise.AppEngine) (reconcile.Result, error) {
5556
return reconcile.Result{}, nil
5657
}
5758
nsSpecs := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
@@ -70,7 +71,7 @@ var _ = Describe("Standalone Controller", func() {
7071

7172
It("Create Standalone custom resource should succeeded", func() {
7273
namespace := "ns-splunk-st-3"
73-
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone) (reconcile.Result, error) {
74+
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone, appEngine enterprise.AppEngine) (reconcile.Result, error) {
7475
return reconcile.Result{}, nil
7576
}
7677
nsSpecs := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
@@ -83,7 +84,7 @@ var _ = Describe("Standalone Controller", func() {
8384

8485
It("Cover Unused methods", func() {
8586
namespace := "ns-splunk-st-4"
86-
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone) (reconcile.Result, error) {
87+
ApplyStandalone = func(ctx context.Context, client client.Client, instance *enterpriseApi.Standalone, appEngine enterprise.AppEngine) (reconcile.Result, error) {
8788
return reconcile.Result{}, nil
8889
}
8990
nsSpecs := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}

pkg/splunk/enterprise/appengine.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright (c) 2018-2026 Splunk Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package enterprise provides core Splunk Enterprise reconciliation logic.
18+
package enterprise
19+
20+
import (
21+
"context"
22+
23+
enterpriseApi "github.com/splunk/splunk-operator/api/v4"
24+
splcommon "github.com/splunk/splunk-operator/pkg/splunk/common"
25+
corev1 "k8s.io/api/core/v1"
26+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
27+
)
28+
29+
// AppEngine defines the app framework operations used during reconciliation.
30+
type AppEngine interface {
31+
ensureAppFrameworkStatus(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, appDeployContext *enterpriseApi.AppDeploymentContext, appFrameworkConfig *enterpriseApi.AppFrameworkSpec, scope enterpriseApi.ScopeType) error
32+
ensureAppRepoState(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, appFrameworkConfig *enterpriseApi.AppFrameworkSpec, appDeployContext *enterpriseApi.AppDeploymentContext) error
33+
runAppFrameworkIfReady(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, appDeployContext *enterpriseApi.AppDeploymentContext, appFrameworkConfig *enterpriseApi.AppFrameworkSpec) *reconcile.Result
34+
ensureAppFrameworkStagingVolume(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, podTemplateSpec *corev1.PodTemplateSpec, appFrameworkConfig *enterpriseApi.AppFrameworkSpec)
35+
changeAppSrcDeployInfoStatus(ctx context.Context, appSrc string, appSrcDeployStatus map[string]enterpriseApi.AppSrcDeployInfo, repoState enterpriseApi.AppRepoState, oldDeployStatus enterpriseApi.AppDeploymentStatus, newDeployStatus enterpriseApi.AppDeploymentStatus)
36+
changePhaseInfo(ctx context.Context, desiredReplicas int32, appSrc string, appSrcDeployStatus map[string]enterpriseApi.AppSrcDeployInfo)
37+
removeStaleEntriesFromAuxPhaseInfo(ctx context.Context, desiredReplicas int32, appSrc string, appSrcDeployStatus map[string]enterpriseApi.AppSrcDeployInfo)
38+
}
39+
40+
// appFrameworkEngine is the default AppEngine implementation.
41+
type appFrameworkEngine struct{}
42+
43+
// NewAppFrameworkEngine returns a default AppEngine implementation.
44+
func NewAppFrameworkEngine() AppEngine {
45+
return &appFrameworkEngine{}
46+
}
47+
48+
// ensureAppFrameworkStatus validates and migrates app framework status when needed.
49+
func (e *appFrameworkEngine) ensureAppFrameworkStatus(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, appDeployContext *enterpriseApi.AppDeploymentContext, appFrameworkConfig *enterpriseApi.AppFrameworkSpec, scope enterpriseApi.ScopeType) error {
50+
return checkAndMigrateAppDeployStatus(ctx, client, target, appDeployContext, appFrameworkConfig, scope == enterpriseApi.ScopeLocal)
51+
}
52+
53+
// ensureAppRepoState initializes clients and checks remote app status when app framework is configured.
54+
func (e *appFrameworkEngine) ensureAppRepoState(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, appFrameworkConfig *enterpriseApi.AppFrameworkSpec, appDeployContext *enterpriseApi.AppDeploymentContext) error {
55+
return initAndCheckAppInfoStatus(ctx, client, target, appFrameworkConfig, appDeployContext)
56+
}
57+
58+
// runAppFrameworkIfReady runs the app framework pipeline only when the CR is ready.
59+
func (e *appFrameworkEngine) runAppFrameworkIfReady(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, appDeployContext *enterpriseApi.AppDeploymentContext, appFrameworkConfig *enterpriseApi.AppFrameworkSpec) *reconcile.Result {
60+
return handleAppFrameworkActivity(ctx, client, target, appDeployContext, appFrameworkConfig)
61+
}
62+
63+
// changePhaseInfo updates deployment phase tracking for the given app source.
64+
func (e *appFrameworkEngine) changePhaseInfo(ctx context.Context, desiredReplicas int32, appSrc string, appSrcDeployStatus map[string]enterpriseApi.AppSrcDeployInfo) {
65+
changePhaseInfo(ctx, desiredReplicas, appSrc, appSrcDeployStatus)
66+
}
67+
68+
// removeStaleEntriesFromAuxPhaseInfo trims aux phase tracking entries.
69+
func (e *appFrameworkEngine) removeStaleEntriesFromAuxPhaseInfo(ctx context.Context, desiredReplicas int32, appSrc string, appSrcDeployStatus map[string]enterpriseApi.AppSrcDeployInfo) {
70+
removeStaleEntriesFromAuxPhaseInfo(ctx, desiredReplicas, appSrc, appSrcDeployStatus)
71+
}
72+
73+
// changeAppSrcDeployInfoStatus records a deployment status transition.
74+
func (e *appFrameworkEngine) changeAppSrcDeployInfoStatus(ctx context.Context, appSrc string, appSrcDeployStatus map[string]enterpriseApi.AppSrcDeployInfo, repoState enterpriseApi.AppRepoState, oldDeployStatus enterpriseApi.AppDeploymentStatus, newDeployStatus enterpriseApi.AppDeploymentStatus) {
75+
changeAppSrcDeployInfoStatus(ctx, appSrc, appSrcDeployStatus, repoState, oldDeployStatus, newDeployStatus)
76+
}
77+
78+
// ensureAppFrameworkStagingVolume adds the staging volume for app framework use.
79+
func (e *appFrameworkEngine) ensureAppFrameworkStagingVolume(ctx context.Context, client splcommon.ControllerClient, target splcommon.MetaObject, podTemplateSpec *corev1.PodTemplateSpec, appFrameworkConfig *enterpriseApi.AppFrameworkSpec) {
80+
setupAppsStagingVolume(ctx, client, target, podTemplateSpec, appFrameworkConfig)
81+
}

pkg/splunk/enterprise/configuration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func TestSmartstoreApplyStandaloneFailsOnInvalidSmartStoreConfig(t *testing.T) {
286286

287287
client := spltest.NewMockClient()
288288

289-
_, err := ApplyStandalone(context.Background(), client, &cr)
289+
_, err := ApplyStandalone(context.Background(), client, &cr, NewAppFrameworkEngine())
290290
if err == nil {
291291
t.Errorf("ApplyStandalone should fail on invalid smartstore config")
292292
}

pkg/splunk/enterprise/standalone.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import (
3535
)
3636

3737
// ApplyStandalone reconciles the StatefulSet for N standalone instances of Splunk Enterprise.
38-
func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr *enterpriseApi.Standalone) (reconcile.Result, error) {
38+
func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr *enterpriseApi.Standalone, appEngine AppEngine) (reconcile.Result, error) {
3939

4040
// unless modified, reconcile for this object will be requeued after 5 seconds
4141
result := reconcile.Result{
@@ -72,7 +72,7 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr
7272
cr.Status.Replicas = cr.Spec.Replicas
7373

7474
// If needed, Migrate the app framework status
75-
err = checkAndMigrateAppDeployStatus(ctx, client, cr, &cr.Status.AppContext, &cr.Spec.AppFrameworkConfig, true)
75+
err = appEngine.ensureAppFrameworkStatus(ctx, client, cr, &cr.Status.AppContext, &cr.Spec.AppFrameworkConfig, enterpriseApi.ScopeLocal)
7676
if err != nil {
7777
return result, err
7878
}
@@ -97,9 +97,9 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr
9797
// 1. Initialize the S3Clients based on providers
9898
// 2. Check the status of apps on remote storage.
9999
if len(cr.Spec.AppFrameworkConfig.AppSources) != 0 {
100-
err := initAndCheckAppInfoStatus(ctx, client, cr, &cr.Spec.AppFrameworkConfig, &cr.Status.AppContext)
100+
err := appEngine.ensureAppRepoState(ctx, client, cr, &cr.Spec.AppFrameworkConfig, &cr.Status.AppContext)
101101
if err != nil {
102-
eventPublisher.Warning(ctx, "initAndCheckAppInfoStatus", fmt.Sprintf("init and check app info status failed %s", err.Error()))
102+
eventPublisher.Warning(ctx, "ensureAppRepoState", fmt.Sprintf("ensure app repo state failed %s", err.Error()))
103103
cr.Status.AppContext.IsDeploymentInProgress = false
104104
return result, err
105105
}
@@ -187,22 +187,22 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr
187187
cr.Status.AppContext.IsDeploymentInProgress = true
188188

189189
for appSrc := range appStatusContext.AppsSrcDeployStatus {
190-
changeAppSrcDeployInfoStatus(ctx, appSrc, appStatusContext.AppsSrcDeployStatus, enterpriseApi.RepoStateActive, enterpriseApi.DeployStatusComplete, enterpriseApi.DeployStatusPending)
191-
changePhaseInfo(ctx, cr.Spec.Replicas, appSrc, appStatusContext.AppsSrcDeployStatus)
190+
appEngine.changeAppSrcDeployInfoStatus(ctx, appSrc, appStatusContext.AppsSrcDeployStatus, enterpriseApi.RepoStateActive, enterpriseApi.DeployStatusComplete, enterpriseApi.DeployStatusPending)
191+
appEngine.changePhaseInfo(ctx, cr.Spec.Replicas, appSrc, appStatusContext.AppsSrcDeployStatus)
192192
}
193193

194194
// if we are scaling down, just delete the state auxPhaseInfo entries
195195
case enterpriseApi.StatefulSetScalingDown:
196196
for appSrc := range appStatusContext.AppsSrcDeployStatus {
197-
removeStaleEntriesFromAuxPhaseInfo(ctx, cr.Spec.Replicas, appSrc, appStatusContext.AppsSrcDeployStatus)
197+
appEngine.removeStaleEntriesFromAuxPhaseInfo(ctx, cr.Spec.Replicas, appSrc, appStatusContext.AppsSrcDeployStatus)
198198
}
199199
default:
200200
// nothing to be done
201201
}
202202
}
203203

204204
// create or update statefulset
205-
statefulSet, err := getStandaloneStatefulSet(ctx, client, cr)
205+
statefulSet, err := getStandaloneStatefulSet(ctx, client, cr, appEngine)
206206
if err != nil {
207207
eventPublisher.Warning(ctx, "getStandaloneStatefulSet", fmt.Sprintf("get standalone status set failed %s", err.Error()))
208208
return result, err
@@ -262,7 +262,7 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr
262262
scopedLog.Error(err, "Error in deleting automated monitoring console resource")
263263
}
264264

265-
finalResult := handleAppFrameworkActivity(ctx, client, cr, &cr.Status.AppContext, &cr.Spec.AppFrameworkConfig)
265+
finalResult := appEngine.runAppFrameworkIfReady(ctx, client, cr, &cr.Status.AppContext, &cr.Spec.AppFrameworkConfig)
266266
result = *finalResult
267267

268268
// Add a splunk operator telemetry app
@@ -287,7 +287,7 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr
287287
}
288288

289289
// getStandaloneStatefulSet returns a Kubernetes StatefulSet object for Splunk Enterprise standalone instances.
290-
func getStandaloneStatefulSet(ctx context.Context, client splcommon.ControllerClient, cr *enterpriseApi.Standalone) (*appsv1.StatefulSet, error) {
290+
func getStandaloneStatefulSet(ctx context.Context, client splcommon.ControllerClient, cr *enterpriseApi.Standalone, appEngine AppEngine) (*appsv1.StatefulSet, error) {
291291
// get generic statefulset for Splunk Enterprise objects
292292
ss, err := getSplunkStatefulSet(ctx, client, cr, &cr.Spec.CommonSplunkSpec, SplunkStandalone, cr.Spec.Replicas, []corev1.EnvVar{})
293293
if err != nil {
@@ -301,7 +301,7 @@ func getStandaloneStatefulSet(ctx context.Context, client splcommon.ControllerCl
301301
}
302302

303303
// Setup App framework staging volume for apps
304-
setupAppsStagingVolume(ctx, client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig)
304+
appEngine.ensureAppFrameworkStagingVolume(ctx, client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig)
305305

306306
return ss, nil
307307
}

0 commit comments

Comments
 (0)