refactor daemon to use Machine to manage all components

This commit is contained in:
Pavel Sviderski
2024-09-06 11:00:33 +10:00
parent a59950631b
commit 31e2b8456f
3 changed files with 130 additions and 138 deletions
+119 -26
View File
@@ -1,54 +1,147 @@
package machine
import (
"context"
"errors"
"fmt"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"log/slog"
"net"
"net/netip"
"os"
"strconv"
"uncloud/internal/machine/api/pb"
"uncloud/internal/machine/cluster"
"uncloud/internal/machine/network"
)
type Config struct {
// DataDir is the directory where the machine stores its persistent state.
DataDir string
APIAddr string
APISockPath string
}
type Machine struct {
config Config
server *grpc.Server
state *State
networkServer *grpc.Server
cluster *cluster.Server
// TODO: create localServer for unix socket.
}
func NewMachine(config *Config) (*Machine, error) {
m := &Machine{
config: *config,
server: grpc.NewServer(),
}
clusterState := cluster.NewState(cluster.StatePath(config.DataDir))
clusterServer := cluster.NewServer(clusterState)
pb.RegisterClusterServer(m.server, clusterServer)
return m, nil
}
func (m *Machine) Run() error {
listener, err := net.Listen("tcp", m.config.APIAddr)
// Load the existing machine state or create a new one.
statePath := StatePath(config.DataDir)
state, err := ParseState(statePath)
if err != nil {
return fmt.Errorf("listen API port: %w", err)
if !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("load machine state: %w", err)
}
// Generate an empty machine config with a new key pair.
slog.Info("Machine state file not found, creating a new one.", "path", statePath)
privKey, pubKey, kErr := network.NewMachineKeys()
if kErr != nil {
return nil, fmt.Errorf("generate machine keys: %w", kErr)
}
slog.Info("Generated machine key pair.", "pubkey", pubKey)
state = &State{
Network: &network.Config{
PrivateKey: privKey,
PublicKey: pubKey,
},
}
state.SetPath(statePath)
if err = state.Save(); err != nil {
return nil, fmt.Errorf("save machine state: %w", err)
}
}
slog.Info("Starting API server.", "addr", m.config.APIAddr)
if err = m.server.Serve(listener); err != nil {
return fmt.Errorf("API server failed: %w", err)
clusterStatePath := cluster.StatePath(config.DataDir)
clusterState := cluster.NewState(clusterStatePath)
if err = clusterState.Load(); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("load cluster state: %w", err)
}
slog.Info("Cluster state file not found, creating a new one.", "path", clusterStatePath)
if err = clusterState.Save(); err != nil {
return nil, fmt.Errorf("save cluster state: %w", err)
}
}
return nil
clusterServer := cluster.NewServer(clusterState)
networkServer := grpc.NewServer()
pb.RegisterClusterServer(networkServer, clusterServer)
return &Machine{
config: *config,
state: state,
networkServer: networkServer,
cluster: clusterServer,
}, nil
}
func (m *Machine) Stop() {
slog.Info("Stopping API server.")
// TODO: implement timeout for graceful shutdown.
m.server.GracefulStop()
slog.Info("API server stopped.")
func (m *Machine) Run(ctx context.Context) error {
// Use an errgroup to coordinate error handling and graceful shutdown of multiple machine components.
errGroup, ctx := errgroup.WithContext(ctx)
// Start the network only if it is configured.
if m.state.Network.IsConfigured() {
wgnet, err := network.NewWireGuardNetwork()
if err != nil {
return fmt.Errorf("create WireGuard network: %w", err)
}
if err = wgnet.Configure(*m.state.Network); err != nil {
return fmt.Errorf("configure WireGuard network: %w", err)
}
//ctx, cancel := context.WithCancel(context.Background())
//go wgnet.WatchEndpoints(ctx, peerEndpointChangeNotifier)
//addrs, err := network.ListRoutableIPs()
//if err != nil {
// return err
//}
//fmt.Println("Addresses:", addrs)
errGroup.Go(func() error {
if err = wgnet.Run(ctx); err != nil {
return fmt.Errorf("WireGuard network failed: %w", err)
}
return nil
})
} else {
slog.Info("Waiting for network configuration to start WireGuard network.")
}
// Start the machine API server if the management IP is configured for it.
if m.state.Network.ManagementIP != (netip.Addr{}) {
apiAddr := net.JoinHostPort(m.state.Network.ManagementIP.String(), strconv.Itoa(APIPort))
listener, err := net.Listen("tcp", apiAddr)
if err != nil {
return fmt.Errorf("listen API port: %w", err)
}
errGroup.Go(func() error {
slog.Info("Starting API server.", "addr", apiAddr)
if err = m.networkServer.Serve(listener); err != nil {
return fmt.Errorf("API server failed: %w", err)
}
return nil
})
}
// Shutdown goroutine.
errGroup.Go(func() error {
<-ctx.Done()
slog.Info("Stopping API server.")
// TODO: implement timeout for graceful shutdown.
m.networkServer.GracefulStop()
slog.Info("API server stopped.")
return nil
})
return errGroup.Wait()
}