mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
refactor daemon to use Machine to manage all components
This commit is contained in:
@@ -3,8 +3,8 @@ package machine
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"uncloud/internal/daemon"
|
||||||
"uncloud/internal/machine"
|
"uncloud/internal/machine"
|
||||||
"uncloud/internal/machine/daemon"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tokenOptions struct {
|
type tokenOptions struct {
|
||||||
|
|||||||
+10
-111
@@ -2,14 +2,9 @@ package daemon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"uncloud/internal/machine"
|
"uncloud/internal/machine"
|
||||||
"uncloud/internal/machine/api/pb"
|
"uncloud/internal/machine/api/pb"
|
||||||
"uncloud/internal/machine/cluster"
|
"uncloud/internal/machine/cluster"
|
||||||
@@ -108,119 +103,23 @@ func InitCluster(dataDir, machineName string, netPrefix netip.Prefix, users []*p
|
|||||||
|
|
||||||
type Daemon struct {
|
type Daemon struct {
|
||||||
machine *machine.Machine
|
machine *machine.Machine
|
||||||
|
|
||||||
state *machine.State
|
|
||||||
cluster *cluster.Server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(dataDir string) (*Daemon, error) {
|
func New(dataDir string) (*Daemon, error) {
|
||||||
mstatePath := machine.StatePath(dataDir)
|
config := &machine.Config{
|
||||||
mstate, err := machine.ParseState(mstatePath)
|
DataDir: dataDir,
|
||||||
|
}
|
||||||
|
mach, err := machine.NewMachine(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
return nil, fmt.Errorf("init machine: %w", err)
|
||||||
return nil, fmt.Errorf("load machine config: %w", err)
|
|
||||||
}
|
|
||||||
// Generate an empty machine config with a new key pair.
|
|
||||||
slog.Info("Machine config not found, creating a new one.", "path", mstatePath)
|
|
||||||
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)
|
|
||||||
|
|
||||||
mstate = &machine.State{
|
|
||||||
Network: &network.Config{
|
|
||||||
PrivateKey: privKey,
|
|
||||||
PublicKey: pubKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mstate.SetPath(mstatePath)
|
|
||||||
if err = mstate.Save(); err != nil {
|
|
||||||
return nil, fmt.Errorf("save machine config: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cstatePath := cluster.StatePath(dataDir)
|
return &Daemon{
|
||||||
cstate := cluster.NewState(cstatePath)
|
machine: mach,
|
||||||
if err = cstate.Load(); err != nil {
|
}, nil
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, fmt.Errorf("load cluster state: %w", err)
|
|
||||||
}
|
|
||||||
slog.Info("Cluster state not found, creating a new one.", "path", cstatePath)
|
|
||||||
if err = cstate.Save(); err != nil {
|
|
||||||
return nil, fmt.Errorf("save cluster state: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d := &Daemon{
|
|
||||||
state: mstate,
|
|
||||||
}
|
|
||||||
if mstate.Network.IsConfigured() {
|
|
||||||
config := &machine.Config{
|
|
||||||
APIAddr: net.JoinHostPort(mstate.Network.ManagementIP.String(), strconv.Itoa(machine.APIPort)),
|
|
||||||
}
|
|
||||||
d.machine, err = machine.NewMachine(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("init machine: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) Run(ctx context.Context) error {
|
func (d *Daemon) Run(ctx context.Context) error {
|
||||||
// Use an errgroup to coordinate error handling and graceful shutdown of multiple daemon components.
|
slog.Info("Starting machine.")
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
return d.machine.Run(ctx)
|
||||||
|
|
||||||
// Start the network only if it is configured.
|
|
||||||
if d.state.Network.IsConfigured() {
|
|
||||||
wgnet, err := network.NewWireGuardNetwork()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create WireGuard network: %w", err)
|
|
||||||
}
|
|
||||||
if err = wgnet.Configure(*d.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.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.cluster != nil {
|
|
||||||
errGroup.Go(func() error {
|
|
||||||
slog.Info("Starting cluster.")
|
|
||||||
if err := d.machine.Run(); err != nil {
|
|
||||||
return fmt.Errorf("cluster failed: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown goroutine.
|
|
||||||
errGroup.Go(func() error {
|
|
||||||
<-ctx.Done()
|
|
||||||
if d.cluster != nil {
|
|
||||||
slog.Info("Stopping cluster.")
|
|
||||||
d.machine.Stop()
|
|
||||||
slog.Info("Cluster server stopped.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return errGroup.Wait()
|
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-26
@@ -1,54 +1,147 @@
|
|||||||
package machine
|
package machine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"uncloud/internal/machine/api/pb"
|
"uncloud/internal/machine/api/pb"
|
||||||
"uncloud/internal/machine/cluster"
|
"uncloud/internal/machine/cluster"
|
||||||
|
"uncloud/internal/machine/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// DataDir is the directory where the machine stores its persistent state.
|
// DataDir is the directory where the machine stores its persistent state.
|
||||||
DataDir string
|
DataDir string
|
||||||
APIAddr string
|
|
||||||
APISockPath string
|
APISockPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Machine struct {
|
type Machine struct {
|
||||||
config Config
|
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) {
|
func NewMachine(config *Config) (*Machine, error) {
|
||||||
m := &Machine{
|
// Load the existing machine state or create a new one.
|
||||||
config: *config,
|
statePath := StatePath(config.DataDir)
|
||||||
server: grpc.NewServer(),
|
state, err := ParseState(statePath)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
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 {
|
clusterStatePath := cluster.StatePath(config.DataDir)
|
||||||
return fmt.Errorf("API server failed: %w", err)
|
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() {
|
func (m *Machine) Run(ctx context.Context) error {
|
||||||
slog.Info("Stopping API server.")
|
// Use an errgroup to coordinate error handling and graceful shutdown of multiple machine components.
|
||||||
// TODO: implement timeout for graceful shutdown.
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
m.server.GracefulStop()
|
|
||||||
slog.Info("API server stopped.")
|
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user