blob: 0d188c262e3ceb8e8794da5911a8af8be6690687 [file] [log] [blame]
Serge Bazanski4abeb132022-10-11 11:32:19 +02001package server
2
3import (
4 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02005 "errors"
Serge Bazanski4abeb132022-10-11 11:32:19 +02006 "flag"
7 "fmt"
8 "net"
9 "os"
10
Serge Bazanski42f13462023-04-19 15:00:06 +020011 "github.com/cenkalti/backoff/v4"
Serge Bazanski4abeb132022-10-11 11:32:19 +020012 "google.golang.org/grpc"
13 "google.golang.org/grpc/reflection"
14 "k8s.io/klog/v2"
15
16 "source.monogon.dev/cloud/bmaas/bmdb"
Serge Bazanskic50f6942023-04-24 18:27:22 +020017 "source.monogon.dev/cloud/bmaas/bmdb/metrics"
Serge Bazanski77628312023-02-15 23:33:22 +010018 "source.monogon.dev/cloud/bmaas/bmdb/webug"
Serge Bazanski4abeb132022-10-11 11:32:19 +020019 apb "source.monogon.dev/cloud/bmaas/server/api"
20 "source.monogon.dev/cloud/lib/component"
21)
22
23type Config struct {
24 Component component.ComponentConfig
25 BMDB bmdb.BMDB
Serge Bazanski77628312023-02-15 23:33:22 +010026 Webug webug.Config
Serge Bazanski4abeb132022-10-11 11:32:19 +020027
28 // PublicListenAddress is the address at which the 'public' (agent-facing) gRPC
29 // server listener will run.
30 PublicListenAddress string
31}
32
33// TODO(q3k): factor this out to BMDB library?
34func runtimeInfo() string {
35 hostname, _ := os.Hostname()
36 if hostname == "" {
37 hostname = "UNKNOWN"
38 }
39 return fmt.Sprintf("host %s", hostname)
40}
41
42func (c *Config) RegisterFlags() {
43 c.Component.RegisterFlags("srv")
44 c.BMDB.ComponentName = "srv"
45 c.BMDB.RuntimeInfo = runtimeInfo()
46 c.BMDB.Database.RegisterFlags("bmdb")
Serge Bazanski77628312023-02-15 23:33:22 +010047 c.Webug.RegisterFlags()
Serge Bazanski4abeb132022-10-11 11:32:19 +020048
49 flag.StringVar(&c.PublicListenAddress, "srv_public_grpc_listen_address", ":8080", "Address to listen at for public/user gRPC connections for bmdbsrv")
50}
51
52type Server struct {
53 Config Config
54
55 // ListenGRPC will contain the address at which the internal gRPC server is
56 // listening after .Start() has been called. This can differ from the configured
57 // value if the configuration requests any port (via :0).
58 ListenGRPC string
59 // ListenPublic will contain the address at which the 'public' (agent-facing)
60 // gRPC server is lsitening after .Start() has been called.
61 ListenPublic string
62
63 bmdb *bmdb.Connection
64 acsvc *agentCallbackService
Serge Bazanski42f13462023-04-19 15:00:06 +020065
66 sessionC chan *bmdb.Session
67}
68
69// sessionWorker emits a valid BMDB session to sessionC as long as ctx is active.
70func (s *Server) sessionWorker(ctx context.Context) {
71 var session *bmdb.Session
72 for {
73 if session == nil || session.Expired() {
74 klog.Infof("Starting new session...")
75 bo := backoff.NewExponentialBackOff()
76 err := backoff.Retry(func() error {
77 var err error
Serge Bazanskic50f6942023-04-24 18:27:22 +020078 session, err = s.bmdb.StartSession(ctx, bmdb.SessionOption{Processor: metrics.ProcessorBMSRV})
Serge Bazanski42f13462023-04-19 15:00:06 +020079 if err != nil {
80 klog.Errorf("Failed to start session: %v", err)
81 return err
82 } else {
83 return nil
84 }
85 }, backoff.WithContext(bo, ctx))
86 if err != nil {
87 // If something's really wrong just crash.
88 klog.Exitf("Gave up on starting session: %v", err)
89 }
90 klog.Infof("New session: %s", session.UUID)
91 }
92
93 select {
94 case <-ctx.Done():
95 return
96 case s.sessionC <- session:
97 }
98 }
99}
100
101func (s *Server) session(ctx context.Context) (*bmdb.Session, error) {
102 select {
103 case sess := <-s.sessionC:
104 return sess, nil
105 case <-ctx.Done():
106 return nil, ctx.Err()
107 }
Serge Bazanski4abeb132022-10-11 11:32:19 +0200108}
109
110func (s *Server) startPublic(ctx context.Context) {
111 g := grpc.NewServer(s.Config.Component.GRPCServerOptionsPublic()...)
112 lis, err := net.Listen("tcp", s.Config.PublicListenAddress)
113 if err != nil {
114 klog.Exitf("Could not listen: %v", err)
115 }
116 s.ListenPublic = lis.Addr().String()
117 apb.RegisterAgentCallbackServer(g, s.acsvc)
118 reflection.Register(g)
119
120 klog.Infof("Public API listening on %s", s.ListenPublic)
121 go func() {
122 err := g.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200123 if !errors.Is(err, ctx.Err()) {
Serge Bazanski4abeb132022-10-11 11:32:19 +0200124 klog.Exitf("Public gRPC serve failed: %v", err)
125 }
126 }()
127}
128
129func (s *Server) startInternalGRPC(ctx context.Context) {
130 g := grpc.NewServer(s.Config.Component.GRPCServerOptions()...)
131 lis, err := net.Listen("tcp", s.Config.Component.GRPCListenAddress)
132 if err != nil {
133 klog.Exitf("Could not listen: %v", err)
134 }
135 s.ListenGRPC = lis.Addr().String()
136
137 reflection.Register(g)
138 klog.Infof("Internal gRPC listening on %s", s.ListenGRPC)
139 go func() {
140 err := g.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200141 if !errors.Is(err, ctx.Err()) {
Serge Bazanski4abeb132022-10-11 11:32:19 +0200142 klog.Exitf("Internal gRPC serve failed: %v", err)
143 }
144 }()
145}
146
147// Start the BMaaS Server in background goroutines. This should only be called
148// once. The process will exit with debug logs if starting the server failed.
149func (s *Server) Start(ctx context.Context) {
Serge Bazanskic50f6942023-04-24 18:27:22 +0200150 reg := s.Config.Component.PrometheusRegistry()
151 s.Config.BMDB.EnableMetrics(reg)
Serge Bazanskifbda89e2023-04-24 17:43:58 +0200152 s.Config.Component.StartPrometheus(ctx)
153
Serge Bazanski4abeb132022-10-11 11:32:19 +0200154 conn, err := s.Config.BMDB.Open(true)
155 if err != nil {
156 klog.Exitf("Failed to connect to BMDB: %v", err)
157 }
158 s.acsvc = &agentCallbackService{
159 s: s,
160 }
161 s.bmdb = conn
Serge Bazanski42f13462023-04-19 15:00:06 +0200162 s.sessionC = make(chan *bmdb.Session)
163 go s.sessionWorker(ctx)
Serge Bazanski4abeb132022-10-11 11:32:19 +0200164 s.startInternalGRPC(ctx)
165 s.startPublic(ctx)
Serge Bazanski77628312023-02-15 23:33:22 +0100166 go func() {
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200167 if err := s.Config.Webug.Start(ctx, conn); err != nil && !errors.Is(err, ctx.Err()) {
Serge Bazanski77628312023-02-15 23:33:22 +0100168 klog.Exitf("Failed to start webug: %v", err)
169 }
170 }()
Serge Bazanski4abeb132022-10-11 11:32:19 +0200171}