Skip to content

Commit b45cb9b

Browse files
committed
create instance manager at runtime
1 parent 96a70f2 commit b45cb9b

File tree

6 files changed

+190
-20
lines changed

6 files changed

+190
-20
lines changed

backend/app/instance_provider.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import (
1313
// InstanceFactoryFunc factory method for creating app instances.
1414
type InstanceFactoryFunc func(ctx context.Context, settings backend.AppInstanceSettings) (instancemgmt.Instance, error)
1515

16-
// NewInstanceManager creates a new app instance manager,
16+
// NewInstanceManager creates a new app instance manager.
1717
//
1818
// This is a helper method for calling NewInstanceProvider and creating a new instancemgmt.InstanceProvider,
19-
// and providing that to instancemgmt.New.
19+
// and providing that to instancemgmt.NewContextAwareInstanceManager.
2020
func NewInstanceManager(fn InstanceFactoryFunc) instancemgmt.InstanceManager {
2121
ip := NewInstanceProvider(fn)
22-
return instancemgmt.New(ip)
22+
return instancemgmt.NewContextAwareInstanceManager(ip)
2323
}
2424

2525
// NewInstanceProvider create a new app instance provider,

backend/datasource/manage.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"os"
77

88
"github.com/grafana/grafana-plugin-sdk-go/backend"
9-
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
109
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
1110
"github.com/grafana/grafana-plugin-sdk-go/internal/automanagement"
1211
"github.com/grafana/grafana-plugin-sdk-go/internal/buildinfo"
@@ -28,11 +27,6 @@ type ManageOpts struct {
2827

2928
// Stateless query conversion handler
3029
QueryConversionHandler backend.QueryConversionHandler
31-
32-
// UseTTLInstanceManager controls whether to use the TTL-based instance manager.
33-
// When true, instances will be automatically evicted from the cache after a configurable TTL.
34-
// Default is false, which uses the standard instance manager.
35-
UseTTLInstanceManager bool
3630
}
3731

3832
// Manage starts serving the data source over gPRC with automatic instance management.
@@ -53,16 +47,7 @@ func Manage(pluginID string, instanceFactory InstanceFactoryFunc, opts ManageOpt
5347
return fmt.Errorf("setup tracer: %w", err)
5448
}
5549

56-
ip := NewInstanceProvider(instanceFactory)
57-
58-
var im instancemgmt.InstanceManager
59-
if opts.UseTTLInstanceManager {
60-
im = instancemgmt.NewTTLInstanceManager(ip)
61-
} else {
62-
im = instancemgmt.New(ip)
63-
}
64-
65-
handler := automanagement.NewManager(im)
50+
handler := automanagement.NewManager(NewInstanceManager(instanceFactory))
6651
return backend.Manage(pluginID, backend.ServeOpts{
6752
CheckHealthHandler: handler,
6853
CallResourceHandler: handler,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package instancemgmt
2+
3+
import (
4+
"context"
5+
6+
"github.com/grafana/grafana-plugin-sdk-go/backend"
7+
"github.com/grafana/grafana-plugin-sdk-go/experimental/featuretoggles"
8+
)
9+
10+
// NewContextAwareInstanceManager creates a new instance manager that dynamically selects
11+
// between standard and TTL instance managers based on feature toggles from the Grafana config.
12+
func NewContextAwareInstanceManager(provider InstanceProvider) InstanceManager {
13+
return &contextAwareInstanceManager{
14+
provider: provider,
15+
standardManager: New(provider),
16+
ttlManager: NewTTLInstanceManager(provider),
17+
}
18+
}
19+
20+
// contextAwareInstanceManager is a wrapper that dynamically selects the appropriate
21+
// instance manager implementation based on feature toggles in the context.
22+
type contextAwareInstanceManager struct {
23+
provider InstanceProvider
24+
standardManager InstanceManager
25+
ttlManager InstanceManager
26+
}
27+
28+
// selectManager returns the appropriate instance manager based on the feature toggle
29+
// from the Grafana config in the plugin context.
30+
func (c *contextAwareInstanceManager) selectManager(_ context.Context, pluginContext backend.PluginContext) InstanceManager {
31+
// Check if TTL instance manager feature toggle is enabled
32+
if pluginContext.GrafanaConfig != nil {
33+
featureToggles := pluginContext.GrafanaConfig.FeatureToggles()
34+
if featureToggles.IsEnabled(featuretoggles.TTLInstanceManager) {
35+
return c.ttlManager
36+
}
37+
}
38+
39+
// Default to standard instance manager
40+
return c.standardManager
41+
}
42+
43+
// Get returns an Instance using the appropriate manager based on feature toggles.
44+
func (c *contextAwareInstanceManager) Get(ctx context.Context, pluginContext backend.PluginContext) (Instance, error) {
45+
manager := c.selectManager(ctx, pluginContext)
46+
return manager.Get(ctx, pluginContext)
47+
}
48+
49+
// Do provides an Instance as argument to fn using the appropriate manager based on feature toggles.
50+
func (c *contextAwareInstanceManager) Do(ctx context.Context, pluginContext backend.PluginContext, fn InstanceCallbackFunc) error {
51+
manager := c.selectManager(ctx, pluginContext)
52+
return manager.Do(ctx, pluginContext, fn)
53+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package instancemgmt
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/grafana/grafana-plugin-sdk-go/backend"
11+
"github.com/grafana/grafana-plugin-sdk-go/experimental/featuretoggles"
12+
)
13+
14+
func TestContextAwareInstanceManager(t *testing.T) {
15+
ctx := context.Background()
16+
tip := &testInstanceProvider{}
17+
im := NewContextAwareInstanceManager(tip)
18+
19+
t.Run("Should use standard manager when feature toggle is disabled", func(t *testing.T) {
20+
pCtx := backend.PluginContext{
21+
OrgID: 1,
22+
AppInstanceSettings: &backend.AppInstanceSettings{
23+
Updated: time.Now(),
24+
},
25+
GrafanaConfig: backend.NewGrafanaCfg(map[string]string{
26+
featuretoggles.EnabledFeatures: "",
27+
}),
28+
}
29+
30+
manager := im.(*contextAwareInstanceManager).selectManager(ctx, pCtx)
31+
require.IsType(t, &instanceManager{}, manager)
32+
})
33+
34+
t.Run("Should use TTL manager when feature toggle is enabled", func(t *testing.T) {
35+
pCtx := backend.PluginContext{
36+
OrgID: 1,
37+
AppInstanceSettings: &backend.AppInstanceSettings{
38+
Updated: time.Now(),
39+
},
40+
GrafanaConfig: backend.NewGrafanaCfg(map[string]string{
41+
featuretoggles.EnabledFeatures: featuretoggles.TTLInstanceManager,
42+
}),
43+
}
44+
45+
manager := im.(*contextAwareInstanceManager).selectManager(ctx, pCtx)
46+
require.IsType(t, &instanceManagerWithTTL{}, manager)
47+
})
48+
49+
t.Run("Should use standard manager when GrafanaConfig is nil", func(t *testing.T) {
50+
pCtx := backend.PluginContext{
51+
OrgID: 1,
52+
AppInstanceSettings: &backend.AppInstanceSettings{
53+
Updated: time.Now(),
54+
},
55+
GrafanaConfig: nil,
56+
}
57+
58+
manager := im.(*contextAwareInstanceManager).selectManager(ctx, pCtx)
59+
require.IsType(t, &instanceManager{}, manager)
60+
})
61+
62+
t.Run("Should use TTL manager when feature toggle is enabled with other flags", func(t *testing.T) {
63+
pCtx := backend.PluginContext{
64+
OrgID: 1,
65+
AppInstanceSettings: &backend.AppInstanceSettings{
66+
Updated: time.Now(),
67+
},
68+
GrafanaConfig: backend.NewGrafanaCfg(map[string]string{
69+
featuretoggles.EnabledFeatures: "someOtherFlag," + featuretoggles.TTLInstanceManager + ",anotherFlag",
70+
}),
71+
}
72+
73+
manager := im.(*contextAwareInstanceManager).selectManager(ctx, pCtx)
74+
require.IsType(t, &instanceManagerWithTTL{}, manager)
75+
})
76+
77+
t.Run("Should delegate Get calls correctly", func(t *testing.T) {
78+
// Test with TTL manager enabled
79+
pCtx := backend.PluginContext{
80+
OrgID: 1,
81+
AppInstanceSettings: &backend.AppInstanceSettings{
82+
Updated: time.Now(),
83+
},
84+
GrafanaConfig: backend.NewGrafanaCfg(map[string]string{
85+
featuretoggles.EnabledFeatures: featuretoggles.TTLInstanceManager,
86+
}),
87+
}
88+
89+
instance, err := im.Get(ctx, pCtx)
90+
require.NoError(t, err)
91+
require.NotNil(t, instance)
92+
require.Equal(t, pCtx.OrgID, instance.(*testInstance).orgID)
93+
})
94+
95+
t.Run("Should delegate Do calls correctly", func(t *testing.T) {
96+
// Test with standard manager (no feature toggle)
97+
pCtx := backend.PluginContext{
98+
OrgID: 2,
99+
AppInstanceSettings: &backend.AppInstanceSettings{
100+
Updated: time.Now(),
101+
},
102+
GrafanaConfig: backend.NewGrafanaCfg(map[string]string{
103+
featuretoggles.EnabledFeatures: "",
104+
}),
105+
}
106+
107+
var receivedInstance *testInstance
108+
err := im.Do(ctx, pCtx, func(instance *testInstance) {
109+
receivedInstance = instance
110+
})
111+
require.NoError(t, err)
112+
require.NotNil(t, receivedInstance)
113+
require.Equal(t, pCtx.OrgID, receivedInstance.orgID)
114+
})
115+
}

backend/instancemgmt/doc.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,19 @@
11
// Package instancemgmt provides utilities for managing plugin instances.
2+
//
3+
// This package offers several instance manager implementations:
4+
//
5+
// 1. Standard Instance Manager (New): Uses sync.Map for caching instances
6+
// and disposes them when they need updates.
7+
//
8+
// 2. TTL Instance Manager (NewTTLInstanceManager): Uses TTL-based caching
9+
// that automatically evicts instances after a configurable time period.
10+
//
11+
// 3. Context-Aware Instance Manager (NewContextAwareInstanceManager):
12+
// Dynamically selects between standard and TTL managers based on
13+
// feature toggles from the Grafana config in the context.
14+
//
15+
// The context-aware manager checks the "ttlInstanceManager" feature toggle
16+
// from the Grafana configuration and automatically uses the appropriate
17+
// underlying implementation. This allows runtime switching without requiring
18+
// plugin restarts or static configuration.
219
package instancemgmt

experimental/featuretoggles/featuretoggles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const (
1010

1111
// TTLInstanceManager is a feature toggle for enabling the TTL-based instance manager.
1212
// When enabled, instances will be automatically evicted from the cache after a configurable TTL.
13-
TTLInstanceManager = "ttlInstanceManager"
13+
TTLInstanceManager = "ttlPluginInstanceManager"
1414
)
1515

1616
// FeatureToggles can check if feature toggles are enabled on the Grafana instance.

0 commit comments

Comments
 (0)