package namespace import ( "context" "fmt" "time" "github.com/DeBrosOfficial/network/pkg/gateway" "github.com/DeBrosOfficial/network/pkg/olric" "github.com/DeBrosOfficial/network/pkg/rqlite" "github.com/DeBrosOfficial/network/pkg/systemd" "go.uber.org/zap" ) // SystemdSpawner spawns namespace cluster processes using systemd services type SystemdSpawner struct { systemdMgr *systemd.Manager namespaceBase string logger *zap.Logger } // NewSystemdSpawner creates a new systemd-based spawner func NewSystemdSpawner(namespaceBase string, logger *zap.Logger) *SystemdSpawner { return &SystemdSpawner{ systemdMgr: systemd.NewManager(namespaceBase, logger), namespaceBase: namespaceBase, logger: logger.With(zap.String("component", "systemd-spawner")), } } // SpawnRQLite starts a RQLite instance using systemd func (s *SystemdSpawner) SpawnRQLite(ctx context.Context, namespace, nodeID string, cfg rqlite.InstanceConfig) error { s.logger.Info("Spawning RQLite via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) // Build join arguments joinArgs := "" if len(cfg.JoinAddresses) > 0 { joinArgs = fmt.Sprintf("-join %s", cfg.JoinAddresses[0]) for _, addr := range cfg.JoinAddresses[1:] { joinArgs += fmt.Sprintf(",%s", addr) } } // Generate environment file envVars := map[string]string{ "HTTP_ADDR": fmt.Sprintf("0.0.0.0:%d", cfg.HTTPPort), "RAFT_ADDR": fmt.Sprintf("0.0.0.0:%d", cfg.RaftPort), "HTTP_ADV_ADDR": cfg.HTTPAdvAddress, "RAFT_ADV_ADDR": cfg.RaftAdvAddress, "JOIN_ARGS": joinArgs, "NODE_ID": nodeID, } if err := s.systemdMgr.GenerateEnvFile(namespace, nodeID, systemd.ServiceTypeRQLite, envVars); err != nil { return fmt.Errorf("failed to generate RQLite env file: %w", err) } // Start the systemd service if err := s.systemdMgr.StartService(namespace, systemd.ServiceTypeRQLite); err != nil { return fmt.Errorf("failed to start RQLite service: %w", err) } // Wait for service to be active if err := s.waitForService(namespace, systemd.ServiceTypeRQLite, 30*time.Second); err != nil { return fmt.Errorf("RQLite service did not become active: %w", err) } s.logger.Info("RQLite spawned successfully via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) return nil } // SpawnOlric starts an Olric instance using systemd func (s *SystemdSpawner) SpawnOlric(ctx context.Context, namespace, nodeID string, cfg olric.InstanceConfig) error { s.logger.Info("Spawning Olric via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) // Generate environment file (Olric uses OLRIC_SERVER_CONFIG env var, set in systemd unit) envVars := map[string]string{ // No additional env vars needed - Olric reads from config file // The config file path is set in the systemd unit file } if err := s.systemdMgr.GenerateEnvFile(namespace, nodeID, systemd.ServiceTypeOlric, envVars); err != nil { return fmt.Errorf("failed to generate Olric env file: %w", err) } // Start the systemd service if err := s.systemdMgr.StartService(namespace, systemd.ServiceTypeOlric); err != nil { return fmt.Errorf("failed to start Olric service: %w", err) } // Wait for service to be active if err := s.waitForService(namespace, systemd.ServiceTypeOlric, 30*time.Second); err != nil { return fmt.Errorf("Olric service did not become active: %w", err) } s.logger.Info("Olric spawned successfully via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) return nil } // SpawnGateway starts a Gateway instance using systemd func (s *SystemdSpawner) SpawnGateway(ctx context.Context, namespace, nodeID string, cfg gateway.InstanceConfig) error { s.logger.Info("Spawning Gateway via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) // Generate environment file envVars := map[string]string{ // Gateway uses config file, no additional env vars needed } if err := s.systemdMgr.GenerateEnvFile(namespace, nodeID, systemd.ServiceTypeGateway, envVars); err != nil { return fmt.Errorf("failed to generate Gateway env file: %w", err) } // Start the systemd service if err := s.systemdMgr.StartService(namespace, systemd.ServiceTypeGateway); err != nil { return fmt.Errorf("failed to start Gateway service: %w", err) } // Wait for service to be active if err := s.waitForService(namespace, systemd.ServiceTypeGateway, 30*time.Second); err != nil { return fmt.Errorf("Gateway service did not become active: %w", err) } s.logger.Info("Gateway spawned successfully via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) return nil } // StopRQLite stops a RQLite instance func (s *SystemdSpawner) StopRQLite(ctx context.Context, namespace, nodeID string) error { s.logger.Info("Stopping RQLite via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) return s.systemdMgr.StopService(namespace, systemd.ServiceTypeRQLite) } // StopOlric stops an Olric instance func (s *SystemdSpawner) StopOlric(ctx context.Context, namespace, nodeID string) error { s.logger.Info("Stopping Olric via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) return s.systemdMgr.StopService(namespace, systemd.ServiceTypeOlric) } // StopGateway stops a Gateway instance func (s *SystemdSpawner) StopGateway(ctx context.Context, namespace, nodeID string) error { s.logger.Info("Stopping Gateway via systemd", zap.String("namespace", namespace), zap.String("node_id", nodeID)) return s.systemdMgr.StopService(namespace, systemd.ServiceTypeGateway) } // StopAll stops all services for a namespace func (s *SystemdSpawner) StopAll(ctx context.Context, namespace string) error { s.logger.Info("Stopping all namespace services via systemd", zap.String("namespace", namespace)) return s.systemdMgr.StopAllNamespaceServices(namespace) } // waitForService waits for a systemd service to become active func (s *SystemdSpawner) waitForService(namespace string, serviceType systemd.ServiceType, timeout time.Duration) error { deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { active, err := s.systemdMgr.IsServiceActive(namespace, serviceType) if err != nil { return fmt.Errorf("failed to check service status: %w", err) } if active { return nil } time.Sleep(1 * time.Second) } return fmt.Errorf("service did not become active within %v", timeout) }