test(unified-storage): Create initial e2e test scenario for unified storage (#102019)

* test: introduce initial e2e test scenario for unified storage

* fix: lint

* chore: trigger build

* test: attach unified storage grpc service into grafana

* fix: lint and todos

* fix: err handling

* chore: trigger build
This commit is contained in:
Mustafa Sencer Özcan
2025-03-19 09:21:04 +01:00
committed by GitHub
parent 450d76518e
commit ef94d21093
5 changed files with 387 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
package e2e
import (
"fmt"
"net/url"
"os"
"github.com/grafana/e2e"
gapi "github.com/grafana/grafana-api-golang-client"
)
const (
grafanaINI = `
app_mode = development
target = all
[grafana-apiserver]
address = 0.0.0.0:10000
storage_type = unified-grpc
[unified_storage.dashboards.dashboard.grafana.app]
dualWriterMode = 5
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 5
`
defaultGrafanaImage = "grafana/grafana:main"
grafanaBinary = "/run.sh"
grafanaHTTPPort = 3000
grafanaGRPCPort = 10000
)
type GrafanaService struct {
*e2e.HTTPService
}
func NewGrafanaService(name string, flags, envVars map[string]string) *GrafanaService {
svc := &GrafanaService{
HTTPService: e2e.NewHTTPService(
name,
GetGrafanaImage(),
e2e.NewCommandWithoutEntrypoint(grafanaBinary, e2e.BuildArgs(flags)...),
e2e.NewHTTPReadinessProbe(grafanaHTTPPort, "/ready", 200, 299),
grafanaHTTPPort,
grafanaGRPCPort),
}
svc.ConcreteService.SetEnvVars(envVars)
return svc
}
func (g *GrafanaService) GRPCEndpoint() string {
return g.HTTPService.NetworkEndpoint(grafanaGRPCPort)
}
type GrafanaClient struct {
*gapi.Client
}
func NewGrafanaClient(host string, orgID int64) (*GrafanaClient, error) {
cfg := gapi.Config{
BasicAuth: url.UserPassword("admin", "admin"),
OrgID: orgID,
HTTPHeaders: map[string]string{
"X-Disable-Provenance": "true",
},
}
client, err := gapi.New(fmt.Sprintf("http://%s/", host), cfg)
if err != nil {
return nil, err
}
return &GrafanaClient{
Client: client,
}, nil
}
// GetDefaultImage returns the Docker image to use to run the Grafana..
func GetGrafanaImage() string {
if img := os.Getenv("GRAFANA_IMAGE"); img != "" {
return img
}
return defaultGrafanaImage
}
+41
View File
@@ -0,0 +1,41 @@
package e2e
import (
"os"
"github.com/grafana/e2e"
)
const (
defaultPostgresImage = "postgres:16.4"
postgresHTTPPort = 5432
)
// GetDefaultImage returns the Docker image to use to run the Postgres..
func GetPostgresImage() string {
if img := os.Getenv("POSTGRES_IMAGE"); img != "" {
return img
}
return defaultPostgresImage
}
type PostgresService struct {
*e2e.HTTPService
}
func NewPostgresService(name string, envVars map[string]string) *PostgresService {
svc := &PostgresService{
HTTPService: e2e.NewHTTPService(
name,
GetPostgresImage(),
nil,
nil,
postgresHTTPPort,
),
}
svc.SetEnvVars(envVars)
return svc
}
+57
View File
@@ -0,0 +1,57 @@
package e2e
import (
"github.com/grafana/e2e"
)
const (
storageINI = `
app_mode = development
target = storage-server
[grafana-apiserver]
storage_type = unified
[database]
type = postgres
host = postgres:5432
name = grafana
user = admin
password = admin
ssl_mode = disable
`
)
type StorageService struct {
*e2e.ConcreteService
}
func NewStorageService(name string, flags, envVars map[string]string) *StorageService {
args := append([]string{"target"}, e2e.BuildArgs(flags)...)
svc := &StorageService{
ConcreteService: e2e.NewConcreteService(
name,
GetGrafanaImage(),
e2e.NewCommandWithoutEntrypoint(grafanaBinary, args...),
e2e.NewTCPReadinessProbe(10000),
10000,
),
}
svc.SetEnvVars(envVars)
svc.Endpoint(10000)
return svc
}
// TODO: remove this method when the storage-server module has a graceful shutdown on docker stop
// the following method is implemented because the storage-server module is not shutting down gracefully
// https://github.com/grafana/search-and-storage-team/issues/228
func (s *StorageService) Stop() error {
_ = s.ConcreteService.Stop()
return nil
}
func (s *StorageService) GRPCEndpoint() string {
return s.NetworkEndpoint(grafanaGRPCPort)
}
+141
View File
@@ -0,0 +1,141 @@
package e2e
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/grafana/e2e"
)
type UnifiedScenario struct {
*e2e.Scenario
Grafana *GrafanaService
UnifiedStorage *StorageService
Postgres *PostgresService
}
func NewUnifiedScenario() (*UnifiedScenario, error) {
s, err := e2e.NewScenario(GetNetworkName())
if err != nil {
return nil, err
}
return &UnifiedScenario{
Scenario: s,
}, nil
}
func (s *UnifiedScenario) NewGrafanaService(name string, grpcEndpoint string) (*GrafanaService, error) {
flags := map[string]string{
"--config": fmt.Sprintf("/shared/%s.ini", name),
}
fts := []string{
"grafanaAPIServerWithExperimentalAPIs",
"kubernetesClientDashboardsFolders",
"grafanaAPIServerEnsureKubectlAccess",
"unifiedStorageSearch",
"unifiedStorageSearchUI",
"kubernetesCliDashboards",
"kubernetesDashboardsAPI",
"kubernetesFolders",
"unified_storage",
}
envVars := map[string]string{
"GF_PATHS_CONFIG": fmt.Sprintf("/shared/%s.ini", name),
"GF_FEATURE_TOGGLES_ENABLE": strings.Join(fts, ","),
"GF_GRAFANA_APISERVER_ADDRESS": grpcEndpoint,
"GF_GRAFANA_APISERVER_STORAGE_TYPE": "unified-grpc",
"GF_DATABASE_TYPE": "postgres",
"GF_DATABASE_HOST": "postgres:5432",
"GF_DATABASE_NAME": "grafana",
"GF_DATABASE_USER": "admin",
"GF_DATABASE_PASSWORD": "admin",
"GF_DATABASE_SSL_MODE": "disable",
}
if err := s.loadCfg(name, grafanaINI); err != nil {
return nil, err
}
g := NewGrafanaService(name, flags, envVars)
s.Grafana = g
return g, nil
}
func (s *UnifiedScenario) NewGrafanaClient(grafanaName string, orgID int64) (*GrafanaClient, error) {
g := s.Grafana
return NewGrafanaClient(g.HTTPEndpoint(), orgID)
}
func (s *UnifiedScenario) NewStorageService(name string) (*StorageService, error) {
flags := map[string]string{
"--config": "/shared/storage.ini",
}
envVars := map[string]string{
"GF_PATHS_CONFIG": "/shared/storage.ini",
"GF_DEFAULT_TARGET": "storage-server",
"GF_GRPC_SERVER_ADDRESS": "0.0.0.0:10000",
"GF_FEATURE_TOGGLES_ENABLE": "unifiedStorage,unifiedStorageSearch",
"GF_RESOURCE_API_DB_TYPE": "postgres",
"GF_RESOURCE_API_DB_HOST": "postgres:5432",
"GF_RESOURCE_API_DB_NAME": "grafana",
"GF_RESOURCE_API_DB_USER": "admin",
"GF_RESOURCE_API_DB_PASS": "admin",
"GF_DATABASE_TYPE": "postgres",
"GF_DATABASE_HOST": "postgres:5432",
"GF_DATABASE_NAME": "grafana",
"GF_DATABASE_USER": "admin",
"GF_DATABASE_PASSWORD": "admin",
"GF_DATABASE_SSL_MODE": "disable",
}
if err := s.loadCfg(name, storageINI); err != nil {
return nil, err
}
us := NewStorageService(name, flags, envVars)
s.UnifiedStorage = us
return us, nil
}
func (s *UnifiedScenario) NewPostgresService(name string) *PostgresService {
ps := NewPostgresService(name, map[string]string{
"POSTGRES_USER": "admin",
"POSTGRES_PASSWORD": "admin",
"POSTGRES_DB": "grafana",
})
s.Postgres = ps
return ps
}
func (s *UnifiedScenario) loadCfg(svc, content string) error {
dst := fmt.Sprintf("%s/%s.ini", s.SharedDir(), svc)
destination, err := os.Create(filepath.Clean(dst))
if err != nil {
return err
}
_, err = destination.Write([]byte(content))
if err != nil {
return err
}
return nil
}
const (
defaultNetworkName = "e2e-grafana-unified"
)
// GetNetworkName returns the docker network name to run tests within.
func GetNetworkName() string {
// If the E2E_NETWORK_NAME is set, use that for the network name.
// Otherwise, return the default network name.
if os.Getenv("E2E_NETWORK_NAME") != "" {
return os.Getenv("E2E_NETWORK_NAME")
}
return defaultNetworkName
}
+61
View File
@@ -0,0 +1,61 @@
package e2e
import (
"testing"
gapi "github.com/grafana/grafana-api-golang-client"
"github.com/stretchr/testify/require"
)
func TestUnifiedStorage(t *testing.T) {
// TODO: remove this line when ci pipeline is ready
t.Skip("skipping e2e test")
t.Run("Create folder and dashboard", func(t *testing.T) {
s, err := NewUnifiedScenario()
require.NoError(t, err)
postgres := s.NewPostgresService("postgres")
require.NoError(t, s.Scenario.StartAndWaitReady(postgres))
storage, err := s.NewStorageService("storage")
require.NoError(t, err)
require.NoError(t, s.Scenario.StartAndWaitReady(storage))
grafana, err := s.NewGrafanaService("grafana", storage.GRPCEndpoint())
require.NoError(t, err)
require.NoError(t, s.Scenario.StartAndWaitReady(grafana))
const org1 = 1
gorg1, err := s.NewGrafanaClient("grafana", org1)
require.NoError(t, err)
folderOrg1, err := gorg1.NewFolder("test-folder-1")
require.NoError(t, err)
const uid = "test-dashboard-1"
dashboard := gapi.Dashboard{
Model: map[string]interface{}{
"uid": uid,
"title": "Test Dashboard 1",
},
FolderID: folderOrg1.ID,
FolderUID: folderOrg1.UID,
Overwrite: false,
Message: "Test Dashboard 1",
}
dashboardOrg1, err := gorg1.NewDashboard(dashboard)
require.NoError(t, err)
newDashboard, err := gorg1.DashboardByUID(uid)
require.NoError(t, err)
require.Equal(t, dashboardOrg1.UID, newDashboard.Model["uid"])
newFolder, err := gorg1.Folder(folderOrg1.ID)
require.NoError(t, err)
require.Equal(t, folderOrg1.UID, newFolder.UID)
require.NoError(t, s.Scenario.Stop(grafana, storage, postgres))
})
}