Skip to content

Commit 2b4a9fb

Browse files
authored
Use tenant ID from incoming gRPC meta for instance caching (#676)
* use tenant id from gRPC meta as instance key * fix linter * add ctc propagation tests * add bigger test * move to diff package * make util * rename
1 parent e600743 commit 2b4a9fb

19 files changed

+606
-68
lines changed

backend/app/instance_provider.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package app
22

33
import (
4+
"context"
45
"fmt"
56

67
"github.com/grafana/grafana-plugin-sdk-go/backend"
78
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
9+
"github.com/grafana/grafana-plugin-sdk-go/backend/tenant"
810
)
911

1012
// InstanceFactoryFunc factory method for creating app instances.
@@ -40,23 +42,28 @@ type instanceProvider struct {
4042
factory InstanceFactoryFunc
4143
}
4244

43-
func (ip *instanceProvider) GetKey(pluginContext backend.PluginContext) (interface{}, error) {
45+
func (ip *instanceProvider) GetKey(ctx context.Context, pluginContext backend.PluginContext) (interface{}, error) {
4446
if pluginContext.AppInstanceSettings == nil {
4547
// fail fast if there is no app settings
4648
return nil, fmt.Errorf("app instance settings cannot be nil")
4749
}
4850

4951
// The instance key generated for app plugins should include both plugin ID, and the OrgID, since for a single
5052
// Grafana instance there might be different orgs using the same plugin.
51-
return fmt.Sprintf("%s#%v", pluginContext.PluginID, pluginContext.OrgID), nil
53+
defaultKey := fmt.Sprintf("%s#%v", pluginContext.PluginID, pluginContext.OrgID)
54+
if tID := tenant.IDFromContext(ctx); tID != "" {
55+
return fmt.Sprintf("%s#%s", tID, defaultKey), nil
56+
}
57+
58+
return defaultKey, nil
5259
}
5360

54-
func (ip *instanceProvider) NeedsUpdate(pluginContext backend.PluginContext, cachedInstance instancemgmt.CachedInstance) bool {
61+
func (ip *instanceProvider) NeedsUpdate(_ context.Context, pluginContext backend.PluginContext, cachedInstance instancemgmt.CachedInstance) bool {
5562
curSettings := pluginContext.AppInstanceSettings
5663
cachedSettings := cachedInstance.PluginContext.AppInstanceSettings
5764
return !curSettings.Updated.Equal(cachedSettings.Updated)
5865
}
5966

60-
func (ip *instanceProvider) NewInstance(pluginContext backend.PluginContext) (instancemgmt.Instance, error) {
67+
func (ip *instanceProvider) NewInstance(_ context.Context, pluginContext backend.PluginContext) (instancemgmt.Instance, error) {
6168
return ip.factory(*pluginContext.AppInstanceSettings)
6269
}

backend/app/instance_provider_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package app
22

33
import (
4+
"context"
45
"testing"
56
"time"
67

@@ -23,12 +24,12 @@ func TestInstanceProvider(t *testing.T) {
2324
})
2425

2526
t.Run("When data source instance settings not provided should return error", func(t *testing.T) {
26-
_, err := ip.GetKey(backend.PluginContext{})
27+
_, err := ip.GetKey(context.Background(), backend.PluginContext{})
2728
require.Error(t, err)
2829
})
2930

3031
t.Run("When app instance settings provided should return expected key", func(t *testing.T) {
31-
key, err := ip.GetKey(backend.PluginContext{
32+
key, err := ip.GetKey(context.Background(), backend.PluginContext{
3233
PluginID: testAppPluginID,
3334
OrgID: testOrgID,
3435
AppInstanceSettings: &backend.AppInstanceSettings{},
@@ -50,7 +51,7 @@ func TestInstanceProvider(t *testing.T) {
5051
},
5152
},
5253
}
53-
needsUpdate := ip.NeedsUpdate(curSettings, cachedInstance)
54+
needsUpdate := ip.NeedsUpdate(context.Background(), curSettings, cachedInstance)
5455
require.False(t, needsUpdate)
5556
})
5657

@@ -67,12 +68,12 @@ func TestInstanceProvider(t *testing.T) {
6768
},
6869
},
6970
}
70-
needsUpdate := ip.NeedsUpdate(curSettings, cachedInstance)
71+
needsUpdate := ip.NeedsUpdate(context.Background(), curSettings, cachedInstance)
7172
require.True(t, needsUpdate)
7273
})
7374

7475
t.Run("When creating a new instance should return expected instance", func(t *testing.T) {
75-
i, err := ip.NewInstance(backend.PluginContext{
76+
i, err := ip.NewInstance(context.Background(), backend.PluginContext{
7677
PluginID: testAppPluginID,
7778
OrgID: testOrgID,
7879
AppInstanceSettings: &backend.AppInstanceSettings{},

backend/common.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package backend
22

33
import (
4+
"context"
45
"encoding/json"
56
"time"
67

78
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
9+
"github.com/grafana/grafana-plugin-sdk-go/backend/tenant"
810
)
911

1012
const dataCustomOptionsKey = "grafanaData"
@@ -205,3 +207,10 @@ func SecureJSONDataFromHTTPClientOptions(opts httpclient.Options) (res map[strin
205207

206208
return secureJSONData
207209
}
210+
211+
func propagateTenantIDIfPresent(ctx context.Context) context.Context {
212+
if tid, exists := tenant.IDFromIncomingGRPCContext(ctx); exists {
213+
ctx = tenant.WithTenant(ctx, tid)
214+
}
215+
return ctx
216+
}

backend/data_adapter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func withHeaderMiddleware(ctx context.Context, headers http.Header) context.Cont
4444
}
4545

4646
func (a *dataSDKAdapter) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest) (*pluginv2.QueryDataResponse, error) {
47+
ctx = propagateTenantIDIfPresent(ctx)
4748
parsedRequest := FromProto().QueryDataRequest(req)
4849
ctx = withHeaderMiddleware(ctx, parsedRequest.GetHTTPHeaders())
4950
resp, err := a.queryDataHandler.QueryData(ctx, parsedRequest)

backend/data_adapter_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import (
99
"net/http/httptest"
1010
"testing"
1111

12+
"github.com/stretchr/testify/require"
13+
"google.golang.org/grpc/metadata"
14+
1215
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
16+
"github.com/grafana/grafana-plugin-sdk-go/backend/tenant"
1317
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
14-
"github.com/stretchr/testify/require"
1518
)
1619

1720
type fakeDataHandlerWithOAuth struct {
@@ -111,6 +114,22 @@ func TestQueryData(t *testing.T) {
111114
require.NoError(t, res.Body.Close())
112115
require.Empty(t, reqClone.Header)
113116
})
117+
118+
t.Run("When tenant information is attached to incoming context, it is propagated from adapter to handler", func(t *testing.T) {
119+
tid := "123456"
120+
a := newDataSDKAdapter(QueryDataHandlerFunc(func(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) {
121+
require.Equal(t, tid, tenant.IDFromContext(ctx))
122+
return NewQueryDataResponse(), nil
123+
}))
124+
125+
ctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
126+
tenant.CtxKey: tid,
127+
}))
128+
_, err := a.QueryData(ctx, &pluginv2.QueryDataRequest{
129+
PluginContext: &pluginv2.PluginContext{},
130+
})
131+
require.NoError(t, err)
132+
})
114133
}
115134

116135
var finalRoundTripper = httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {

backend/datasource/instance_provider.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package datasource
22

33
import (
4+
"context"
45
"fmt"
56

67
"github.com/grafana/grafana-plugin-sdk-go/backend"
78
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
9+
"github.com/grafana/grafana-plugin-sdk-go/backend/tenant"
810
)
911

1012
// InstanceFactoryFunc factory method for creating data source instances.
@@ -40,20 +42,25 @@ type instanceProvider struct {
4042
factory InstanceFactoryFunc
4143
}
4244

43-
func (ip *instanceProvider) GetKey(pluginContext backend.PluginContext) (interface{}, error) {
45+
func (ip *instanceProvider) GetKey(ctx context.Context, pluginContext backend.PluginContext) (interface{}, error) {
4446
if pluginContext.DataSourceInstanceSettings == nil {
4547
return nil, fmt.Errorf("data source instance settings cannot be nil")
4648
}
4749

48-
return pluginContext.DataSourceInstanceSettings.ID, nil
50+
defaultKey := pluginContext.DataSourceInstanceSettings.ID
51+
if tID := tenant.IDFromContext(ctx); tID != "" {
52+
return fmt.Sprintf("%s#%v", tID, defaultKey), nil
53+
}
54+
55+
return defaultKey, nil
4956
}
5057

51-
func (ip *instanceProvider) NeedsUpdate(pluginContext backend.PluginContext, cachedInstance instancemgmt.CachedInstance) bool {
58+
func (ip *instanceProvider) NeedsUpdate(_ context.Context, pluginContext backend.PluginContext, cachedInstance instancemgmt.CachedInstance) bool {
5259
curSettings := pluginContext.DataSourceInstanceSettings
5360
cachedSettings := cachedInstance.PluginContext.DataSourceInstanceSettings
5461
return !curSettings.Updated.Equal(cachedSettings.Updated)
5562
}
5663

57-
func (ip *instanceProvider) NewInstance(pluginContext backend.PluginContext) (instancemgmt.Instance, error) {
64+
func (ip *instanceProvider) NewInstance(_ context.Context, pluginContext backend.PluginContext) (instancemgmt.Instance, error) {
5865
return ip.factory(*pluginContext.DataSourceInstanceSettings)
5966
}

backend/datasource/instance_provider_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package datasource
22

33
import (
4+
"context"
45
"testing"
56
"time"
67

@@ -18,12 +19,12 @@ func TestInstanceProvider(t *testing.T) {
1819
})
1920

2021
t.Run("When data source instance settings not provided should return error", func(t *testing.T) {
21-
_, err := ip.GetKey(backend.PluginContext{})
22+
_, err := ip.GetKey(context.Background(), backend.PluginContext{})
2223
require.Error(t, err)
2324
})
2425

2526
t.Run("When data source instance settings provided should return expected key", func(t *testing.T) {
26-
key, err := ip.GetKey(backend.PluginContext{
27+
key, err := ip.GetKey(context.Background(), backend.PluginContext{
2728
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{
2829
ID: 4,
2930
},
@@ -45,7 +46,7 @@ func TestInstanceProvider(t *testing.T) {
4546
},
4647
},
4748
}
48-
needsUpdate := ip.NeedsUpdate(curSettings, cachedInstance)
49+
needsUpdate := ip.NeedsUpdate(context.Background(), curSettings, cachedInstance)
4950
require.False(t, needsUpdate)
5051
})
5152

@@ -62,12 +63,12 @@ func TestInstanceProvider(t *testing.T) {
6263
},
6364
},
6465
}
65-
needsUpdate := ip.NeedsUpdate(curSettings, cachedInstance)
66+
needsUpdate := ip.NeedsUpdate(context.Background(), curSettings, cachedInstance)
6667
require.True(t, needsUpdate)
6768
})
6869

6970
t.Run("When creating a new instance should return expected instance", func(t *testing.T) {
70-
i, err := ip.NewInstance(backend.PluginContext{
71+
i, err := ip.NewInstance(context.Background(), backend.PluginContext{
7172
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{},
7273
})
7374
require.NoError(t, err)

backend/datasource/serve_example_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ func newDataSource() datasource.ServeOpts {
5656
}
5757
}
5858

59-
func (ds *testDataSource) getSettings(pluginContext backend.PluginContext) (*testDataSourceInstanceSettings, error) {
60-
iface, err := ds.im.Get(pluginContext)
59+
func (ds *testDataSource) getSettings(ctx context.Context, pluginContext backend.PluginContext) (*testDataSourceInstanceSettings, error) {
60+
iface, err := ds.im.Get(ctx, pluginContext)
6161
if err != nil {
6262
return nil, err
6363
}
@@ -66,7 +66,7 @@ func (ds *testDataSource) getSettings(pluginContext backend.PluginContext) (*tes
6666
}
6767

6868
func (ds *testDataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
69-
settings, err := ds.getSettings(req.PluginContext)
69+
settings, err := ds.getSettings(ctx, req.PluginContext)
7070
if err != nil {
7171
return nil, err
7272
}
@@ -82,7 +82,7 @@ func (ds *testDataSource) CheckHealth(ctx context.Context, req *backend.CheckHea
8282

8383
func (ds *testDataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
8484
var resp *backend.QueryDataResponse
85-
err := ds.im.Do(req.PluginContext, func(settings *testDataSourceInstanceSettings) error {
85+
err := ds.im.Do(ctx, req.PluginContext, func(settings *testDataSourceInstanceSettings) error {
8686
// Handle request
8787
resp, err := settings.httpClient.Get("http://")
8888
if err != nil {
@@ -96,8 +96,9 @@ func (ds *testDataSource) QueryData(ctx context.Context, req *backend.QueryDataR
9696
}
9797

9898
func (ds *testDataSource) handleTest(rw http.ResponseWriter, req *http.Request) {
99-
pluginContext := httpadapter.PluginConfigFromContext(req.Context())
100-
settings, err := ds.getSettings(pluginContext)
99+
ctx := req.Context()
100+
pluginContext := httpadapter.PluginConfigFromContext(ctx)
101+
settings, err := ds.getSettings(ctx, pluginContext)
101102
if err != nil {
102103
rw.WriteHeader(500)
103104
return

backend/diagnostics_adapter.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"bytes"
55
"context"
66

7-
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
87
"github.com/prometheus/client_golang/prometheus"
98
"github.com/prometheus/common/expfmt"
9+
10+
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
1011
)
1112

1213
// diagnosticsSDKAdapter adapter between low level plugin protocol and SDK interfaces.
@@ -45,6 +46,7 @@ func (a *diagnosticsSDKAdapter) CollectMetrics(_ context.Context, _ *pluginv2.Co
4546

4647
func (a *diagnosticsSDKAdapter) CheckHealth(ctx context.Context, protoReq *pluginv2.CheckHealthRequest) (*pluginv2.CheckHealthResponse, error) {
4748
if a.checkHealthHandler != nil {
49+
ctx = propagateTenantIDIfPresent(ctx)
4850
parsedReq := FromProto().CheckHealthRequest(protoReq)
4951
ctx = withHeaderMiddleware(ctx, parsedReq.GetHTTPHeaders())
5052
res, err := a.checkHealthHandler.CheckHealth(ctx, parsedReq)

backend/diagnostics_adapter_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import (
66
"errors"
77
"testing"
88

9-
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
10-
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
119
"github.com/prometheus/client_golang/prometheus"
1210
"github.com/prometheus/common/expfmt"
1311
"github.com/stretchr/testify/require"
12+
"google.golang.org/grpc/metadata"
13+
14+
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
15+
"github.com/grafana/grafana-plugin-sdk-go/backend/tenant"
16+
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
1417
)
1518

1619
func TestCollectMetrcis(t *testing.T) {
@@ -122,6 +125,22 @@ func TestCheckHealth(t *testing.T) {
122125
require.NotNil(t, res)
123126
require.Equal(t, pluginv2.CheckHealthResponse_OK, res.Status)
124127
})
128+
129+
t.Run("When tenant information is attached to incoming context, it is propagated from adapter to handler", func(t *testing.T) {
130+
tid := "123456"
131+
a := newDiagnosticsSDKAdapter(nil, CheckHealthHandlerFunc(func(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) {
132+
require.Equal(t, tid, tenant.IDFromContext(ctx))
133+
return &CheckHealthResult{}, nil
134+
}))
135+
136+
ctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
137+
tenant.CtxKey: tid,
138+
}))
139+
_, err := a.CheckHealth(ctx, &pluginv2.CheckHealthRequest{
140+
PluginContext: &pluginv2.PluginContext{},
141+
})
142+
require.NoError(t, err)
143+
})
125144
}
126145

127146
type testCheckHealthHandler struct {

0 commit comments

Comments
 (0)