FEMT: Basic frontend-service implementation (#104229)

* create the most basic frontend-server module

* expose prom metrics??

* add todo list

* move frontend-service to its own folder in services

* check error from writer.Write

* reword comment, add launch config
pull/103987/head^2
Josh Hunt 4 weeks ago committed by GitHub
parent 8e6b8fad01
commit a8aa6b74a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      .github/CODEOWNERS
  2. 10
      .vscode/launch.json
  3. 2
      pkg/modules/dependencies.go
  4. 7
      pkg/server/module_server.go
  5. 109
      pkg/services/frontend/frontend_service.go

@ -138,6 +138,7 @@
/pkg/services/dashboardversion/ @grafana/grafana-backend-group
/pkg/services/encryption/ @grafana/grafana-operator-experience-squad
/pkg/services/folder/ @grafana/grafana-search-and-storage
/pkg/services/frontend/ @grafana/grafana-frontend-platform
/pkg/services/apiserver @grafana/grafana-app-platform-squad
/pkg/services/hooks/ @grafana/grafana-backend-group
/pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad

@ -82,6 +82,16 @@
"cwd": "${workspaceFolder}",
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
},
{
"name": "Run Frontend Server",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/pkg/cmd/grafana/",
"cwd": "${workspaceFolder}",
"env": { "GF_DEFAULT_TARGET": "frontend-server", "GF_SERVER_HTTP_PORT": "3003" },
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
},
{
"name": "Attach to Chrome",
"port": 9222,

@ -9,6 +9,7 @@ const (
StorageServer string = "storage-server"
ZanzanaServer string = "zanzana-server"
InstrumentationServer string = "instrumentation-server"
FrontendServer string = "frontend-server"
)
var dependencyMap = map[string][]string{
@ -17,4 +18,5 @@ var dependencyMap = map[string][]string{
ZanzanaServer: {InstrumentationServer},
Core: {},
All: {Core},
FrontendServer: {},
}

@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/modules"
"github.com/grafana/grafana/pkg/services/authz"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/frontend"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/sql"
@ -120,7 +121,7 @@ func (s *ModuleServer) Run() error {
// only run the instrumentation server module if were not running a module that already contains an http server
m.RegisterInvisibleModule(modules.InstrumentationServer, func() (services.Service, error) {
if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) {
if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) || m.IsModuleEnabled(modules.FrontendServer) {
return services.NewBasicService(nil, nil, nil).WithName(modules.InstrumentationServer), nil
}
return NewInstrumentationService(s.log, s.cfg, s.promGatherer)
@ -151,6 +152,10 @@ func (s *ModuleServer) Run() error {
return authz.ProvideZanzanaService(s.cfg, s.features)
})
m.RegisterModule(modules.FrontendServer, func() (services.Service, error) {
return frontend.ProvideFrontendService(s.cfg, s.promGatherer)
})
m.RegisterModule(modules.All, nil)
return m.Run(s.context)

@ -0,0 +1,109 @@
package frontend
import (
"context"
"net"
"net/http"
"time"
"github.com/grafana/dskit/services"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type frontendService struct {
*services.BasicService
cfg *setting.Cfg
httpServ *http.Server
log log.Logger
errChan chan error
promGatherer prometheus.Gatherer
}
func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer) (*frontendService, error) {
s := &frontendService{
cfg: cfg,
log: log.New("frontend-server"),
promGatherer: promGatherer,
}
s.BasicService = services.NewBasicService(s.start, s.running, s.stop)
return s, nil
}
func (s *frontendService) start(ctx context.Context) error {
s.httpServ = s.newFrontendServer(ctx)
s.errChan = make(chan error)
go func() {
s.errChan <- s.httpServ.ListenAndServe()
}()
return nil
}
func (s *frontendService) running(ctx context.Context) error {
select {
case <-ctx.Done():
return nil
case err := <-s.errChan:
return err
}
}
func (s *frontendService) stop(failureReason error) error {
s.log.Info("stopping frontend server", "reason", failureReason)
if err := s.httpServ.Shutdown(context.Background()); err != nil {
s.log.Error("failed to shutdown frontend server", "error", err)
return err
}
return nil
}
func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server {
s.log.Info("starting frontend server", "addr", ":"+s.cfg.HTTPPort)
router := http.NewServeMux()
router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}))
router.HandleFunc("/", s.handleRequest)
server := &http.Server{
// 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/)
ReadHeaderTimeout: 5 * time.Second,
Addr: ":" + s.cfg.HTTPPort,
Handler: router,
BaseContext: func(_ net.Listener) context.Context { return ctx },
}
return server
}
func (s *frontendService) handleRequest(writer http.ResponseWriter, request *http.Request) {
// This should:
// - get correct asset urls from fs or cdn
// - generate a nonce
// - render them into the index.html
// - and return it to the user!
s.log.Info("handling request", "method", request.Method, "url", request.URL.String())
htmlContent := `<!DOCTYPE html>
<html>
<head>
<title>Grafana Frontend Server</title>
<style>
body {
font-family: sans-serif;
}
</style>
</head>
<body>
<h1>Grafana Frontend Server</h1>
<p>This is a simple static HTML page served by the Grafana frontend server module.</p>
</body>
</html>`
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
_, err := writer.Write([]byte(htmlContent))
if err != nil {
s.log.Error("could not write to response", "err", err)
}
}
Loading…
Cancel
Save