blob: 8cedf2bb2aa675e93d15f309b443fd81d921034b [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanski4abeb132022-10-11 11:32:19 +02004package server
5
6import (
7 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02008 "errors"
Serge Bazanski4abeb132022-10-11 11:32:19 +02009 "flag"
10 "fmt"
11 "net"
12 "os"
13
Serge Bazanski42f13462023-04-19 15:00:06 +020014 "github.com/cenkalti/backoff/v4"
Serge Bazanski4abeb132022-10-11 11:32:19 +020015 "google.golang.org/grpc"
16 "google.golang.org/grpc/reflection"
17 "k8s.io/klog/v2"
18
19 "source.monogon.dev/cloud/bmaas/bmdb"
Serge Bazanskic50f6942023-04-24 18:27:22 +020020 "source.monogon.dev/cloud/bmaas/bmdb/metrics"
Serge Bazanski77628312023-02-15 23:33:22 +010021 "source.monogon.dev/cloud/bmaas/bmdb/webug"
Serge Bazanski4abeb132022-10-11 11:32:19 +020022 apb "source.monogon.dev/cloud/bmaas/server/api"
23 "source.monogon.dev/cloud/lib/component"
24)
25
26type Config struct {
27 Component component.ComponentConfig
28 BMDB bmdb.BMDB
Serge Bazanski77628312023-02-15 23:33:22 +010029 Webug webug.Config
Serge Bazanski4abeb132022-10-11 11:32:19 +020030
31 // PublicListenAddress is the address at which the 'public' (agent-facing) gRPC
32 // server listener will run.
33 PublicListenAddress string
34}
35
36// TODO(q3k): factor this out to BMDB library?
37func runtimeInfo() string {
38 hostname, _ := os.Hostname()
39 if hostname == "" {
40 hostname = "UNKNOWN"
41 }
42 return fmt.Sprintf("host %s", hostname)
43}
44
45func (c *Config) RegisterFlags() {
46 c.Component.RegisterFlags("srv")
47 c.BMDB.ComponentName = "srv"
48 c.BMDB.RuntimeInfo = runtimeInfo()
49 c.BMDB.Database.RegisterFlags("bmdb")
Serge Bazanski77628312023-02-15 23:33:22 +010050 c.Webug.RegisterFlags()
Serge Bazanski4abeb132022-10-11 11:32:19 +020051
52 flag.StringVar(&c.PublicListenAddress, "srv_public_grpc_listen_address", ":8080", "Address to listen at for public/user gRPC connections for bmdbsrv")
53}
54
55type Server struct {
56 Config Config
57
58 // ListenGRPC will contain the address at which the internal gRPC server is
59 // listening after .Start() has been called. This can differ from the configured
60 // value if the configuration requests any port (via :0).
61 ListenGRPC string
62 // ListenPublic will contain the address at which the 'public' (agent-facing)
63 // gRPC server is lsitening after .Start() has been called.
64 ListenPublic string
65
66 bmdb *bmdb.Connection
67 acsvc *agentCallbackService
Serge Bazanski42f13462023-04-19 15:00:06 +020068
69 sessionC chan *bmdb.Session
70}
71
72// sessionWorker emits a valid BMDB session to sessionC as long as ctx is active.
73func (s *Server) sessionWorker(ctx context.Context) {
74 var session *bmdb.Session
75 for {
76 if session == nil || session.Expired() {
77 klog.Infof("Starting new session...")
78 bo := backoff.NewExponentialBackOff()
79 err := backoff.Retry(func() error {
80 var err error
Serge Bazanskic50f6942023-04-24 18:27:22 +020081 session, err = s.bmdb.StartSession(ctx, bmdb.SessionOption{Processor: metrics.ProcessorBMSRV})
Serge Bazanski42f13462023-04-19 15:00:06 +020082 if err != nil {
83 klog.Errorf("Failed to start session: %v", err)
84 return err
85 } else {
86 return nil
87 }
88 }, backoff.WithContext(bo, ctx))
89 if err != nil {
90 // If something's really wrong just crash.
91 klog.Exitf("Gave up on starting session: %v", err)
92 }
93 klog.Infof("New session: %s", session.UUID)
94 }
95
96 select {
97 case <-ctx.Done():
98 return
99 case s.sessionC <- session:
100 }
101 }
102}
103
104func (s *Server) session(ctx context.Context) (*bmdb.Session, error) {
105 select {
106 case sess := <-s.sessionC:
107 return sess, nil
108 case <-ctx.Done():
109 return nil, ctx.Err()
110 }
Serge Bazanski4abeb132022-10-11 11:32:19 +0200111}
112
113func (s *Server) startPublic(ctx context.Context) {
114 g := grpc.NewServer(s.Config.Component.GRPCServerOptionsPublic()...)
115 lis, err := net.Listen("tcp", s.Config.PublicListenAddress)
116 if err != nil {
117 klog.Exitf("Could not listen: %v", err)
118 }
119 s.ListenPublic = lis.Addr().String()
120 apb.RegisterAgentCallbackServer(g, s.acsvc)
121 reflection.Register(g)
122
123 klog.Infof("Public API listening on %s", s.ListenPublic)
124 go func() {
125 err := g.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200126 if !errors.Is(err, ctx.Err()) {
Serge Bazanski4abeb132022-10-11 11:32:19 +0200127 klog.Exitf("Public gRPC serve failed: %v", err)
128 }
129 }()
130}
131
132func (s *Server) startInternalGRPC(ctx context.Context) {
133 g := grpc.NewServer(s.Config.Component.GRPCServerOptions()...)
134 lis, err := net.Listen("tcp", s.Config.Component.GRPCListenAddress)
135 if err != nil {
136 klog.Exitf("Could not listen: %v", err)
137 }
138 s.ListenGRPC = lis.Addr().String()
139
140 reflection.Register(g)
141 klog.Infof("Internal gRPC listening on %s", s.ListenGRPC)
142 go func() {
143 err := g.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200144 if !errors.Is(err, ctx.Err()) {
Serge Bazanski4abeb132022-10-11 11:32:19 +0200145 klog.Exitf("Internal gRPC serve failed: %v", err)
146 }
147 }()
148}
149
150// Start the BMaaS Server in background goroutines. This should only be called
151// once. The process will exit with debug logs if starting the server failed.
152func (s *Server) Start(ctx context.Context) {
Serge Bazanskic50f6942023-04-24 18:27:22 +0200153 reg := s.Config.Component.PrometheusRegistry()
154 s.Config.BMDB.EnableMetrics(reg)
Serge Bazanskifbda89e2023-04-24 17:43:58 +0200155 s.Config.Component.StartPrometheus(ctx)
156
Serge Bazanski4abeb132022-10-11 11:32:19 +0200157 conn, err := s.Config.BMDB.Open(true)
158 if err != nil {
159 klog.Exitf("Failed to connect to BMDB: %v", err)
160 }
161 s.acsvc = &agentCallbackService{
162 s: s,
163 }
164 s.bmdb = conn
Serge Bazanski42f13462023-04-19 15:00:06 +0200165 s.sessionC = make(chan *bmdb.Session)
166 go s.sessionWorker(ctx)
Serge Bazanski4abeb132022-10-11 11:32:19 +0200167 s.startInternalGRPC(ctx)
168 s.startPublic(ctx)
Serge Bazanski77628312023-02-15 23:33:22 +0100169 go func() {
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200170 if err := s.Config.Webug.Start(ctx, conn); err != nil && !errors.Is(err, ctx.Err()) {
Serge Bazanski77628312023-02-15 23:33:22 +0100171 klog.Exitf("Failed to start webug: %v", err)
172 }
173 }()
Serge Bazanski4abeb132022-10-11 11:32:19 +0200174}