Use dw dynamic config (#91222)

* Remove kubernetesPlaylists feature_toggle

* Remove unified_storage_mode

* Remove double import

* Regenerate feature-toggles

* Read from config instead from feature_toggle

* cover scenario for when unified storage is not defined
This commit is contained in:
Leonor Oliveira
2024-08-13 09:03:28 +01:00
committed by GitHub
parent b67bcdb9b8
commit 0258842f87
14 changed files with 31 additions and 70 deletions

View File

@@ -50,7 +50,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `transformationsVariableSupport` | Allows using variables in transformations | Yes |
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes |

View File

@@ -112,7 +112,6 @@ export interface FeatureToggles {
disableClassicHTTPHistogram?: boolean;
formatString?: boolean;
transformationsVariableSupport?: boolean;
kubernetesPlaylists?: boolean;
kubernetesSnapshots?: boolean;
kubernetesDashboards?: boolean;
datasourceQueryTypes?: boolean;

View File

@@ -185,6 +185,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
cloudMigrationPollIntervalMs = 2000;
reportingStaticContext?: Record<string, string>;
exploreDefaultTimeOffset = '1h';
unifiedStorage: Map<string, number> = new Map<string, number>();
/**
* Language used in Grafana's UI. This is after the user's preference (or deteceted locale) is resolved to one of

View File

@@ -12,13 +12,12 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
playlistalpha1 "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
"github.com/grafana/grafana/pkg/middleware"
internalplaylist "github.com/grafana/grafana/pkg/registry/apis/playlist"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/util/errhttp"
"github.com/grafana/grafana/pkg/web"
@@ -27,7 +26,8 @@ import (
func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) {
// Register the actual handlers
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
if hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesPlaylists) {
unifiedStorageOptions := hs.Cfg.UnifiedStorage
if mode, ok := unifiedStorageOptions[playlistalpha1.GROUPRESOURCE]; ok && mode > 0 {
// Use k8s client to implement legacy API
handler := newPlaylistK8sHandler(hs)
playlistRoute.Get("/", handler.searchPlaylists)
@@ -330,7 +330,7 @@ type playlistK8sHandler struct {
func newPlaylistK8sHandler(hs *HTTPServer) *playlistK8sHandler {
return &playlistK8sHandler{
gvr: v0alpha1.PlaylistResourceInfo.GroupVersionResource(),
gvr: playlistalpha1.PlaylistResourceInfo.GroupVersionResource(),
namespacer: request.GetNamespaceMapper(hs.Cfg),
clientConfigProvider: hs.clientConfigProvider,
}

View File

@@ -4,7 +4,6 @@
```ini
[feature_toggles]
kubernetesPlaylists = true
```
Start Grafana:
@@ -62,8 +61,10 @@ For kubectl to work, grafana needs to run over https. To simplify development,
app_mode = development
[feature_toggles]
grafanaAPIServerEnsureKubectlAccess = true
kubernetesPlaylists = true
grafanaAPIServerEnsureKubectlAccess = true
[unified_storage]
playlists.playlist.grafana.app = 2
```
This will create a development kubeconfig and start a parallel ssl listener. It can be registered by
@@ -90,4 +91,4 @@ The folder structure aims to follow the patterns established in standard (https:
* [pkg/apis](/pkg/apis) - where API resource types are defined. this is based on the structure of the [sample-apiserver](https://github.com/kubernetes/sample-apiserver/tree/master/pkg/apis)
* [hack/update-codegen.sh](/hack#kubernetes-hack-alert) - this script is used to run [k8s codegen](https://github.com/kubernetes/code-generator/), which generates the code that is used by the API server to handle the types defined in `pkg/apis`. it is based on the [update-codegen.sh from sample-apiserver](https://github.com/kubernetes/sample-apiserver/blob/master/hack/update-codegen.sh)
* [pkg/registry/apis](/pkg/registry/apis) - where all of the types in `pkg/apis` are registered with the API server by implementing the [builder](/pkg/services/apiserver/builder/common.go#L18) interface. this pattern is unique to grafana, and is needed to support using wire dependencies in legacy storage implementations. this is separated from `pkg/apis` to avoid issues with k8s codegen.
* [pkg/cmd/grafana/apiserver](/pkg/cmd/grafana/apiserver) - this is where the apiserver is configured for the `grafana apiserver` CLI command, which can be used to launch standalone API servers. this will eventually be merged with the config in `pkg/services/apiserver` to reduce duplication.
* [pkg/cmd/grafana/apiserver](/pkg/cmd/grafana/apiserver) - this is where the apiserver is configured for the `grafana apiserver` CLI command, which can be used to launch standalone API servers. this will eventually be merged with the config in `pkg/services/apiserver` to reduce duplication.

View File

@@ -7,7 +7,6 @@ import (
"strconv"
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/options"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
@@ -55,14 +54,16 @@ func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o
o.StorageOptions.StorageType = options.StorageType(apiserverCfg.Key("storage_type").MustString(string(options.StorageTypeLegacy)))
o.StorageOptions.DataPath = apiserverCfg.Key("storage_path").MustString(filepath.Join(cfg.DataPath, "grafana-apiserver"))
o.StorageOptions.Address = apiserverCfg.Key("address").MustString(o.StorageOptions.Address)
o.StorageOptions.DualWriterDesiredModes = map[string]grafanarest.DualWriterMode{
// TODO: use the new config from HGAPI after https://github.com/grafana/hosted-grafana/pull/5707
playlist.GROUPRESOURCE: 2,
}
// unified storage modes
unifiedStorageCfg := cfg.UnifiedStorage
o.StorageOptions.DualWriterDesiredModes = unifiedStorageCfg
// TODO: ensure backwards compatibility with production
// remove this after changing the unified_storage_mode key format in HGAPI
o.StorageOptions.DualWriterDesiredModes[playlist.RESOURCE+"."+playlist.GROUP] = o.StorageOptions.DualWriterDesiredModes[playlist.GROUPRESOURCE]
// remove this after changing the unified_storage key format in HGAPI
if _, ok := o.StorageOptions.DualWriterDesiredModes[playlist.RESOURCE+"."+playlist.GROUP]; ok {
o.StorageOptions.DualWriterDesiredModes[playlist.RESOURCE+"."+playlist.GROUP] = o.StorageOptions.DualWriterDesiredModes[playlist.GROUPRESOURCE]
}
o.ExtraOptions.DevMode = features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerEnsureKubectlAccess)
o.ExtraOptions.ExternalAddress = host

View File

@@ -714,14 +714,6 @@ var (
Owner: grafanaDatavizSquad,
Expression: "true", // Enabled by default
},
{
Name: "kubernetesPlaylists",
Description: "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s",
Stage: FeatureStageGeneralAvailability,
Owner: grafanaAppPlatformSquad,
Expression: "true",
RequiresRestart: true, // changes the API routing
},
{
Name: "kubernetesSnapshots",
Description: "Routes snapshot requests from /api to the /apis endpoint",

View File

@@ -93,7 +93,6 @@ enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,f
disableClassicHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false
formatString,GA,@grafana/dataviz-squad,false,false,true
transformationsVariableSupport,GA,@grafana/dataviz-squad,false,false,true
kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false
kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
93 disableClassicHTTPHistogram experimental @grafana/grafana-backend-services-squad false true false
94 formatString GA @grafana/dataviz-squad false false true
95 transformationsVariableSupport GA @grafana/dataviz-squad false false true
kubernetesPlaylists GA @grafana/grafana-app-platform-squad false true false
96 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad false true false
97 kubernetesDashboards experimental @grafana/grafana-app-platform-squad false false true
98 datasourceQueryTypes experimental @grafana/grafana-app-platform-squad false true false

View File

@@ -383,10 +383,6 @@ const (
// Allows using variables in transformations
FlagTransformationsVariableSupport = "transformationsVariableSupport"
// FlagKubernetesPlaylists
// Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s
FlagKubernetesPlaylists = "kubernetesPlaylists"
// FlagKubernetesSnapshots
// Routes snapshot requests from /api to the /apis endpoint
FlagKubernetesSnapshots = "kubernetesSnapshots"

View File

@@ -1438,6 +1438,7 @@
"name": "kubernetesPlaylists",
"resourceVersion": "1720021873452",
"creationTimestamp": "2023-10-05T19:00:36Z",
"deletionTimestamp": "2024-07-30T19:34:12Z",
"annotations": {
"grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC"
}

View File

@@ -29,6 +29,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
"github.com/grafana/grafana/pkg/apimachinery/identity"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/osutil"
@@ -520,6 +521,9 @@ type Cfg struct {
//Short Links
ShortLinkExpiration int
// Unified Storage
UnifiedStorage map[string]grafanarest.DualWriterMode
}
// AddChangePasswordLink returns if login form is disabled or not since

View File

@@ -77,24 +77,11 @@ func TestIntegrationPlaylist(t *testing.T) {
]`, disco)
})
t.Run("with k8s api flag", func(t *testing.T) {
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true, // do not start extra port 6443
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // <<< The change we are testing!
},
}))
})
t.Run("with dual write (file, mode 0)", func(t *testing.T) {
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "file", // write the files to disk
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode0,
},
@@ -106,9 +93,6 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "file", // write the files to disk
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode1,
},
@@ -120,9 +104,6 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "file", // write the files to disk
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode2,
},
@@ -134,9 +115,6 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "file", // write the files to disk
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode3,
},
@@ -150,7 +128,6 @@ func TestIntegrationPlaylist(t *testing.T) {
APIServerStorageType: "unified", // use the entity api tables
EnableFeatureToggles: []string{
featuremgmt.FlagUnifiedStorage,
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode0,
@@ -165,7 +142,6 @@ func TestIntegrationPlaylist(t *testing.T) {
APIServerStorageType: "unified", // use the entity api tables
EnableFeatureToggles: []string{
featuremgmt.FlagUnifiedStorage,
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode1,
@@ -180,7 +156,6 @@ func TestIntegrationPlaylist(t *testing.T) {
APIServerStorageType: "unified", // use the entity api tables
EnableFeatureToggles: []string{
featuremgmt.FlagUnifiedStorage,
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode2,
@@ -195,7 +170,6 @@ func TestIntegrationPlaylist(t *testing.T) {
APIServerStorageType: "unified", // use the entity api tables
EnableFeatureToggles: []string{
featuremgmt.FlagUnifiedStorage,
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode3,
@@ -211,9 +185,6 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode0,
},
@@ -238,9 +209,7 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
EnableFeatureToggles: []string{},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode1,
},
@@ -265,9 +234,7 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
EnableFeatureToggles: []string{},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode2,
},
@@ -292,9 +259,7 @@ func TestIntegrationPlaylist(t *testing.T) {
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
},
EnableFeatureToggles: []string{},
DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{
playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode3,
},

View File

@@ -387,7 +387,7 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
}
if o.DualWriterDesiredModes != nil {
unifiedStorageMode, err := getOrCreateSection("unified_storage_mode")
unifiedStorageMode, err := getOrCreateSection("unified_storage")
require.NoError(t, err)
for k, v := range o.DualWriterDesiredModes {
_, err = unifiedStorageMode.NewKey(k, fmt.Sprint(v))

View File

@@ -209,5 +209,8 @@ export function searchPlaylists(playlists: Playlist[], query?: string): Playlist
}
export function getPlaylistAPI() {
return config.featureToggles.kubernetesPlaylists ? new K8sAPI() : new LegacyAPI();
if (config.unifiedStorage) {
return config.unifiedStorage.has('playlist.grafana.app/playlists') ? new K8sAPI() : new LegacyAPI();
}
return new LegacyAPI();
}