Skip to content

Commit 36f4285

Browse files
authored
Storage: Read desired mode from config instead of feature flags (grafana#88353)
* Read desired mode from config * Update playlist integration tests * Add mode 1 playlist integration tests * Add mode 0 dual writing to playlist integration tests * Add documentation for the different dual writing modes
1 parent d3fa52a commit 36f4285

File tree

25 files changed

+301
-73
lines changed

25 files changed

+301
-73
lines changed

.golangci.toml

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ allow = [
7878
"github.com/grafana/grafana/pkg/services/apiserver/utils",
7979
"github.com/grafana/grafana/pkg/services/featuremgmt",
8080
"github.com/grafana/grafana/pkg/infra/kvstore",
81+
"github.com/grafana/grafana/pkg/services/apiserver/options",
82+
"github.com/grafana/grafana/pkg/apis/playlist/v0alpha1",
8183
]
8284
deny = [
8385
{ pkg = "github.com/grafana/grafana/pkg", desc = "apiserver is not allowed to import grafana core" }

pkg/apis/playlist/v0alpha1/register.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import (
88
)
99

1010
const (
11-
GROUP = "playlist.grafana.app"
12-
VERSION = "v0alpha1"
13-
APIVERSION = GROUP + "/" + VERSION
11+
GROUP = "playlist.grafana.app"
12+
VERSION = "v0alpha1"
13+
APIVERSION = GROUP + "/" + VERSION
14+
RESOURCE = "playlists"
15+
GROUPRESOURCE = GROUP + "/" + RESOURCE
1416
)
1517

1618
var PlaylistResourceInfo = common.NewResourceInfo(GROUP, VERSION,
17-
"playlists", "playlist", "Playlist",
19+
RESOURCE, "playlist", "Playlist",
1820
func() runtime.Object { return &Playlist{} },
1921
func() runtime.Object { return &PlaylistList{} },
2022
)

pkg/apiserver/builder/common.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package builder
33
import (
44
"net/http"
55

6+
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
67
"k8s.io/apimachinery/pkg/runtime"
78
"k8s.io/apimachinery/pkg/runtime/schema"
89
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -27,7 +28,7 @@ type APIGroupBuilder interface {
2728
scheme *runtime.Scheme,
2829
codecs serializer.CodecFactory,
2930
optsGetter generic.RESTOptionsGetter,
30-
dualWrite bool,
31+
desiredMode grafanarest.DualWriterMode,
3132
) (*genericapiserver.APIGroupInfo, error)
3233

3334
// Get OpenAPI definitions
@@ -40,6 +41,11 @@ type APIGroupBuilder interface {
4041
// Standard namespace checking will happen before this is called, specifically
4142
// the namespace must matches an org|stack that the user belongs to
4243
GetAuthorizer() authorizer.Authorizer
44+
45+
// Get the desired dual writing mode. These are modes 1, 2, 3 and 4 if
46+
// the feature flag `unifiedStorage` is enabled and mode 0 if it is not enabled.
47+
// #TODO add type for map[string]grafanarest.DualWriterMode?
48+
GetDesiredDualWriterMode(dualWrite bool, toMode map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode
4349
}
4450

4551
// Builders that implement OpenAPIPostProcessor are given a chance to modify the schema directly

pkg/apiserver/builder/helper.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"k8s.io/kube-openapi/pkg/common"
2525

2626
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
27+
"github.com/grafana/grafana/pkg/services/apiserver/options"
2728
)
2829

2930
// TODO: this is a temporary hack to make rest.Connecter work with resource level routes
@@ -126,10 +127,16 @@ func InstallAPIs(
126127
server *genericapiserver.GenericAPIServer,
127128
optsGetter generic.RESTOptionsGetter,
128129
builders []APIGroupBuilder,
129-
dualWrite bool,
130+
storageOpts *options.StorageOptions,
130131
) error {
132+
// dual writing is only enabled when the storage type is not legacy.
133+
// this is needed to support setting a default RESTOptionsGetter for new APIs that don't
134+
// support the legacy storage type.
135+
dualWriteEnabled := storageOpts.StorageType != options.StorageTypeLegacy
136+
131137
for _, b := range builders {
132-
g, err := b.GetAPIGroupInfo(scheme, codecs, optsGetter, dualWrite)
138+
mode := b.GetDesiredDualWriterMode(dualWriteEnabled, storageOpts.DualWriterDesiredModes)
139+
g, err := b.GetAPIGroupInfo(scheme, codecs, optsGetter, mode)
133140
if err != nil {
134141
return err
135142
}

pkg/apiserver/rest/dualwriter.go

+26-16
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77

88
"github.com/grafana/grafana/pkg/infra/kvstore"
9-
"github.com/grafana/grafana/pkg/services/featuremgmt"
109
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1110
"k8s.io/apimachinery/pkg/runtime"
1211
"k8s.io/apiserver/pkg/registry/rest"
@@ -82,15 +81,25 @@ type DualWriter interface {
8281
type DualWriterMode int
8382

8483
const (
85-
Mode1 DualWriterMode = iota + 1
84+
// Mode0 represents writing to and reading from solely LegacyStorage. This mode is enabled when the
85+
// `unifiedStorage` feature flag is not set. All reads and writes are made to LegacyStorage. None are made to Storage.
86+
Mode0 DualWriterMode = iota
87+
// Mode1 represents writing to and reading from LegacyStorage for all primary functionality while additionally
88+
// reading and writing to Storage on a best effort basis for the sake of collecting metrics.
89+
Mode1
90+
// Mode2 is the dual writing mode that represents writing to LegacyStorage and Storage and reading from LegacyStorage.
8691
Mode2
92+
// Mode3 represents writing to LegacyStorage and Storage and reading from Storage.
8793
Mode3
94+
// Mode4 represents writing and reading from Storage.
8895
Mode4
8996
)
9097

9198
// NewDualWriter returns a new DualWriter.
9299
func NewDualWriter(mode DualWriterMode, legacy LegacyStorage, storage Storage) DualWriter {
93100
switch mode {
101+
// It is not possible to initialize a mode 0 dual writer. Mode 0 represents
102+
// writing to legacy storage without `unifiedStorage` enabled.
94103
case Mode1:
95104
// read and write only from legacy storage
96105
return newDualWriterMode1(legacy, storage)
@@ -129,12 +138,14 @@ func (u *updateWrapper) UpdatedObject(ctx context.Context, oldObj runtime.Object
129138
func SetDualWritingMode(
130139
ctx context.Context,
131140
kvs *kvstore.NamespacedKVStore,
132-
features featuremgmt.FeatureToggles,
133-
entity string,
134141
legacy LegacyStorage,
135142
storage Storage,
143+
entity string,
144+
desiredMode DualWriterMode,
136145
) (DualWriter, error) {
137146
toMode := map[string]DualWriterMode{
147+
// It is not possible to initialize a mode 0 dual writer. Mode 0 represents
148+
// writing to legacy storage without `unifiedStorage` enabled.
138149
"1": Mode1,
139150
"2": Mode2,
140151
"3": Mode3,
@@ -166,7 +177,7 @@ func SetDualWritingMode(
166177
}
167178

168179
// Desired mode is 2 and current mode is 1
169-
if features.IsEnabledGlobally(featuremgmt.FlagDualWritePlaylistsMode2) && (currentMode == Mode1) {
180+
if (desiredMode == Mode2) && (currentMode == Mode1) {
170181
// This is where we go through the different gates to allow the instance to migrate from mode 1 to mode 2.
171182
// There are none between mode 1 and mode 2
172183
currentMode = Mode2
@@ -176,17 +187,16 @@ func SetDualWritingMode(
176187
return nil, errDualWriterSetCurrentMode
177188
}
178189
}
179-
// #TODO enable this check when we have a flag/config for setting mode 1 as the desired mode
180-
// if features.IsEnabledGlobally(featuremgmt.FlagDualWritePlaylistsMode1) && (currentMode == Mode2) {
181-
// // This is where we go through the different gates to allow the instance to migrate from mode 2 to mode 1.
182-
// // There are none between mode 1 and mode 2
183-
// currentMode = Mode1
184-
185-
// err := kvs.Set(ctx, entity, fmt.Sprint(currentMode))
186-
// if err != nil {
187-
// return nil, errDualWriterSetCurrentMode
188-
// }
189-
// }
190+
if (desiredMode == Mode1) && (currentMode == Mode2) {
191+
// This is where we go through the different gates to allow the instance to migrate from mode 2 to mode 1.
192+
// There are none between mode 1 and mode 2
193+
currentMode = Mode1
194+
195+
err := kvs.Set(ctx, entity, fmt.Sprint(currentMode))
196+
if err != nil {
197+
return nil, errDualWriterSetCurrentMode
198+
}
199+
}
190200

191201
// #TODO add support for other combinations of desired and current modes
192202

pkg/apiserver/rest/dualwriter_test.go

+10-13
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,33 @@ import (
55
"fmt"
66
"testing"
77

8+
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
89
"github.com/grafana/grafana/pkg/infra/kvstore"
9-
"github.com/grafana/grafana/pkg/services/featuremgmt"
1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/mock"
1212
)
1313

1414
func TestSetDualWritingMode(t *testing.T) {
1515
type testCase struct {
1616
name string
17-
features []any
1817
stackID string
18+
desiredMode DualWriterMode
1919
expectedMode DualWriterMode
2020
}
2121
tests :=
2222
// #TODO add test cases for kv store failures. Requires adding support in kvstore test_utils.go
2323
[]testCase{
2424
{
25-
name: "should return a mode 1 dual writer when no desired mode is set",
26-
features: []any{},
25+
name: "should return a mode 2 dual writer when mode 2 is set as the desired mode",
2726
stackID: "stack-1",
28-
expectedMode: Mode1,
27+
desiredMode: Mode2,
28+
expectedMode: Mode2,
2929
},
3030
{
31-
name: "should return a mode 2 dual writer when mode 2 is set as the desired mode",
32-
features: []any{featuremgmt.FlagDualWritePlaylistsMode2},
31+
name: "should return a mode 1 dual writer when mode 1 is set as the desired mode",
3332
stackID: "stack-1",
34-
expectedMode: Mode2,
33+
desiredMode: Mode1,
34+
expectedMode: Mode1,
3535
},
3636
}
3737

@@ -43,17 +43,14 @@ func TestSetDualWritingMode(t *testing.T) {
4343
ls := legacyStoreMock{m, l}
4444
us := storageMock{m, s}
4545

46-
f := featuremgmt.WithFeatures(tt.features...)
4746
kvStore := kvstore.WithNamespace(kvstore.NewFakeKVStore(), 0, "storage.dualwriting."+tt.stackID)
4847

49-
key := "playlist"
50-
51-
dw, err := SetDualWritingMode(context.Background(), kvStore, f, key, ls, us)
48+
dw, err := SetDualWritingMode(context.Background(), kvStore, ls, us, playlist.GROUPRESOURCE, tt.desiredMode)
5249
assert.NoError(t, err)
5350
assert.Equal(t, tt.expectedMode, dw.Mode())
5451

5552
// check kv store
56-
val, ok, err := kvStore.Get(context.Background(), key)
53+
val, ok, err := kvStore.Get(context.Background(), playlist.GROUPRESOURCE)
5754
assert.True(t, ok)
5855
assert.NoError(t, err)
5956
assert.Equal(t, val, fmt.Sprint(tt.expectedMode))

pkg/cmd/grafana/apiserver/server.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ func (o *APIServerOptions) RunAPIServer(config *genericapiserver.RecommendedConf
162162
}
163163

164164
// Install the API Group+version
165-
err = builder.InstallAPIs(grafanaAPIServer.Scheme, grafanaAPIServer.Codecs, server, config.RESTOptionsGetter, o.builders, true)
165+
// #TODO figure out how to configure storage type in o.Options.StorageOptions
166+
err = builder.InstallAPIs(grafanaAPIServer.Scheme, grafanaAPIServer.Codecs, server, config.RESTOptionsGetter, o.builders, o.Options.StorageOptions)
166167
if err != nil {
167168
return err
168169
}

pkg/registry/apis/dashboard/register.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func (b *DashboardsAPIBuilder) GetGroupVersion() schema.GroupVersion {
7373
return v0alpha1.DashboardResourceInfo.GroupVersion()
7474
}
7575

76+
func (b *DashboardsAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, modeMap map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode {
77+
// Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go
78+
return grafanarest.Mode0
79+
}
80+
7681
func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
7782
scheme.AddKnownTypes(gv,
7883
&v0alpha1.Dashboard{},
@@ -109,7 +114,7 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
109114
scheme *runtime.Scheme,
110115
codecs serializer.CodecFactory, // pointer?
111116
optsGetter generic.RESTOptionsGetter,
112-
dualWrite bool,
117+
desiredMode grafanarest.DualWriterMode,
113118
) (*genericapiserver.APIGroupInfo, error) {
114119
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(v0alpha1.GROUP, scheme, metav1.ParameterCodec, codecs)
115120

@@ -135,7 +140,7 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
135140
}
136141

137142
// Dual writes if a RESTOptionsGetter is provided
138-
if dualWrite && optsGetter != nil {
143+
if desiredMode != grafanarest.Mode0 && optsGetter != nil {
139144
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: grafanaregistry.GetAttrs}
140145
if err := store.CompleteWithOptions(options); err != nil {
141146
return nil, err

pkg/registry/apis/dashboardsnapshot/register.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
2424
"github.com/grafana/grafana/pkg/apiserver/builder"
25+
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
2526
"github.com/grafana/grafana/pkg/infra/appcontext"
2627
"github.com/grafana/grafana/pkg/infra/db"
2728
"github.com/grafana/grafana/pkg/infra/log"
@@ -86,6 +87,11 @@ func (b *SnapshotsAPIBuilder) GetGroupVersion() schema.GroupVersion {
8687
return resourceInfo.GroupVersion()
8788
}
8889

90+
func (b *SnapshotsAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, modeMap map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode {
91+
// Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go
92+
return grafanarest.Mode0
93+
}
94+
8995
func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
9096
scheme.AddKnownTypes(gv,
9197
&dashboardsnapshot.DashboardSnapshot{},
@@ -122,7 +128,7 @@ func (b *SnapshotsAPIBuilder) GetAPIGroupInfo(
122128
scheme *runtime.Scheme,
123129
codecs serializer.CodecFactory, // pointer?
124130
optsGetter generic.RESTOptionsGetter,
125-
dualWrite bool,
131+
_ grafanarest.DualWriterMode,
126132
) (*genericapiserver.APIGroupInfo, error) {
127133
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(dashboardsnapshot.GROUP, scheme, metav1.ParameterCodec, codecs)
128134
storage := map[string]rest.Storage{}

pkg/registry/apis/datasource/register.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
datasource "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
2424
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
2525
"github.com/grafana/grafana/pkg/apiserver/builder"
26+
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
2627
"github.com/grafana/grafana/pkg/plugins"
2728
"github.com/grafana/grafana/pkg/promlib/models"
2829
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
@@ -151,6 +152,11 @@ func (b *DataSourceAPIBuilder) GetGroupVersion() schema.GroupVersion {
151152
return b.connectionResourceInfo.GroupVersion()
152153
}
153154

155+
func (b *DataSourceAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, modeMap map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode {
156+
// Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go
157+
return grafanarest.Mode0
158+
}
159+
154160
func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
155161
scheme.AddKnownTypes(gv,
156162
&datasource.DataSourceConnection{},
@@ -198,7 +204,7 @@ func (b *DataSourceAPIBuilder) GetAPIGroupInfo(
198204
scheme *runtime.Scheme,
199205
codecs serializer.CodecFactory, // pointer?
200206
_ generic.RESTOptionsGetter,
201-
_ bool,
207+
_ grafanarest.DualWriterMode,
202208
) (*genericapiserver.APIGroupInfo, error) {
203209
storage := map[string]rest.Storage{}
204210

pkg/registry/apis/example/register.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
2424
"github.com/grafana/grafana/pkg/apiserver/builder"
25+
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
2526
"github.com/grafana/grafana/pkg/infra/appcontext"
2627
"github.com/grafana/grafana/pkg/services/featuremgmt"
2728
)
@@ -53,6 +54,11 @@ func (b *TestingAPIBuilder) GetGroupVersion() schema.GroupVersion {
5354
return b.gv
5455
}
5556

57+
func (b *TestingAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, modeMap map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode {
58+
// Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go
59+
return grafanarest.Mode0
60+
}
61+
5662
func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
5763
scheme.AddKnownTypes(gv,
5864
&example.RuntimeInfo{},
@@ -85,7 +91,7 @@ func (b *TestingAPIBuilder) GetAPIGroupInfo(
8591
scheme *runtime.Scheme,
8692
codecs serializer.CodecFactory, // pointer?
8793
_ generic.RESTOptionsGetter,
88-
_ bool,
94+
_ grafanarest.DualWriterMode,
8995
) (*genericapiserver.APIGroupInfo, error) {
9096
b.codecs = codecs
9197
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(b.gv.Group, scheme, metav1.ParameterCodec, codecs)

pkg/registry/apis/featuretoggle/register.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
1717
"github.com/grafana/grafana/pkg/apiserver/builder"
18+
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
1819
"github.com/grafana/grafana/pkg/services/accesscontrol"
1920
"github.com/grafana/grafana/pkg/services/featuremgmt"
2021
"github.com/grafana/grafana/pkg/setting"
@@ -49,6 +50,11 @@ func (b *FeatureFlagAPIBuilder) GetGroupVersion() schema.GroupVersion {
4950
return gv
5051
}
5152

53+
func (b *FeatureFlagAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, modeMap map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode {
54+
// Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go
55+
return grafanarest.Mode0
56+
}
57+
5258
func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
5359
scheme.AddKnownTypes(gv,
5460
&v0alpha1.Feature{},
@@ -82,7 +88,7 @@ func (b *FeatureFlagAPIBuilder) GetAPIGroupInfo(
8288
scheme *runtime.Scheme,
8389
codecs serializer.CodecFactory, // pointer?
8490
_ generic.RESTOptionsGetter,
85-
_ bool,
91+
_ grafanarest.DualWriterMode,
8692
) (*genericapiserver.APIGroupInfo, error) {
8793
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(v0alpha1.GROUP, scheme, metav1.ParameterCodec, codecs)
8894

0 commit comments

Comments
 (0)