mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
chore: simplify cluster controller initialisation, prepare for reset
This commit is contained in:
@@ -29,7 +29,9 @@ const (
|
|||||||
APIPort = 51000
|
APIPort = 51000
|
||||||
)
|
)
|
||||||
|
|
||||||
type networkController struct {
|
// clusterController is the main controller for the machine that is a cluster member. It manages components such as
|
||||||
|
// the WireGuard network, Corrosion service, Docker network and containers, and embedded DNS server.
|
||||||
|
type clusterController struct {
|
||||||
state *State
|
state *State
|
||||||
store *store.Store
|
store *store.Store
|
||||||
|
|
||||||
@@ -39,29 +41,26 @@ type networkController struct {
|
|||||||
server *grpc.Server
|
server *grpc.Server
|
||||||
corroService corroservice.Service
|
corroService corroservice.Service
|
||||||
dockerCli *client.Client
|
dockerCli *client.Client
|
||||||
caddyfileCtrl *caddyconfig.Controller
|
// dockerReady is signalled when Docker is configured and ready for containers.
|
||||||
|
dockerReady chan<- struct{}
|
||||||
|
caddyconfigCtrl *caddyconfig.Controller
|
||||||
|
|
||||||
// dnsServer is the embedded internal DNS server for the cluster listening on the machine IP.
|
// dnsServer is the embedded internal DNS server for the cluster listening on the machine IP.
|
||||||
dnsServer *dns.Server
|
dnsServer *dns.Server
|
||||||
dnsResolver *dns.ClusterResolver
|
dnsResolver *dns.ClusterResolver
|
||||||
|
|
||||||
// networkReady is signalled when the Docker network is configured and ready for containers.
|
|
||||||
networkReady chan<- struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNetworkController(
|
func newClusterController(
|
||||||
state *State,
|
state *State,
|
||||||
store *store.Store,
|
store *store.Store,
|
||||||
server *grpc.Server,
|
server *grpc.Server,
|
||||||
corroService corroservice.Service,
|
corroService corroservice.Service,
|
||||||
dockerCli *client.Client,
|
dockerCli *client.Client,
|
||||||
|
dockerReady chan<- struct{},
|
||||||
caddyfileCtrl *caddyconfig.Controller,
|
caddyfileCtrl *caddyconfig.Controller,
|
||||||
dnsServer *dns.Server,
|
dnsServer *dns.Server,
|
||||||
dnsResolver *dns.ClusterResolver,
|
dnsResolver *dns.ClusterResolver,
|
||||||
networkReady chan<- struct{},
|
) (*clusterController, error) {
|
||||||
) (
|
|
||||||
*networkController, error,
|
|
||||||
) {
|
|
||||||
slog.Info("Starting WireGuard network.")
|
slog.Info("Starting WireGuard network.")
|
||||||
wgnet, err := network.NewWireGuardNetwork()
|
wgnet, err := network.NewWireGuardNetwork()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,7 +68,7 @@ func newNetworkController(
|
|||||||
}
|
}
|
||||||
endpointChanges := wgnet.WatchEndpoints()
|
endpointChanges := wgnet.WatchEndpoints()
|
||||||
|
|
||||||
return &networkController{
|
return &clusterController{
|
||||||
state: state,
|
state: state,
|
||||||
store: store,
|
store: store,
|
||||||
wgnet: wgnet,
|
wgnet: wgnet,
|
||||||
@@ -77,59 +76,55 @@ func newNetworkController(
|
|||||||
server: server,
|
server: server,
|
||||||
corroService: corroService,
|
corroService: corroService,
|
||||||
dockerCli: dockerCli,
|
dockerCli: dockerCli,
|
||||||
caddyfileCtrl: caddyfileCtrl,
|
dockerReady: dockerReady,
|
||||||
|
caddyconfigCtrl: caddyfileCtrl,
|
||||||
dnsServer: dnsServer,
|
dnsServer: dnsServer,
|
||||||
dnsResolver: dnsResolver,
|
dnsResolver: dnsResolver,
|
||||||
networkReady: networkReady,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *networkController) Run(ctx context.Context) error {
|
func (cc *clusterController) Run(ctx context.Context) error {
|
||||||
if err := firewall.ConfigureIptablesChains(); err != nil {
|
if err := firewall.ConfigureIptablesChains(); err != nil {
|
||||||
return fmt.Errorf("configure iptables chains: %w", err)
|
return fmt.Errorf("configure iptables chains: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := nc.wgnet.Configure(*nc.state.Network); err != nil {
|
if err := cc.wgnet.Configure(*cc.state.Network); err != nil {
|
||||||
return fmt.Errorf("configure WireGuard network: %w", err)
|
return fmt.Errorf("configure WireGuard network: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("WireGuard network configured.")
|
slog.Info("WireGuard network configured.")
|
||||||
|
|
||||||
if nc.corroService.Running() {
|
if cc.corroService.Running() {
|
||||||
// Corrosion service was running before the WireGuard network was configured so we need to restart it.
|
// Corrosion service was running before the WireGuard network was configured so we need to restart it.
|
||||||
slog.Info("Restarting corrosion service to apply new configuration with WireGuard network.")
|
slog.Info("Restarting corrosion service to apply new configuration with WireGuard network.")
|
||||||
if err := nc.corroService.Restart(ctx); err != nil {
|
if err := cc.corroService.Restart(ctx); err != nil {
|
||||||
return fmt.Errorf("restart corrosion service: %w", err)
|
return fmt.Errorf("restart corrosion service: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
slog.Info("Starting corrosion service.")
|
slog.Info("Starting corrosion service.")
|
||||||
if err := nc.corroService.Start(ctx); err != nil {
|
if err := cc.corroService.Start(ctx); err != nil {
|
||||||
return fmt.Errorf("start corrosion service: %w", err)
|
return fmt.Errorf("start corrosion service: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Figure out if we need to manually stop the corrosion service when the context is done or just
|
|
||||||
// rely on systemd to handle service dependencies on its own.
|
|
||||||
|
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
// Start the network API server. Assume the management IP can't be changed when the network is running.
|
// Start the network API server. Assume the management IP can't be changed when the network is running.
|
||||||
apiAddr := net.JoinHostPort(nc.state.Network.ManagementIP.String(), strconv.Itoa(APIPort))
|
apiAddr := net.JoinHostPort(cc.state.Network.ManagementIP.String(), strconv.Itoa(APIPort))
|
||||||
listener, err := net.Listen("tcp", apiAddr)
|
listener, err := net.Listen("tcp", apiAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listen API port: %w", err)
|
return fmt.Errorf("listen API port: %w", err)
|
||||||
}
|
}
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
|
||||||
slog.Info("Starting network API server.", "addr", apiAddr)
|
slog.Info("Starting network API server.", "addr", apiAddr)
|
||||||
if err := nc.server.Serve(listener); err != nil {
|
if err := cc.server.Serve(listener); err != nil {
|
||||||
return fmt.Errorf("network API server failed: %w", err)
|
return fmt.Errorf("network API server failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
slog.Info("Starting embedded DNS resolver.")
|
slog.Info("Starting embedded DNS resolver.")
|
||||||
if err := nc.dnsResolver.Run(ctx); err != nil {
|
if err := cc.dnsResolver.Run(ctx); err != nil {
|
||||||
return fmt.Errorf("embedded DNS resolver failed: %w", err)
|
return fmt.Errorf("embedded DNS resolver failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -137,7 +132,7 @@ func (nc *networkController) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
slog.Info("Starting embedded DNS server.")
|
slog.Info("Starting embedded DNS server.")
|
||||||
if err := nc.dnsServer.Run(ctx); err != nil {
|
if err := cc.dnsServer.Run(ctx); err != nil {
|
||||||
return fmt.Errorf("embedded DNS server failed: %w", err)
|
return fmt.Errorf("embedded DNS server failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -145,13 +140,13 @@ func (nc *networkController) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
// Setup Docker network and synchronise containers to the cluster store.
|
// Setup Docker network and synchronise containers to the cluster store.
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
return nc.prepareAndWatchDocker(ctx)
|
return cc.prepareAndWatchDocker(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle machine changes in the cluster. Handling machine and endpoint changes should be done
|
// Handle machine changes in the cluster. Handling machine and endpoint changes should be done
|
||||||
// in separate goroutines to avoid a deadlock when reconfiguring the network.
|
// in separate goroutines to avoid a deadlock when reconfiguring the network.
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
if err := nc.handleMachineChanges(ctx); err != nil {
|
if err := cc.handleMachineChanges(ctx); err != nil {
|
||||||
return fmt.Errorf("handle new machines: %w", err)
|
return fmt.Errorf("handle new machines: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -161,24 +156,24 @@ func (nc *networkController) Run(ctx context.Context) error {
|
|||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case e, ok := <-nc.endpointChanges:
|
case e, ok := <-cc.endpointChanges:
|
||||||
if !ok {
|
if !ok {
|
||||||
// The channel was closed, stop watching for changes.
|
// The channel was closed, stop watching for changes.
|
||||||
nc.endpointChanges = nil
|
cc.endpointChanges = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
nc.state.mu.Lock()
|
cc.state.mu.Lock()
|
||||||
for i := range nc.state.Network.Peers {
|
for i := range cc.state.Network.Peers {
|
||||||
if nc.state.Network.Peers[i].PublicKey.Equal(e.PublicKey) {
|
if cc.state.Network.Peers[i].PublicKey.Equal(e.PublicKey) {
|
||||||
nc.state.Network.Peers[i].Endpoint = &e.Endpoint
|
cc.state.Network.Peers[i].Endpoint = &e.Endpoint
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := nc.state.Save(); err != nil {
|
if err := cc.state.Save(); err != nil {
|
||||||
slog.Error("Failed to save machine state.", "err", err)
|
slog.Error("Failed to save machine state.", "err", err)
|
||||||
}
|
}
|
||||||
nc.state.mu.Unlock()
|
cc.state.mu.Unlock()
|
||||||
|
|
||||||
slog.Debug("Preserved endpoint change in the machine state.",
|
slog.Debug("Preserved endpoint change in the machine state.",
|
||||||
"public_key", e.PublicKey, "endpoint", e.Endpoint)
|
"public_key", e.PublicKey, "endpoint", e.Endpoint)
|
||||||
@@ -189,49 +184,56 @@ func (nc *networkController) Run(ctx context.Context) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
if err := nc.wgnet.Run(ctx); err != nil {
|
if err := cc.wgnet.Run(ctx); err != nil {
|
||||||
return fmt.Errorf("WireGuard network failed: %w", err)
|
return fmt.Errorf("WireGuard network failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
slog.Info("Starting Caddyconfig controller.")
|
slog.Info("Starting caddyconfig controller.")
|
||||||
if err := nc.caddyfileCtrl.Run(ctx); err != nil {
|
if err := cc.caddyconfigCtrl.Run(ctx); err != nil {
|
||||||
//goland:noinspection GoErrorStringFormat
|
return fmt.Errorf("caddyconfig controller failed: %w", err)
|
||||||
return fmt.Errorf("Caddyconfig controller failed: %w", err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for the context to be done and stop the network API server.
|
// Wait for the context to be done and stop the network API server.
|
||||||
errGroup.Go(func() error {
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
slog.Info("Stopping network API server.")
|
slog.Info("Stopping network API server.")
|
||||||
// TODO: implement timeout for graceful shutdown.
|
// TODO: implement timeout for graceful shutdown.
|
||||||
nc.server.GracefulStop()
|
cc.server.GracefulStop()
|
||||||
slog.Info("Network API server stopped.")
|
slog.Info("Network API server stopped.")
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return errGroup.Wait()
|
// Wait for all controllers to finish.
|
||||||
|
err = errGroup.Wait()
|
||||||
|
|
||||||
|
// It's safe to stop the Corrosion service after the controllers depending on it and API server are stopped.
|
||||||
|
if corroErr := cc.corroService.Stop(ctx); corroErr != nil {
|
||||||
|
slog.Error("Failed to stop corrosion service.", "err", corroErr)
|
||||||
|
err = errors.Join(err, fmt.Errorf("stop corrosion service: %w", corroErr))
|
||||||
|
} else {
|
||||||
|
slog.Info("Corrosion service stopped.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareAndWatchDocker configures the Docker network and watches local Docker containers to sync them
|
// prepareAndWatchDocker configures the Docker network and watches local Docker containers to sync them
|
||||||
// to the cluster store.
|
// to the cluster store.
|
||||||
func (nc *networkController) prepareAndWatchDocker(ctx context.Context) error {
|
func (cc *clusterController) prepareAndWatchDocker(ctx context.Context) error {
|
||||||
manager := docker.NewManager(nc.dockerCli, nc.state.ID, nc.store)
|
manager := docker.NewManager(cc.dockerCli, cc.state.ID, cc.store)
|
||||||
if err := manager.WaitDaemonReady(ctx); err != nil {
|
if err := manager.WaitDaemonReady(ctx); err != nil {
|
||||||
return fmt.Errorf("wait for Docker daemon: %w", err)
|
return fmt.Errorf("wait for Docker daemon: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.EnsureUncloudNetwork(ctx, nc.state.Network.Subnet, nc.dnsServer.ListenAddr()); err != nil {
|
if err := manager.EnsureUncloudNetwork(ctx, cc.state.Network.Subnet, cc.dnsServer.ListenAddr()); err != nil {
|
||||||
return fmt.Errorf("ensure Docker network: %w", err)
|
return fmt.Errorf("ensure Docker network: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Docker network configured.")
|
slog.Info("Docker network configured.")
|
||||||
|
|
||||||
// Signal that the Docker network is ready for containers
|
// Signal that Docker is ready for containers.
|
||||||
close(nc.networkReady)
|
close(cc.dockerReady)
|
||||||
|
|
||||||
slog.Info("Watching Docker containers and syncing them to cluster store.")
|
slog.Info("Watching Docker containers and syncing them to cluster store.")
|
||||||
// Retry to watch and sync containers until the context is done.
|
// Retry to watch and sync containers until the context is done.
|
||||||
@@ -259,7 +261,7 @@ func (nc *networkController) prepareAndWatchDocker(ctx context.Context) error {
|
|||||||
|
|
||||||
// handleMachineChanges subscribes to machine changes in the cluster and reconfigures the network peers accordingly
|
// handleMachineChanges subscribes to machine changes in the cluster and reconfigures the network peers accordingly
|
||||||
// when changes occur.
|
// when changes occur.
|
||||||
func (nc *networkController) handleMachineChanges(ctx context.Context) error {
|
func (cc *clusterController) handleMachineChanges(ctx context.Context) error {
|
||||||
for {
|
for {
|
||||||
// Retry to subscribe to machine changes indefinitely until the context is done.
|
// Retry to subscribe to machine changes indefinitely until the context is done.
|
||||||
boff := backoff.WithContext(backoff.NewExponentialBackOff(
|
boff := backoff.WithContext(backoff.NewExponentialBackOff(
|
||||||
@@ -274,7 +276,7 @@ func (nc *networkController) handleMachineChanges(ctx context.Context) error {
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
subscribe := func() error {
|
subscribe := func() error {
|
||||||
if machines, changes, err = nc.store.SubscribeMachines(ctx); err != nil {
|
if machines, changes, err = cc.store.SubscribeMachines(ctx); err != nil {
|
||||||
slog.Info("Failed to subscribe to machine changes, retrying.", "err", err)
|
slog.Info("Failed to subscribe to machine changes, retrying.", "err", err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -292,7 +294,7 @@ func (nc *networkController) handleMachineChanges(ctx context.Context) error {
|
|||||||
// completes. Skip configuration now and apply it when the store changes are received.
|
// completes. Skip configuration now and apply it when the store changes are received.
|
||||||
if len(machines) > 0 {
|
if len(machines) > 0 {
|
||||||
slog.Info("Reconfiguring network peers with the current machines.", "machines", len(machines))
|
slog.Info("Reconfiguring network peers with the current machines.", "machines", len(machines))
|
||||||
if err = nc.configurePeers(machines); err != nil {
|
if err = cc.configurePeers(machines); err != nil {
|
||||||
slog.Error("Failed to configure peers.", "err", err)
|
slog.Error("Failed to configure peers.", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,11 +306,11 @@ func (nc *networkController) handleMachineChanges(ctx context.Context) error {
|
|||||||
// be reworked as well.
|
// be reworked as well.
|
||||||
case <-changes:
|
case <-changes:
|
||||||
slog.Info("Cluster machines changed, reconfiguring network peers.")
|
slog.Info("Cluster machines changed, reconfiguring network peers.")
|
||||||
if machines, err = nc.store.ListMachines(ctx); err != nil {
|
if machines, err = cc.store.ListMachines(ctx); err != nil {
|
||||||
slog.Error("Failed to list machines.", "err", err)
|
slog.Error("Failed to list machines.", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = nc.configurePeers(machines); err != nil {
|
if err = cc.configurePeers(machines); err != nil {
|
||||||
slog.Error("Failed to configure peers.", "err", err)
|
slog.Error("Failed to configure peers.", "err", err)
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -318,23 +320,23 @@ func (nc *networkController) handleMachineChanges(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *networkController) configurePeers(machines []*pb.MachineInfo) error {
|
func (cc *clusterController) configurePeers(machines []*pb.MachineInfo) error {
|
||||||
if len(machines) == 0 {
|
if len(machines) == 0 {
|
||||||
return fmt.Errorf("no machines to configure peers")
|
return fmt.Errorf("no machines to configure peers")
|
||||||
}
|
}
|
||||||
|
|
||||||
nc.state.mu.RLock()
|
cc.state.mu.RLock()
|
||||||
currentPeerEndpoints := make(map[string]*netip.AddrPort, len(nc.state.Network.Peers))
|
currentPeerEndpoints := make(map[string]*netip.AddrPort, len(cc.state.Network.Peers))
|
||||||
for _, p := range nc.state.Network.Peers {
|
for _, p := range cc.state.Network.Peers {
|
||||||
currentPeerEndpoints[p.PublicKey.String()] = p.Endpoint
|
currentPeerEndpoints[p.PublicKey.String()] = p.Endpoint
|
||||||
}
|
}
|
||||||
nc.state.mu.RUnlock()
|
cc.state.mu.RUnlock()
|
||||||
|
|
||||||
// Construct the list of peers from the machine configurations ensuring that the current endpoint is preserved.
|
// Construct the list of peers from the machine configurations ensuring that the current endpoint is preserved.
|
||||||
peers := make([]network.PeerConfig, 0, len(machines)-1)
|
peers := make([]network.PeerConfig, 0, len(machines)-1)
|
||||||
for _, m := range machines {
|
for _, m := range machines {
|
||||||
// Skip the current machine.
|
// Skip the current machine.
|
||||||
if m.Id == nc.state.ID {
|
if m.Id == cc.state.ID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := m.Network.Validate(); err != nil {
|
if err := m.Network.Validate(); err != nil {
|
||||||
@@ -367,17 +369,17 @@ func (nc *networkController) configurePeers(machines []*pb.MachineInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preserve the new list of peers in the machine state.
|
// Preserve the new list of peers in the machine state.
|
||||||
nc.state.mu.Lock()
|
cc.state.mu.Lock()
|
||||||
nc.state.Network.Peers = peers
|
cc.state.Network.Peers = peers
|
||||||
err := nc.state.Save()
|
err := cc.state.Save()
|
||||||
nc.state.mu.Unlock()
|
cc.state.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("save machine state: %w", err)
|
return fmt.Errorf("save machine state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nc.state.mu.RLock()
|
cc.state.mu.RLock()
|
||||||
defer nc.state.mu.RUnlock()
|
defer cc.state.mu.RUnlock()
|
||||||
if err = nc.wgnet.Configure(*nc.state.Network); err != nil {
|
if err = cc.wgnet.Configure(*cc.state.Network); err != nil {
|
||||||
return fmt.Errorf("configure network peers: %w", err)
|
return fmt.Errorf("configure network peers: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -52,6 +52,20 @@ func (s *DockerService) Start(ctx context.Context) error {
|
|||||||
return s.startNewContainer(ctx)
|
return s.startNewContainer(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerService) Stop(ctx context.Context) error {
|
||||||
|
if err := s.Client.ContainerStop(ctx, s.Name, container.StopOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("stop container %q: %w", s.Name, err)
|
||||||
|
}
|
||||||
|
slog.Debug("Corrosion Docker container stopped.", "name", s.Name)
|
||||||
|
|
||||||
|
if err := s.Client.ContainerRemove(ctx, s.Name, container.RemoveOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("remove container %q: %w", s.Name, err)
|
||||||
|
}
|
||||||
|
slog.Debug("Corrosion Docker container removed.", "name", s.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerService) Restart(ctx context.Context) error {
|
func (s *DockerService) Restart(ctx context.Context) error {
|
||||||
if err := s.Client.ContainerRestart(ctx, s.Name, container.StopOptions{}); err != nil {
|
if err := s.Client.ContainerRestart(ctx, s.Name, container.StopOptions{}); err != nil {
|
||||||
return fmt.Errorf("restart container %q: %w", s.Name, err)
|
return fmt.Errorf("restart container %q: %w", s.Name, err)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import "context"
|
|||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start(ctx context.Context) error
|
Start(ctx context.Context) error
|
||||||
|
Stop(ctx context.Context) error
|
||||||
Restart(ctx context.Context) error
|
Restart(ctx context.Context) error
|
||||||
Running() bool
|
Running() bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
package corroservice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultCommand = "corrosion"
|
|
||||||
DefaultDataDir = "/var/lib/uncloud/corrosion"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SubprocessService implements the Service interface by running the service as a subprocess.
|
|
||||||
type SubprocessService struct {
|
|
||||||
Command string
|
|
||||||
DataDir string
|
|
||||||
|
|
||||||
cmd *exec.Cmd
|
|
||||||
running bool
|
|
||||||
mu sync.Mutex
|
|
||||||
cancelWatch context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultSubprocessService() *SubprocessService {
|
|
||||||
return &SubprocessService{
|
|
||||||
Command: DefaultCommand,
|
|
||||||
DataDir: DefaultDataDir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe stop the process if this ctx is cancelled.
|
|
||||||
func (s *SubprocessService) Start(ctx context.Context) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
if s.running {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.startProcess(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubprocessService) Restart(ctx context.Context) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
if s.running {
|
|
||||||
if err := s.stopProcess(); err != nil {
|
|
||||||
return fmt.Errorf("stop process: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.startProcess(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubprocessService) Running() bool {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return s.running
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubprocessService) startProcess(ctx context.Context) error {
|
|
||||||
s.cmd = exec.Command(s.Command, "agent", "-c", filepath.Join(s.DataDir, "config.toml"))
|
|
||||||
|
|
||||||
// Redirect stdout and stderr to the logger.
|
|
||||||
stdout, err := s.cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create stdout pipe: %w", err)
|
|
||||||
}
|
|
||||||
stderr, err := s.cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create stderr pipe: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
scanner := bufio.NewScanner(stdout)
|
|
||||||
for scanner.Scan() {
|
|
||||||
slog.Info("[corrosion]: " + scanner.Text())
|
|
||||||
}
|
|
||||||
// TODO: remove
|
|
||||||
slog.Info("######## corrosion redirect go routine end ########")
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
scanner := bufio.NewScanner(stderr)
|
|
||||||
for scanner.Scan() {
|
|
||||||
slog.Error("[corrosion]: " + scanner.Text())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = s.cmd.Start(); err != nil {
|
|
||||||
return fmt.Errorf("start process: %w", err)
|
|
||||||
}
|
|
||||||
s.running = true
|
|
||||||
|
|
||||||
// Watch for process exit to update running status.
|
|
||||||
go func() {
|
|
||||||
if err := s.cmd.Wait(); err != nil {
|
|
||||||
slog.Error("corrosion process exited with error.", "code", s.cmd.ProcessState.ExitCode(), "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
s.running = false
|
|
||||||
s.mu.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TODO: figure out the waiting process
|
|
||||||
// Wait for initialization
|
|
||||||
// timer := time.NewTimer(2 * time.Second)
|
|
||||||
// defer timer.Stop()
|
|
||||||
|
|
||||||
//select {
|
|
||||||
////case <-timer.C:
|
|
||||||
//// s.running = true
|
|
||||||
//// return nil
|
|
||||||
//case <-watchCtx.Done():
|
|
||||||
// return fmt.Errorf("process failed to start")
|
|
||||||
//case <-ctx.Done():
|
|
||||||
// s.stopProcess()
|
|
||||||
// return ctx.Err()
|
|
||||||
//}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubprocessService) stopProcess() error {
|
|
||||||
if s.cmd == nil || s.cmd.Process == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
|
||||||
return fmt.Errorf("send SIGTERM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait up to 5 seconds for graceful shutdown before killing the process.
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
done <- s.cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
if err := s.cmd.Process.Kill(); err != nil {
|
|
||||||
return fmt.Errorf("kill process: %w", err)
|
|
||||||
}
|
|
||||||
case err := <-done:
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("process exited with error: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,15 @@ func (s *SystemdService) Start(ctx context.Context) error {
|
|||||||
return s.startOrRestart(ctx, "start")
|
return s.startOrRestart(ctx, "start")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SystemdService) Stop(ctx context.Context) error {
|
||||||
|
if _, err := exec.Command("systemctl", "stop", s.Unit).Output(); err != nil {
|
||||||
|
return fmt.Errorf("systemctl stop %s: %w", s.Unit, err)
|
||||||
|
}
|
||||||
|
slog.Info("Corrosion systemd service stopped.", "unit", s.Unit)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SystemdService) Restart(ctx context.Context) error {
|
func (s *SystemdService) Restart(ctx context.Context) error {
|
||||||
return s.startOrRestart(ctx, "restart")
|
return s.startOrRestart(ctx, "restart")
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-88
@@ -152,9 +152,10 @@ type Machine struct {
|
|||||||
initialised chan struct{}
|
initialised chan struct{}
|
||||||
// networkReady is signalled when the Docker network is configured and ready for containers.
|
// networkReady is signalled when the Docker network is configured and ready for containers.
|
||||||
networkReady chan struct{}
|
networkReady chan struct{}
|
||||||
// networkReadyMu protects networkReady channel operations
|
// stop cancels the Run method context to stop the machine gracefully.
|
||||||
networkReadyMu sync.RWMutex
|
stop func()
|
||||||
|
|
||||||
|
clusterCtrl *clusterController
|
||||||
// store is the cluster store backed by a distributed Corrosion database.
|
// store is the cluster store backed by a distributed Corrosion database.
|
||||||
store *store.Store
|
store *store.Store
|
||||||
cluster *cluster.Cluster
|
cluster *cluster.Cluster
|
||||||
@@ -168,6 +169,9 @@ type Machine struct {
|
|||||||
// It proxies requests to the local or remote machine API servers depending on the request targets
|
// It proxies requests to the local or remote machine API servers depending on the request targets
|
||||||
// and aggregates responses.
|
// and aggregates responses.
|
||||||
localProxyServer *grpc.Server
|
localProxyServer *grpc.Server
|
||||||
|
|
||||||
|
// mu protects the Machine from concurrent reads and writes.
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMachine(config *Config) (*Machine, error) {
|
func NewMachine(config *Config) (*Machine, error) {
|
||||||
@@ -258,10 +262,6 @@ func NewMachine(config *Config) (*Machine, error) {
|
|||||||
|
|
||||||
if m.Initialised() {
|
if m.Initialised() {
|
||||||
m.initialised <- struct{}{}
|
m.initialised <- struct{}{}
|
||||||
} else {
|
|
||||||
// For non-initialized machines, signal network is ready immediately
|
|
||||||
// since there's no cluster network to set up
|
|
||||||
close(m.networkReady)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -299,6 +299,9 @@ func (m *Machine) IP() netip.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Machine) Run(ctx context.Context) error {
|
func (m *Machine) Run(ctx context.Context) error {
|
||||||
|
// Create a cancellable context for the Run method to allow stopping the machine gracefully.
|
||||||
|
ctx, m.stop = context.WithCancel(ctx)
|
||||||
|
|
||||||
// Docker dependency is essential for the machine to function. Block until it's ready.
|
// Docker dependency is essential for the machine to function. Block until it's ready.
|
||||||
if err := docker.WaitDaemonReady(ctx, m.config.DockerClient); err != nil {
|
if err := docker.WaitDaemonReady(ctx, m.config.DockerClient); err != nil {
|
||||||
return fmt.Errorf("wait for Docker daemon: %w", err)
|
return fmt.Errorf("wait for Docker daemon: %w", err)
|
||||||
@@ -306,7 +309,7 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
// Configure and start the corrosion service on the loopback if the machine is not initialised as a cluster
|
// Configure and start the corrosion service on the loopback if the machine is not initialised as a cluster
|
||||||
// member. This provides the store required for the machine to initialise a new cluster on it. Once the machine
|
// member. This provides the store required for the machine to initialise a new cluster on it. Once the machine
|
||||||
// is initialised, the corrosion service is managed by the networkController.
|
// is initialised, the corrosion service is managed by the clusterController.
|
||||||
if !m.Initialised() {
|
if !m.Initialised() {
|
||||||
if err := m.configureCorrosion(); err != nil {
|
if err := m.configureCorrosion(); err != nil {
|
||||||
return fmt.Errorf("configure corrosion service: %w", err)
|
return fmt.Errorf("configure corrosion service: %w", err)
|
||||||
@@ -326,69 +329,48 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listen machine API unix socket %q: %w", m.config.MachineSockPath, err)
|
return fmt.Errorf("listen machine API unix socket %q: %w", m.config.MachineSockPath, err)
|
||||||
}
|
}
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
|
||||||
slog.Info("Starting local machine API server.", "path", m.config.MachineSockPath)
|
slog.Info("Starting local machine API server.", "path", m.config.MachineSockPath)
|
||||||
if err := m.localMachineServer.Serve(machineListener); err != nil {
|
if err := m.localMachineServer.Serve(machineListener); err != nil {
|
||||||
return fmt.Errorf("local machine API server failed: %w", err)
|
return fmt.Errorf("local machine API server failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Start the local API proxy server.
|
// Start the local API proxy server.
|
||||||
proxyListener, err := listenUnixSocket(m.config.UncloudSockPath)
|
proxyListener, err := listenUnixSocket(m.config.UncloudSockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listen API proxy unix socket %q: %w", m.config.UncloudSockPath, err)
|
return fmt.Errorf("listen API proxy unix socket %q: %w", m.config.UncloudSockPath, err)
|
||||||
}
|
}
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
|
||||||
slog.Info("Starting local API proxy server.", "path", m.config.UncloudSockPath)
|
slog.Info("Starting local API proxy server.", "path", m.config.UncloudSockPath)
|
||||||
if err := m.localProxyServer.Serve(proxyListener); err != nil {
|
if err := m.localProxyServer.Serve(proxyListener); err != nil {
|
||||||
return fmt.Errorf("local API proxy server failed: %w", err)
|
return fmt.Errorf("local API proxy server failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
})
|
||||||
)
|
|
||||||
// Signal that the machine is ready.
|
// Signal that the machine is ready.
|
||||||
close(m.started)
|
close(m.started)
|
||||||
|
|
||||||
// Control loop for managing components that depend on the machine being initialised as a cluster member.
|
// Wait for the machine to be initialised as a member of a cluster and run the cluster controller.
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
|
||||||
if !m.Initialised() {
|
if !m.Initialised() {
|
||||||
slog.Info(
|
slog.Info(
|
||||||
"Waiting for the machine to be initialised as a member of a cluster " +
|
"Waiting for the machine to be initialised as a member of a cluster to start the cluster controller.",
|
||||||
"to start the network controller.",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<-m.initialised
|
||||||
var ctrl *networkController
|
|
||||||
// Error channel for communicating the termination of the network controller.
|
|
||||||
errCh := make(chan error)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
// Wait for the machine to be initialised as a member of a cluster to start the network controller.
|
|
||||||
// It can be reset when leaving the cluster and then re-initialised again with a new configuration.
|
|
||||||
case <-m.initialised:
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Reset networkReady channel for the new cluster configuration
|
|
||||||
m.networkReadyMu.Lock()
|
|
||||||
m.networkReady = make(chan struct{})
|
|
||||||
m.networkReadyMu.Unlock()
|
|
||||||
|
|
||||||
m.cluster.UpdateMachineID(m.state.ID)
|
m.cluster.UpdateMachineID(m.state.ID)
|
||||||
|
|
||||||
// Ensure the corrosion config is up to date, including a new gossip address if the machine
|
// Ensure the corrosion config is up to date, including a new gossip address if the machine
|
||||||
// has just joined a cluster.
|
// has just joined a cluster.
|
||||||
if err = m.configureCorrosion(); err != nil {
|
if err := m.configureCorrosion(); err != nil {
|
||||||
return fmt.Errorf("configure corrosion service: %w", err)
|
return fmt.Errorf("configure corrosion service: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Configured corrosion service.", "dir", m.config.CorrosionDir)
|
slog.Info("Configured corrosion service.", "dir", m.config.CorrosionDir)
|
||||||
|
|
||||||
slog.Info("Starting network controller.")
|
slog.Info("Starting cluster controller.")
|
||||||
// Update the proxy director's local address to the machine's management IP address, allowing
|
// Update the proxy director's local address to the machine's management IP address, allowing
|
||||||
// the proxy to identify which requests should be proxied to the local machine API server.
|
// the proxy to identify which requests should be proxied to the local machine API server.
|
||||||
m.proxyDirector.UpdateLocalAddress(m.state.Network.ManagementIP.String())
|
m.proxyDirector.UpdateLocalAddress(m.state.Network.ManagementIP.String())
|
||||||
@@ -399,11 +381,11 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create a new Caddyfile controller for managing the Caddy reverse proxy configuration.
|
// Create a new caddyconfig controller for managing the Caddy reverse proxy configuration.
|
||||||
// It will also serve the current machine ID at /.uncloud-verify to verify Caddy reachability.
|
// It will also serve the current machine ID at /.uncloud-verify to verify Caddy reachability.
|
||||||
caddyfileCtrl, err := caddyconfig.NewController(m.store, m.config.CaddyConfigPath, m.state.ID)
|
caddyconfigCtrl, err := caddyconfig.NewController(m.store, m.config.CaddyConfigPath, m.state.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create Caddyfile controller: %w", err)
|
return fmt.Errorf("create caddyconfig controller: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsResolver := dns.NewClusterResolver(m.store)
|
dnsResolver := dns.NewClusterResolver(m.store)
|
||||||
@@ -412,50 +394,33 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
return fmt.Errorf("create embedded DNS server: %w", err)
|
return fmt.Errorf("create embedded DNS server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrl, err = newNetworkController(
|
m.mu.Lock()
|
||||||
|
m.clusterCtrl, err = newClusterController(
|
||||||
m.state,
|
m.state,
|
||||||
m.store,
|
m.store,
|
||||||
proxyServer,
|
proxyServer,
|
||||||
m.config.CorrosionService,
|
m.config.CorrosionService,
|
||||||
m.config.DockerClient,
|
m.config.DockerClient,
|
||||||
caddyfileCtrl,
|
m.networkReady,
|
||||||
|
caddyconfigCtrl,
|
||||||
dnsServer,
|
dnsServer,
|
||||||
dnsResolver,
|
dnsResolver,
|
||||||
m.networkReady,
|
|
||||||
)
|
)
|
||||||
|
m.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initialise network controller: %w", err)
|
return fmt.Errorf("initialise cluster controller: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
if err = m.clusterCtrl.Run(ctx); err != nil {
|
||||||
if err = ctrl.Run(ctx); err != nil {
|
return fmt.Errorf("run cluster controller: %w", err)
|
||||||
errCh <- fmt.Errorf("run network controller: %w", err)
|
|
||||||
} else {
|
|
||||||
slog.Info("Network controller stopped.")
|
|
||||||
errCh <- nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
case err := <-errCh:
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctrl = nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Wait for the network controller to stop before returning.
|
|
||||||
if ctrl != nil {
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
slog.Info("Cluster controller stopped.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Shutdown goroutine.
|
// Shutdown goroutine.
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
slog.Info("Stopping local machine API server.")
|
slog.Info("Stopping local machine API server.")
|
||||||
// TODO: implement timeout for graceful shutdown.
|
// TODO: implement timeout for graceful shutdown.
|
||||||
@@ -471,8 +436,7 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
m.config.DockerClient.Close()
|
m.config.DockerClient.Close()
|
||||||
return nil
|
return nil
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return errGroup.Wait()
|
return errGroup.Wait()
|
||||||
}
|
}
|
||||||
@@ -798,13 +762,10 @@ func (m *Machine) Inspect(_ context.Context, _ *emptypb.Empty) (*pb.MachineInfo,
|
|||||||
func (m *Machine) IsNetworkReady() bool {
|
func (m *Machine) IsNetworkReady() bool {
|
||||||
if !m.Initialised() {
|
if !m.Initialised() {
|
||||||
// If machine is not initialized, there's no network to check
|
// If machine is not initialized, there's no network to check
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if network is ready by checking if the networkReady channel has been closed
|
// Check if network is ready by checking if the networkReady channel has been closed
|
||||||
m.networkReadyMu.RLock()
|
|
||||||
defer m.networkReadyMu.RUnlock()
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-m.networkReady:
|
case <-m.networkReady:
|
||||||
return true
|
return true
|
||||||
@@ -821,34 +782,43 @@ func (m *Machine) WaitForNetworkReady(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a copy of the channel to wait on
|
|
||||||
m.networkReadyMu.RLock()
|
|
||||||
networkReady := m.networkReady
|
|
||||||
m.networkReadyMu.RUnlock()
|
|
||||||
|
|
||||||
// Wait for network to be ready or context to be cancelled
|
// Wait for network to be ready or context to be cancelled
|
||||||
select {
|
select {
|
||||||
case <-networkReady:
|
case <-m.networkReady:
|
||||||
return nil
|
return nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset restores the machine to a clean state, removing all cluster-related сonfiguration and data and scheduling
|
// Reset restores the machine to a clean state, removing all cluster-related configuration and data and scheduling
|
||||||
// a graceful shutdown. The uncloud daemon will restart the machine if managed by systemd.
|
// a graceful shutdown. The uncloud daemon will restart the machine if managed by systemd.
|
||||||
func (m *Machine) Reset(ctx context.Context, _ *pb.ResetRequest) (*emptypb.Empty, error) {
|
func (m *Machine) Reset(ctx context.Context, _ *pb.ResetRequest) (*emptypb.Empty, error) {
|
||||||
|
if !m.Initialised() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
slog.Info("Resetting machine to a clean state.")
|
slog.Info("Resetting machine to a clean state.")
|
||||||
|
|
||||||
// TODO: stop and remove all managed service containers.
|
// TODO: stop and remove all managed service containers.
|
||||||
// TODO: check if the request is coming from the unix or network socket. For the network socket, the reset should
|
// TODO: check if the request is coming from the unix or network socket. For the network socket, the reset should
|
||||||
// be called in a separate goroutine to avoid blocking the RPC response.
|
// be called in a separate goroutine to avoid blocking the RPC response.
|
||||||
// TODO: stop the network controller
|
|
||||||
// TODO: implement and call Cleanup on the network controller to remove Docker network, WG interface, iptables
|
|
||||||
// rules, corrosion state, ?stop corrosion service.
|
|
||||||
// TODO: stop the machine and remove the machine.json state. The daemon should restart it to a clean state.
|
|
||||||
|
|
||||||
return &emptypb.Empty{}, status.Error(codes.Unimplemented, "reset machine is not implemented yet")
|
// TODO: Stop the machine asynchronously. The gRPC servers will wait for this request to complete before stopping.
|
||||||
|
go func() {
|
||||||
|
m.stop()
|
||||||
|
// TODO: wait for the cluster controller to stop.
|
||||||
|
// TODO: Cleanup cluster controller (WG network, Docker network, iptables rules, etc.)
|
||||||
|
|
||||||
|
// TODO: uncomment after testing all other cleanup steps.
|
||||||
|
//if err := os.RemoveAll(m.config.DataDir); err != nil {
|
||||||
|
// slog.Error("Failed to remove data directory storing persistent machine state.",
|
||||||
|
// "path", m.config.DataDir, "err", err)
|
||||||
|
// return nil, status.Errorf(codes.Internal, "remove data directory on machine '%s': %v", m.config.DataDir, err)
|
||||||
|
//}
|
||||||
|
//slog.Info("Removed data directory storing persistent machine state.", "path", m.config.DataDir)
|
||||||
|
}()
|
||||||
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InspectService returns detailed information about a service and its containers stored in the cluster store.
|
// InspectService returns detailed information about a service and its containers stored in the cluster store.
|
||||||
|
|||||||
Reference in New Issue
Block a user