cloud/bmaas/server: init
This adds the BMaaS server alongside its first functionality: serving an
Agent heartbeat API.
This allows (untrusted) Agents to communicate with the rest of the
system by submitting heartbeats which may include a hardware report.
The BMaaS server will likely grow to implement further functionality as
described in its README.
Change-Id: I1ede02121b3700079cbb11295525f4c167ee1e7d
Reviewed-on: https://review.monogon.dev/c/monogon/+/988
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/bmaas/server/server.go b/cloud/bmaas/server/server.go
new file mode 100644
index 0000000..97fb393
--- /dev/null
+++ b/cloud/bmaas/server/server.go
@@ -0,0 +1,111 @@
+package server
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/reflection"
+ "k8s.io/klog/v2"
+
+ "source.monogon.dev/cloud/bmaas/bmdb"
+ apb "source.monogon.dev/cloud/bmaas/server/api"
+ "source.monogon.dev/cloud/lib/component"
+)
+
+type Config struct {
+ Component component.ComponentConfig
+ BMDB bmdb.BMDB
+
+ // PublicListenAddress is the address at which the 'public' (agent-facing) gRPC
+ // server listener will run.
+ PublicListenAddress string
+}
+
+// TODO(q3k): factor this out to BMDB library?
+func runtimeInfo() string {
+ hostname, _ := os.Hostname()
+ if hostname == "" {
+ hostname = "UNKNOWN"
+ }
+ return fmt.Sprintf("host %s", hostname)
+}
+
+func (c *Config) RegisterFlags() {
+ c.Component.RegisterFlags("srv")
+ c.BMDB.ComponentName = "srv"
+ c.BMDB.RuntimeInfo = runtimeInfo()
+ c.BMDB.Database.RegisterFlags("bmdb")
+
+ flag.StringVar(&c.PublicListenAddress, "srv_public_grpc_listen_address", ":8080", "Address to listen at for public/user gRPC connections for bmdbsrv")
+}
+
+type Server struct {
+ Config Config
+
+ // ListenGRPC will contain the address at which the internal gRPC server is
+ // listening after .Start() has been called. This can differ from the configured
+ // value if the configuration requests any port (via :0).
+ ListenGRPC string
+ // ListenPublic will contain the address at which the 'public' (agent-facing)
+ // gRPC server is lsitening after .Start() has been called.
+ ListenPublic string
+
+ bmdb *bmdb.Connection
+ acsvc *agentCallbackService
+}
+
+func (s *Server) startPublic(ctx context.Context) {
+ g := grpc.NewServer(s.Config.Component.GRPCServerOptionsPublic()...)
+ lis, err := net.Listen("tcp", s.Config.PublicListenAddress)
+ if err != nil {
+ klog.Exitf("Could not listen: %v", err)
+ }
+ s.ListenPublic = lis.Addr().String()
+ apb.RegisterAgentCallbackServer(g, s.acsvc)
+ reflection.Register(g)
+
+ klog.Infof("Public API listening on %s", s.ListenPublic)
+ go func() {
+ err := g.Serve(lis)
+ if err != ctx.Err() {
+ klog.Exitf("Public gRPC serve failed: %v", err)
+ }
+ }()
+}
+
+func (s *Server) startInternalGRPC(ctx context.Context) {
+ g := grpc.NewServer(s.Config.Component.GRPCServerOptions()...)
+ lis, err := net.Listen("tcp", s.Config.Component.GRPCListenAddress)
+ if err != nil {
+ klog.Exitf("Could not listen: %v", err)
+ }
+ s.ListenGRPC = lis.Addr().String()
+
+ reflection.Register(g)
+ klog.Infof("Internal gRPC listening on %s", s.ListenGRPC)
+ go func() {
+ err := g.Serve(lis)
+ if err != ctx.Err() {
+ klog.Exitf("Internal gRPC serve failed: %v", err)
+ }
+ }()
+}
+
+// Start the BMaaS Server in background goroutines. This should only be called
+// once. The process will exit with debug logs if starting the server failed.
+func (s *Server) Start(ctx context.Context) {
+ conn, err := s.Config.BMDB.Open(true)
+ if err != nil {
+ klog.Exitf("Failed to connect to BMDB: %v", err)
+ }
+ s.acsvc = &agentCallbackService{
+ s: s,
+ }
+ s.bmdb = conn
+ s.startInternalGRPC(ctx)
+ s.startPublic(ctx)
+}