| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | package rendering | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-12-01 08:58:43 -07:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/http/httptest" | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2021-12-01 08:58:43 -07:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-01 08:58:43 -07:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2022-02-09 13:23:32 +04:00
										 |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							| 
									
										
										
										
											2022-05-09 19:11:24 +01:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestGetUrl(t *testing.T) { | 
					
						
							|  |  |  | 	path := "render/d-solo/5SdHCadmz/panel-tests-graph?orgId=1&from=1587390211965&to=1587393811965&panelId=5&width=1000&height=500&tz=Europe%2FStockholm" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:11:22 +02:00
										 |  |  | 	cfg := setting.NewCfg() | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 	rs := &RenderingService{ | 
					
						
							| 
									
										
										
										
											2021-08-25 15:11:22 +02:00
										 |  |  | 		Cfg: cfg, | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("When renderer and callback url configured should return callback url plus path", func(t *testing.T) { | 
					
						
							|  |  |  | 		rs.Cfg.RendererUrl = "http://localhost:8081/render" | 
					
						
							|  |  |  | 		rs.Cfg.RendererCallbackUrl = "http://public-grafana.com/" | 
					
						
							|  |  |  | 		url := rs.getURL(path) | 
					
						
							|  |  |  | 		require.Equal(t, rs.Cfg.RendererCallbackUrl+path+"&render=1", url) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("When renderer url not configured", func(t *testing.T) { | 
					
						
							|  |  |  | 		rs.Cfg.RendererUrl = "" | 
					
						
							|  |  |  | 		rs.domain = "localhost" | 
					
						
							| 
									
										
										
										
											2021-03-10 12:41:29 +01:00
										 |  |  | 		rs.Cfg.HTTPPort = "3000" | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("And protocol HTTP configured should return expected path", func(t *testing.T) { | 
					
						
							|  |  |  | 			rs.Cfg.ServeFromSubPath = false | 
					
						
							| 
									
										
										
										
											2020-11-13 09:52:38 +01:00
										 |  |  | 			rs.Cfg.AppSubURL = "" | 
					
						
							| 
									
										
										
										
											2020-12-11 11:44:44 +01:00
										 |  |  | 			rs.Cfg.Protocol = setting.HTTPScheme | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 			url := rs.getURL(path) | 
					
						
							|  |  |  | 			require.Equal(t, "http://localhost:3000/"+path+"&render=1", url) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			t.Run("And serve from sub path should return expected path", func(t *testing.T) { | 
					
						
							|  |  |  | 				rs.Cfg.ServeFromSubPath = true | 
					
						
							| 
									
										
										
										
											2020-11-13 09:52:38 +01:00
										 |  |  | 				rs.Cfg.AppSubURL = "/grafana" | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 				url := rs.getURL(path) | 
					
						
							|  |  |  | 				require.Equal(t, "http://localhost:3000/grafana/"+path+"&render=1", url) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("And protocol HTTPS configured should return expected path", func(t *testing.T) { | 
					
						
							|  |  |  | 			rs.Cfg.ServeFromSubPath = false | 
					
						
							| 
									
										
										
										
											2020-11-13 09:52:38 +01:00
										 |  |  | 			rs.Cfg.AppSubURL = "" | 
					
						
							| 
									
										
										
										
											2020-12-11 11:44:44 +01:00
										 |  |  | 			rs.Cfg.Protocol = setting.HTTPSScheme | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 			url := rs.getURL(path) | 
					
						
							|  |  |  | 			require.Equal(t, "https://localhost:3000/"+path+"&render=1", url) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("And protocol HTTP2 configured should return expected path", func(t *testing.T) { | 
					
						
							|  |  |  | 			rs.Cfg.ServeFromSubPath = false | 
					
						
							| 
									
										
										
										
											2020-11-13 09:52:38 +01:00
										 |  |  | 			rs.Cfg.AppSubURL = "" | 
					
						
							| 
									
										
										
										
											2020-12-11 11:44:44 +01:00
										 |  |  | 			rs.Cfg.Protocol = setting.HTTP2Scheme | 
					
						
							| 
									
										
										
										
											2020-04-21 19:57:11 +02:00
										 |  |  | 			url := rs.getURL(path) | 
					
						
							|  |  |  | 			require.Equal(t, "https://localhost:3000/"+path+"&render=1", url) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestRenderErrorImage(t *testing.T) { | 
					
						
							|  |  |  | 	path, err := filepath.Abs("../../../") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rs := RenderingService{ | 
					
						
							|  |  |  | 		Cfg: &setting.Cfg{ | 
					
						
							|  |  |  | 			HomePath: path, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.Run("No theme set returns error image with dark theme", func(t *testing.T) { | 
					
						
							|  |  |  | 		result, err := rs.RenderErrorImage("", nil) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		assert.Equal(t, result.FilePath, path+"/public/img/rendering_error_dark.png") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Timeout error returns timeout error image", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-02-09 13:23:32 +04:00
										 |  |  | 		result, err := rs.RenderErrorImage(models.ThemeLight, ErrTimeout) | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		assert.Equal(t, result.FilePath, path+"/public/img/rendering_timeout_light.png") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Generic error returns error image", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-02-09 13:23:32 +04:00
										 |  |  | 		result, err := rs.RenderErrorImage(models.ThemeLight, errors.New("an error")) | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		assert.Equal(t, result.FilePath, path+"/public/img/rendering_error_light.png") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Unknown image path returns error", func(t *testing.T) { | 
					
						
							|  |  |  | 		result, err := rs.RenderErrorImage("abc", errors.New("random error")) | 
					
						
							|  |  |  | 		assert.Error(t, err) | 
					
						
							|  |  |  | 		assert.Nil(t, result) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-09 19:11:24 +01:00
										 |  |  | type unavailableRendererManager struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m unavailableRendererManager) Renderer() *plugins.Plugin { return nil } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRenderUnavailableError(t *testing.T) { | 
					
						
							|  |  |  | 	rs := RenderingService{ | 
					
						
							|  |  |  | 		Cfg:                   &setting.Cfg{}, | 
					
						
							|  |  |  | 		log:                   log.New("test"), | 
					
						
							|  |  |  | 		RendererPluginManager: unavailableRendererManager{}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	opts := Opts{ErrorOpts: ErrorOpts{ErrorRenderUnavailable: true}} | 
					
						
							|  |  |  | 	result, err := rs.Render(context.Background(), opts, nil) | 
					
						
							|  |  |  | 	assert.Equal(t, ErrRenderUnavailable, err) | 
					
						
							|  |  |  | 	assert.Nil(t, result) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | func TestRenderLimitImage(t *testing.T) { | 
					
						
							|  |  |  | 	path, err := filepath.Abs("../../../") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rs := RenderingService{ | 
					
						
							|  |  |  | 		Cfg: &setting.Cfg{ | 
					
						
							|  |  |  | 			HomePath: path, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		inProgressCount: 2, | 
					
						
							| 
									
										
										
										
											2022-01-10 08:21:35 -08:00
										 |  |  | 		log:             log.New("test"), | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name     string | 
					
						
							| 
									
										
										
										
											2022-02-09 13:23:32 +04:00
										 |  |  | 		theme    models.Theme | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 		expected string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:     "Light theme returns light image", | 
					
						
							| 
									
										
										
										
											2022-02-09 13:23:32 +04:00
										 |  |  | 			theme:    models.ThemeLight, | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 			expected: path + "/public/img/rendering_limit_light.png", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:     "Dark theme returns dark image", | 
					
						
							| 
									
										
										
										
											2022-02-09 13:23:32 +04:00
										 |  |  | 			theme:    models.ThemeDark, | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 			expected: path + "/public/img/rendering_limit_dark.png", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:     "No theme returns dark image", | 
					
						
							|  |  |  | 			theme:    "", | 
					
						
							|  |  |  | 			expected: path + "/public/img/rendering_limit_dark.png", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tc := range tests { | 
					
						
							|  |  |  | 		t.Run(tc.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			opts := Opts{Theme: tc.theme, ConcurrentLimit: 1} | 
					
						
							| 
									
										
										
										
											2022-01-27 02:02:19 +04:00
										 |  |  | 			result, err := rs.Render(context.Background(), opts, nil) | 
					
						
							| 
									
										
										
										
											2021-11-17 12:18:47 +01:00
										 |  |  | 			assert.NoError(t, err) | 
					
						
							|  |  |  | 			assert.Equal(t, tc.expected, result.FilePath) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-01 08:58:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-09 19:11:24 +01:00
										 |  |  | func TestRenderLimitImageError(t *testing.T) { | 
					
						
							|  |  |  | 	rs := RenderingService{ | 
					
						
							|  |  |  | 		Cfg:             &setting.Cfg{}, | 
					
						
							|  |  |  | 		inProgressCount: 2, | 
					
						
							|  |  |  | 		log:             log.New("test"), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	opts := Opts{ | 
					
						
							|  |  |  | 		ErrorOpts:       ErrorOpts{ErrorConcurrentLimitReached: true}, | 
					
						
							|  |  |  | 		ConcurrentLimit: 1, | 
					
						
							|  |  |  | 		Theme:           models.ThemeDark, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	result, err := rs.Render(context.Background(), opts, nil) | 
					
						
							|  |  |  | 	assert.Equal(t, ErrConcurrentLimitReached, err) | 
					
						
							|  |  |  | 	assert.Nil(t, result) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-01 08:58:43 -07:00
										 |  |  | func TestRenderingServiceGetRemotePluginVersion(t *testing.T) { | 
					
						
							|  |  |  | 	cfg := setting.NewCfg() | 
					
						
							|  |  |  | 	rs := &RenderingService{ | 
					
						
							|  |  |  | 		Cfg: cfg, | 
					
						
							|  |  |  | 		log: log.New("rendering-test"), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("When renderer responds with correct version should return that version", func(t *testing.T) { | 
					
						
							|  |  |  | 		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | 
					
						
							|  |  |  | 			w.Header().Set("Content-Type", "application/json") | 
					
						
							|  |  |  | 			w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  | 			_, err := w.Write([]byte("{\"version\":\"2.7.1828\"}")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		})) | 
					
						
							|  |  |  | 		defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rs.Cfg.RendererUrl = server.URL + "/render" | 
					
						
							|  |  |  | 		version, err := rs.getRemotePluginVersion() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, "2.7.1828", version) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("When renderer responds with 404 should assume a valid but old version", func(t *testing.T) { | 
					
						
							|  |  |  | 		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | 
					
						
							|  |  |  | 			w.WriteHeader(http.StatusNotFound) | 
					
						
							|  |  |  | 		})) | 
					
						
							|  |  |  | 		defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rs.Cfg.RendererUrl = server.URL + "/render" | 
					
						
							|  |  |  | 		version, err := rs.getRemotePluginVersion() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, version, "1.0.0") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("When renderer responds with 500 should retry until success", func(t *testing.T) { | 
					
						
							|  |  |  | 		tries := uint(0) | 
					
						
							|  |  |  | 		ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | 
					
						
							|  |  |  | 			tries++ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if tries < remoteVersionFetchRetries { | 
					
						
							|  |  |  | 				w.WriteHeader(http.StatusInternalServerError) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				w.Header().Set("Content-Type", "application/json") | 
					
						
							|  |  |  | 				w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  | 				_, err := w.Write([]byte("{\"version\":\"3.1.4159\"}")) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 				cancel() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		})) | 
					
						
							|  |  |  | 		defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rs.Cfg.RendererUrl = server.URL + "/render" | 
					
						
							|  |  |  | 		remoteVersionFetchInterval = time.Millisecond | 
					
						
							|  |  |  | 		remoteVersionFetchRetries = 5 | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			require.NoError(t, rs.Run(ctx)) | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.Eventually(t, func() bool { return rs.Version() == "3.1.4159" }, time.Second, time.Millisecond) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } |