Commit 58de0dab by Marcus Efraimsson Committed by GitHub

Alerting: Upload error image when image renderer unavailable (#23713)

When Include image is enabled for an alert notification channel, but there's 
no image renderer available/installed when sending notification an error 
image will be uploaded/attached explaining that you need to install the 
Grafana Image Renderer plugin.

Ref #13802

Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>
parent 98df2ec7
......@@ -53,25 +53,19 @@ func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error {
}
if notifierStates.ShouldUploadImage() {
if n.renderService.IsAvailable() {
// Create a copy of EvalContext and give it a new, shorter, timeout context to upload the image
uploadEvalCtx := *evalCtx
timeout := setting.AlertingNotificationTimeout / 2
var uploadCtxCancel func()
uploadEvalCtx.Ctx, uploadCtxCancel = context.WithTimeout(evalCtx.Ctx, timeout)
// Try to upload the image without consuming all the time allocated for EvalContext
if err = n.renderAndUploadImage(&uploadEvalCtx, timeout); err != nil {
n.log.Error("Failed to render and upload alert panel image.", "ruleId", uploadEvalCtx.Rule.ID, "error", err)
}
uploadCtxCancel()
evalCtx.ImageOnDiskPath = uploadEvalCtx.ImageOnDiskPath
evalCtx.ImagePublicURL = uploadEvalCtx.ImagePublicURL
} else {
n.log.Warn("Could not render image for alert notification, no image renderer found/installed. " +
"For image rendering support please install the grafana-image-renderer plugin. " +
"Read more at https://grafana.com/docs/grafana/latest/administration/image_rendering/")
// Create a copy of EvalContext and give it a new, shorter, timeout context to upload the image
uploadEvalCtx := *evalCtx
timeout := setting.AlertingNotificationTimeout / 2
var uploadCtxCancel func()
uploadEvalCtx.Ctx, uploadCtxCancel = context.WithTimeout(evalCtx.Ctx, timeout)
// Try to upload the image without consuming all the time allocated for EvalContext
if err = n.renderAndUploadImage(&uploadEvalCtx, timeout); err != nil {
n.log.Error("Failed to render and upload alert panel image.", "ruleId", uploadEvalCtx.Rule.ID, "error", err)
}
uploadCtxCancel()
evalCtx.ImageOnDiskPath = uploadEvalCtx.ImageOnDiskPath
evalCtx.ImagePublicURL = uploadEvalCtx.ImagePublicURL
}
return n.sendNotifications(evalCtx, notifierStates)
......@@ -174,6 +168,7 @@ func (n *notificationService) renderAndUploadImage(evalCtx *EvalContext, timeout
n.log.Debug("Rendered alert panel image", "ruleId", evalCtx.Rule.ID, "path", result.FilePath, "took", took)
evalCtx.ImageOnDiskPath = result.FilePath
n.log.Debug("Uploading alert panel image to external image store", "ruleId", evalCtx.Rule.ID, "path", evalCtx.ImageOnDiskPath)
start = time.Now()
......
......@@ -39,13 +39,13 @@ func TestNotificationService(t *testing.T) {
require.Truef(t, evalCtx.Ctx.Value(notificationSent{}).(bool), "expected notification to be sent, but wasn't")
})
notificationServiceScenario(t, "Given alert rule with upload image enabled but no renderer available should not render and upload image, but send notification", evalCtx, true, func(scenarioCtx *scenarioContext) {
notificationServiceScenario(t, "Given alert rule with upload image enabled but no renderer available should render and upload unavailable image and send notification", evalCtx, true, func(scenarioCtx *scenarioContext) {
scenarioCtx.rendererAvailable = false
err := scenarioCtx.notificationService.SendIfNeeded(evalCtx)
require.NoError(t, err)
require.Equalf(t, 0, scenarioCtx.renderCount, "expected render to not be called, but it was")
require.Equalf(t, 0, scenarioCtx.imageUploadCount, "expected image to not be uploaded, but it was")
require.Equalf(t, 1, scenarioCtx.renderCount, "expected render to be called, but it wasn't")
require.Equalf(t, 1, scenarioCtx.imageUploadCount, "expected image to be uploaded, but it wasn't")
require.Truef(t, evalCtx.Ctx.Value(notificationSent{}).(bool), "expected notification to be sent, but wasn't")
})
......
......@@ -121,6 +121,14 @@ func (rs *RenderingService) RenderErrorImage(err error) (*RenderResult, error) {
}, nil
}
func (rs *RenderingService) renderUnavailableImage() *RenderResult {
imgPath := "public/img/rendering_plugin_not_installed.png"
return &RenderResult{
FilePath: filepath.Join(setting.HomePath, imgPath),
}
}
func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResult, error) {
if rs.inProgressCount > opts.ConcurrentLimit {
return &RenderResult{
......@@ -128,8 +136,11 @@ func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResul
}, nil
}
if rs.renderAction == nil {
return nil, fmt.Errorf("no renderer found")
if !rs.IsAvailable() {
rs.log.Warn("Could not render image, no image renderer found/installed. " +
"For image rendering support please install the grafana-image-renderer plugin. " +
"Read more at https://grafana.com/docs/grafana/latest/administration/image_rendering/")
return rs.renderUnavailableImage(), nil
}
rs.log.Info("Rendering", "path", opts.Path)
......

3.57 KB | W: | H:

8.88 KB | W: | H:

public/img/rendering_plugin_not_installed.png
public/img/rendering_plugin_not_installed.png
public/img/rendering_plugin_not_installed.png
public/img/rendering_plugin_not_installed.png
  • 2-up
  • Swipe
  • Onion skin
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment