From 5148250366b7e1bcef5510d5f00183384159c191 Mon Sep 17 00:00:00 2001 From: Artur Wierzbicki Date: Thu, 27 Jan 2022 02:02:19 +0400 Subject: [PATCH] Rendering service - add optional RenderingSession (#44098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rendering service changes: - make node-renderer request timeout configurable - introduce optional RenderingSession providing a long-lived session key * remove console logs * added comment explaining empty "afterRequest" method * fix compilation error * update imports formatting * Update pkg/services/rendering/interface.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update pkg/services/rendering/rendering.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * review fix: extract renderKey related functions/structs to auth.go * #44449: private'd `rendering.getRequestTimeout` Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> --- pkg/api/render.go | 14 ++- pkg/services/alerting/notifier.go | 12 ++- pkg/services/alerting/notifier_test.go | 8 +- pkg/services/rendering/auth.go | 120 +++++++++++++++++++++++ pkg/services/rendering/http_mode.go | 4 +- pkg/services/rendering/interface.go | 51 ++++++++-- pkg/services/rendering/plugin_mode.go | 5 +- pkg/services/rendering/rendering.go | 93 ++++++------------ pkg/services/rendering/rendering_test.go | 2 +- pkg/services/thumbs/crawler.go | 21 ++-- 10 files changed, 232 insertions(+), 98 deletions(-) create mode 100644 pkg/services/rendering/auth.go diff --git a/pkg/api/render.go b/pkg/api/render.go index 0293c2a4147..157c789713b 100644 --- a/pkg/api/render.go +++ b/pkg/api/render.go @@ -53,12 +53,16 @@ func (hs *HTTPServer) RenderToPng(c *models.ReqContext) { } result, err := hs.RenderService.Render(c.Req.Context(), rendering.Opts{ + TimeoutOpts: rendering.TimeoutOpts{ + Timeout: time.Duration(timeout) * time.Second, + }, + AuthOpts: rendering.AuthOpts{ + OrgID: c.OrgId, + UserID: c.UserId, + OrgRole: c.OrgRole, + }, Width: width, Height: height, - Timeout: time.Duration(timeout) * time.Second, - OrgID: c.OrgId, - UserID: c.UserId, - OrgRole: c.OrgRole, Path: web.Params(c.Req)["*"] + queryParams, Timezone: queryReader.Get("tz", ""), Encoding: queryReader.Get("encoding", ""), @@ -66,7 +70,7 @@ func (hs *HTTPServer) RenderToPng(c *models.ReqContext) { DeviceScaleFactor: scale, Headers: headers, Theme: rendering.ThemeDark, - }) + }, nil) if err != nil { if errors.Is(err, rendering.ErrTimeout) { c.Handle(hs.Cfg, 500, err.Error(), err) diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 8874261fd1b..98e0b135822 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -200,11 +200,15 @@ func (n *notificationService) renderAndUploadImage(evalCtx *EvalContext, timeout } renderOpts := rendering.Opts{ + TimeoutOpts: rendering.TimeoutOpts{ + Timeout: timeout, + }, + AuthOpts: rendering.AuthOpts{ + OrgID: evalCtx.Rule.OrgID, + OrgRole: models.ROLE_ADMIN, + }, Width: 1000, Height: 500, - Timeout: timeout, - OrgID: evalCtx.Rule.OrgID, - OrgRole: models.ROLE_ADMIN, ConcurrentLimit: setting.AlertingRenderLimit, Theme: rendering.ThemeDark, } @@ -218,7 +222,7 @@ func (n *notificationService) renderAndUploadImage(evalCtx *EvalContext, timeout n.log.Debug("Rendering alert panel image", "ruleId", evalCtx.Rule.ID, "urlPath", renderOpts.Path) start := time.Now() - result, err := n.renderService.Render(evalCtx.Ctx, renderOpts) + result, err := n.renderService.Render(evalCtx.Ctx, renderOpts, nil) if err != nil { return err } diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go index 4b158351d05..71d75c4aea3 100644 --- a/pkg/services/alerting/notifier_test.go +++ b/pkg/services/alerting/notifier_test.go @@ -353,7 +353,7 @@ func (s *testRenderService) IsAvailable() bool { return true } -func (s *testRenderService) Render(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error) { +func (s *testRenderService) Render(ctx context.Context, opts rendering.Opts, session rendering.Session) (*rendering.RenderResult, error) { if s.renderProvider != nil { return s.renderProvider(ctx, opts) } @@ -361,7 +361,7 @@ func (s *testRenderService) Render(ctx context.Context, opts rendering.Opts) (*r return &rendering.RenderResult{FilePath: "image.png"}, nil } -func (s *testRenderService) RenderCSV(ctx context.Context, opts rendering.CSVOpts) (*rendering.RenderCSVResult, error) { +func (s *testRenderService) RenderCSV(ctx context.Context, opts rendering.CSVOpts, session rendering.Session) (*rendering.RenderCSVResult, error) { return nil, nil } @@ -381,6 +381,10 @@ func (s *testRenderService) Version() string { return "" } +func (s *testRenderService) CreateRenderingSession(ctx context.Context, authOpts rendering.AuthOpts, sessionOpts rendering.SessionOpts) (rendering.Session, error) { + return nil, nil +} + var _ rendering.Service = &testRenderService{} type testImageUploader struct { diff --git a/pkg/services/rendering/auth.go b/pkg/services/rendering/auth.go new file mode 100644 index 00000000000..f68067c5ed9 --- /dev/null +++ b/pkg/services/rendering/auth.go @@ -0,0 +1,120 @@ +package rendering + +import ( + "context" + "fmt" + "time" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/util" +) + +const renderKeyPrefix = "render-%s" + +type RenderUser struct { + OrgID int64 + UserID int64 + OrgRole string +} + +func (rs *RenderingService) GetRenderUser(ctx context.Context, key string) (*RenderUser, bool) { + val, err := rs.RemoteCacheService.Get(ctx, fmt.Sprintf(renderKeyPrefix, key)) + if err != nil { + rs.log.Error("Failed to get render key from cache", "error", err) + } + + if val != nil { + if user, ok := val.(*RenderUser); ok { + return user, true + } + } + + return nil, false +} + +func setRenderKey(cache *remotecache.RemoteCache, ctx context.Context, opts AuthOpts, renderKey string, expiry time.Duration) error { + err := cache.Set(ctx, fmt.Sprintf(renderKeyPrefix, renderKey), &RenderUser{ + OrgID: opts.OrgID, + UserID: opts.UserID, + OrgRole: string(opts.OrgRole), + }, expiry) + return err +} + +func generateAndSetRenderKey(cache *remotecache.RemoteCache, ctx context.Context, opts AuthOpts, expiry time.Duration) (string, error) { + key, err := util.GetRandomString(32) + if err != nil { + return "", err + } + + err = setRenderKey(cache, ctx, opts, key, expiry) + if err != nil { + return "", err + } + + return key, nil +} + +type longLivedRenderKeyProvider struct { + cache *remotecache.RemoteCache + log log.Logger + renderKey string + authOpts AuthOpts + sessionOpts SessionOpts +} + +func (rs *RenderingService) CreateRenderingSession(ctx context.Context, opts AuthOpts, sessionOpts SessionOpts) (Session, error) { + renderKey, err := generateAndSetRenderKey(rs.RemoteCacheService, ctx, opts, sessionOpts.Expiry) + if err != nil { + return nil, err + } + + return &longLivedRenderKeyProvider{ + log: rs.log, + renderKey: renderKey, + cache: rs.RemoteCacheService, + authOpts: opts, + sessionOpts: sessionOpts, + }, nil +} + +func deleteRenderKey(cache *remotecache.RemoteCache, log log.Logger, ctx context.Context, renderKey string) { + err := cache.Delete(ctx, fmt.Sprintf(renderKeyPrefix, renderKey)) + if err != nil { + log.Error("Failed to delete render key", "error", err) + } +} + +type perRequestRenderKeyProvider struct { + cache *remotecache.RemoteCache + log log.Logger + keyExpiry time.Duration +} + +func (r *perRequestRenderKeyProvider) get(ctx context.Context, opts AuthOpts) (string, error) { + return generateAndSetRenderKey(r.cache, ctx, opts, r.keyExpiry) +} + +func (r *perRequestRenderKeyProvider) afterRequest(ctx context.Context, opts AuthOpts, renderKey string) { + deleteRenderKey(r.cache, r.log, ctx, renderKey) +} + +func (r *longLivedRenderKeyProvider) get(ctx context.Context, opts AuthOpts) (string, error) { + if r.sessionOpts.RefreshExpiryOnEachRequest { + err := setRenderKey(r.cache, ctx, opts, r.renderKey, r.sessionOpts.Expiry) + if err != nil { + r.log.Error("Failed to refresh render key", "error", err, "renderKey", r.renderKey) + } + } + return r.renderKey, nil +} + +func (r *longLivedRenderKeyProvider) afterRequest(ctx context.Context, opts AuthOpts, renderKey string) { + // do nothing - renderKey from longLivedRenderKeyProvider is deleted only after session expires + // or someone calls session.Dispose() +} + +func (r *longLivedRenderKeyProvider) Dispose(ctx context.Context) { + deleteRenderKey(r.cache, r.log, ctx, r.renderKey) +} diff --git a/pkg/services/rendering/http_mode.go b/pkg/services/rendering/http_mode.go index 5511b300387..3617b3296bd 100644 --- a/pkg/services/rendering/http_mode.go +++ b/pkg/services/rendering/http_mode.go @@ -58,7 +58,7 @@ func (rs *RenderingService) renderViaHTTP(ctx context.Context, renderKey string, rendererURL.RawQuery = queryParams.Encode() // gives service some additional time to timeout and return possible errors. - reqContext, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2) + reqContext, cancel := context.WithTimeout(ctx, getRequestTimeout(opts.TimeoutOpts)) defer cancel() resp, err := rs.doRequest(reqContext, rendererURL, opts.Headers) @@ -103,7 +103,7 @@ func (rs *RenderingService) renderCSVViaHTTP(ctx context.Context, renderKey stri rendererURL.RawQuery = queryParams.Encode() // gives service some additional time to timeout and return possible errors. - reqContext, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2) + reqContext, cancel := context.WithTimeout(ctx, getRequestTimeout(opts.TimeoutOpts)) defer cancel() resp, err := rs.doRequest(reqContext, rendererURL, opts.Headers) diff --git a/pkg/services/rendering/interface.go b/pkg/services/rendering/interface.go index aa085427806..8868d43c921 100644 --- a/pkg/services/rendering/interface.go +++ b/pkg/services/rendering/interface.go @@ -26,13 +26,30 @@ const ( ThemeDark Theme = "dark" ) +type TimeoutOpts struct { + Timeout time.Duration // Timeout param passed to image-renderer service + RequestTimeoutMultiplier time.Duration // RequestTimeoutMultiplier used for plugin/HTTP request context timeout +} + +type AuthOpts struct { + OrgID int64 + UserID int64 + OrgRole models.RoleType +} + +func getRequestTimeout(opt TimeoutOpts) time.Duration { + if opt.RequestTimeoutMultiplier == 0 { + return opt.Timeout * 2 // default + } + + return opt.Timeout * opt.RequestTimeoutMultiplier +} + type Opts struct { + TimeoutOpts + AuthOpts Width int Height int - Timeout time.Duration - OrgID int64 - UserID int64 - OrgRole models.RoleType Path string Encoding string Timezone string @@ -43,10 +60,8 @@ type Opts struct { } type CSVOpts struct { - Timeout time.Duration - OrgID int64 - UserID int64 - OrgRole models.RoleType + TimeoutOpts + AuthOpts Path string Encoding string Timezone string @@ -66,11 +81,27 @@ type RenderCSVResult struct { type renderFunc func(ctx context.Context, renderKey string, options Opts) (*RenderResult, error) type renderCSVFunc func(ctx context.Context, renderKey string, options CSVOpts) (*RenderCSVResult, error) +type renderKeyProvider interface { + get(ctx context.Context, opts AuthOpts) (string, error) + afterRequest(ctx context.Context, opts AuthOpts, renderKey string) +} + +type SessionOpts struct { + Expiry time.Duration + RefreshExpiryOnEachRequest bool +} + +type Session interface { + renderKeyProvider + Dispose(ctx context.Context) +} + type Service interface { IsAvailable() bool Version() string - Render(ctx context.Context, opts Opts) (*RenderResult, error) - RenderCSV(ctx context.Context, opts CSVOpts) (*RenderCSVResult, error) + Render(ctx context.Context, opts Opts, session Session) (*RenderResult, error) + RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error) RenderErrorImage(theme Theme, error error) (*RenderResult, error) GetRenderUser(ctx context.Context, key string) (*RenderUser, bool) + CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error) } diff --git a/pkg/services/rendering/plugin_mode.go b/pkg/services/rendering/plugin_mode.go index 83bee4fe6e5..5e5c247f7b3 100644 --- a/pkg/services/rendering/plugin_mode.go +++ b/pkg/services/rendering/plugin_mode.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "time" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" ) @@ -15,7 +14,7 @@ func (rs *RenderingService) startPlugin(ctx context.Context) error { func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderKey string, opts Opts) (*RenderResult, error) { // gives plugin some additional time to timeout and return possible errors. - ctx, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2) + ctx, cancel := context.WithTimeout(ctx, getRequestTimeout(opts.TimeoutOpts)) defer cancel() filePath, err := rs.getNewFilePath(RenderPNG) @@ -62,7 +61,7 @@ func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderKey strin func (rs *RenderingService) renderCSVViaPlugin(ctx context.Context, renderKey string, opts CSVOpts) (*RenderCSVResult, error) { // gives plugin some additional time to timeout and return possible errors. - ctx, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2) + ctx, cancel := context.WithTimeout(ctx, getRequestTimeout(opts.TimeoutOpts)) defer cancel() filePath, err := rs.getNewFilePath(RenderCSV) diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go index b6ec052f959..d3b0fe1cde5 100644 --- a/pkg/services/rendering/rendering.go +++ b/pkg/services/rendering/rendering.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/remotecache" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -28,13 +27,6 @@ func init() { } const ServiceName = "RenderingService" -const renderKeyPrefix = "render-%s" - -type RenderUser struct { - OrgID int64 - UserID int64 - OrgRole string -} type RenderingService struct { log log.Logger @@ -46,9 +38,10 @@ type RenderingService struct { version string versionMutex sync.RWMutex - Cfg *setting.Cfg - RemoteCacheService *remotecache.RemoteCache - RendererPluginManager plugins.RendererManager + perRequestRenderKeyProvider renderKeyProvider + Cfg *setting.Cfg + RemoteCacheService *remotecache.RemoteCache + RendererPluginManager plugins.RendererManager } func ProvideService(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache, rm plugins.RendererManager) (*RenderingService, error) { @@ -81,11 +74,17 @@ func ProvideService(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache, rm p domain = "localhost" } + logger := log.New("rendering") s := &RenderingService{ + perRequestRenderKeyProvider: &perRequestRenderKeyProvider{ + cache: remoteCache, + log: logger, + keyExpiry: 5 * time.Minute, + }, Cfg: cfg, RemoteCacheService: remoteCache, RendererPluginManager: rm, - log: log.New("rendering"), + log: logger, domain: domain, } return s, nil @@ -195,9 +194,14 @@ func (rs *RenderingService) renderUnavailableImage() *RenderResult { } } -func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResult, error) { +func (rs *RenderingService) Render(ctx context.Context, opts Opts, session Session) (*RenderResult, error) { startTime := time.Now() - result, err := rs.render(ctx, opts) + + renderKeyProvider := rs.perRequestRenderKeyProvider + if session != nil { + renderKeyProvider = session + } + result, err := rs.render(ctx, opts, renderKeyProvider) elapsedTime := time.Since(startTime).Milliseconds() saveMetrics(elapsedTime, err, RenderPNG) @@ -205,7 +209,7 @@ func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResul return result, err } -func (rs *RenderingService) render(ctx context.Context, opts Opts) (*RenderResult, error) { +func (rs *RenderingService) render(ctx context.Context, opts Opts, renderKeyProvider renderKeyProvider) (*RenderResult, error) { if int(atomic.LoadInt32(&rs.inProgressCount)) > opts.ConcurrentLimit { rs.log.Warn("Could not render image, hit the currency limit", "concurrencyLimit", opts.ConcurrentLimit, "path", opts.Path) @@ -230,12 +234,12 @@ func (rs *RenderingService) render(ctx context.Context, opts Opts) (*RenderResul if math.IsInf(opts.DeviceScaleFactor, 0) || math.IsNaN(opts.DeviceScaleFactor) || opts.DeviceScaleFactor == 0 { opts.DeviceScaleFactor = 1 } - renderKey, err := rs.generateAndStoreRenderKey(ctx, opts.OrgID, opts.UserID, opts.OrgRole) + renderKey, err := renderKeyProvider.get(ctx, opts.AuthOpts) if err != nil { return nil, err } - defer rs.deleteRenderKey(ctx, renderKey) + defer renderKeyProvider.afterRequest(ctx, opts.AuthOpts, renderKey) defer func() { metrics.MRenderingQueue.Set(float64(atomic.AddInt32(&rs.inProgressCount, -1))) @@ -245,9 +249,14 @@ func (rs *RenderingService) render(ctx context.Context, opts Opts) (*RenderResul return rs.renderAction(ctx, renderKey, opts) } -func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts) (*RenderCSVResult, error) { +func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error) { startTime := time.Now() - result, err := rs.renderCSV(ctx, opts) + + renderKeyProvider := rs.perRequestRenderKeyProvider + if session != nil { + renderKeyProvider = session + } + result, err := rs.renderCSV(ctx, opts, renderKeyProvider) elapsedTime := time.Since(startTime).Milliseconds() saveMetrics(elapsedTime, err, RenderCSV) @@ -255,7 +264,7 @@ func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts) (*Rende return result, err } -func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts) (*RenderCSVResult, error) { +func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts, renderKeyProvider renderKeyProvider) (*RenderCSVResult, error) { if int(atomic.LoadInt32(&rs.inProgressCount)) > opts.ConcurrentLimit { return nil, ErrConcurrentLimitReached } @@ -265,12 +274,12 @@ func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts) (*Rende } rs.log.Info("Rendering", "path", opts.Path) - renderKey, err := rs.generateAndStoreRenderKey(ctx, opts.OrgID, opts.UserID, opts.OrgRole) + renderKey, err := renderKeyProvider.get(ctx, opts.AuthOpts) if err != nil { return nil, err } - defer rs.deleteRenderKey(ctx, renderKey) + defer renderKeyProvider.afterRequest(ctx, opts.AuthOpts, renderKey) defer func() { metrics.MRenderingQueue.Set(float64(atomic.AddInt32(&rs.inProgressCount, -1))) @@ -280,21 +289,6 @@ func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts) (*Rende return rs.renderCSVAction(ctx, renderKey, opts) } -func (rs *RenderingService) GetRenderUser(ctx context.Context, key string) (*RenderUser, bool) { - val, err := rs.RemoteCacheService.Get(ctx, fmt.Sprintf(renderKeyPrefix, key)) - if err != nil { - rs.log.Error("Failed to get render key from cache", "error", err) - } - - if val != nil { - if user, ok := val.(*RenderUser); ok { - return user, true - } - } - - return nil, false -} - func (rs *RenderingService) getNewFilePath(rt RenderType) (string, error) { rand, err := util.GetRandomString(20) if err != nil { @@ -340,31 +334,6 @@ func (rs *RenderingService) getURL(path string) string { return fmt.Sprintf("%s://%s:%s%s/%s&render=1", protocol, rs.domain, rs.Cfg.HTTPPort, subPath, path) } -func (rs *RenderingService) generateAndStoreRenderKey(ctx context.Context, orgId, userId int64, orgRole models.RoleType) (string, error) { - key, err := util.GetRandomString(32) - if err != nil { - return "", err - } - - err = rs.RemoteCacheService.Set(ctx, fmt.Sprintf(renderKeyPrefix, key), &RenderUser{ - OrgID: orgId, - UserID: userId, - OrgRole: string(orgRole), - }, 5*time.Minute) - if err != nil { - return "", err - } - - return key, nil -} - -func (rs *RenderingService) deleteRenderKey(ctx context.Context, key string) { - err := rs.RemoteCacheService.Delete(ctx, fmt.Sprintf(renderKeyPrefix, key)) - if err != nil { - rs.log.Error("Failed to delete render key", "error", err) - } -} - func isoTimeOffsetToPosixTz(isoOffset string) string { // invert offset if strings.HasPrefix(isoOffset, "UTC+") { diff --git a/pkg/services/rendering/rendering_test.go b/pkg/services/rendering/rendering_test.go index 06ddcc7c9f3..954f739ac20 100644 --- a/pkg/services/rendering/rendering_test.go +++ b/pkg/services/rendering/rendering_test.go @@ -138,7 +138,7 @@ func TestRenderLimitImage(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { opts := Opts{Theme: tc.theme, ConcurrentLimit: 1} - result, err := rs.Render(context.Background(), opts) + result, err := rs.Render(context.Background(), opts, nil) assert.NoError(t, err) assert.Equal(t, tc.expected, result.FilePath) }) diff --git a/pkg/services/thumbs/crawler.go b/pkg/services/thumbs/crawler.go index 884712c81c9..110372c6909 100644 --- a/pkg/services/thumbs/crawler.go +++ b/pkg/services/thumbs/crawler.go @@ -139,9 +139,15 @@ func (r *simpleCrawler) Start(c *models.ReqContext, mode CrawlerMode, theme rend r.mode = mode r.opts = rendering.Opts{ - OrgID: c.OrgId, - UserID: c.UserId, - OrgRole: c.OrgRole, + AuthOpts: rendering.AuthOpts{ + OrgID: c.OrgId, + UserID: c.UserId, + OrgRole: c.OrgRole, + }, + TimeoutOpts: rendering.TimeoutOpts{ + Timeout: 10 * time.Second, + RequestTimeoutMultiplier: 3, + }, Theme: theme, ConcurrentLimit: 10, } @@ -204,14 +210,11 @@ func (r *simpleCrawler) walk() { Width: 320, Height: 240, Path: panelURL, - OrgID: r.opts.OrgID, - UserID: r.opts.UserID, - ConcurrentLimit: r.opts.ConcurrentLimit, - OrgRole: r.opts.OrgRole, + AuthOpts: r.opts.AuthOpts, + TimeoutOpts: r.opts.TimeoutOpts, Theme: r.opts.Theme, - Timeout: 10 * time.Second, DeviceScaleFactor: -5, // negative numbers will render larger then scale down - }) + }, nil) if err != nil { tlog.Warn("error getting image", "err", err) r.status.Errors++