package server import ( "context" "encoding/json" "errors" "time" "sultan/pkg/license" "sultan/pkg/store" "sultan/pkg/stream" ) // ensureLease stellt die Lease eines verbundenen Knotens aus bzw. erneuert // sie ab renew_after und schickt sie per lease_renew. Mit always wird auch // eine noch gültige Lease erneut zugestellt (beim Verbinden). func (s *Server) ensureLease(nodeID string, always bool) { ctx := context.Background() now := time.Now() current, err := s.store.GetLease(ctx, nodeID) if err == nil && now.Before(current.RenewAfter) { if always { s.sendLease(nodeID, []byte(current.Document)) } return } if err != nil && !errors.Is(err, store.ErrNotFound) { s.log.Error("Lease lesen", "node", nodeID, "err", err) return } node, err := s.store.GetNode(ctx, nodeID) if err != nil { return } tenant, err := s.store.GetTenant(ctx, node.TenantID) if err != nil || tenant.Status == store.TenantQuarantined { return } lic := licenseOf(tenant) terms, ok := license.TermsFor(lic.Tier, node.Maintenance.Active(now), lic.ValidUntil, now, s.cfg.LeaseDuration, nil) if !ok { s.log.Warn("Keine Lease: Vertrag abgelaufen", "node", nodeID, "tenant", tenant.ID) return } lease, err := (&license.Issuer{Key: s.master}).IssueLease(lic, nodeID, terms, now) if err != nil { s.log.Error("Lease ausstellen", "node", nodeID, "err", err) return } doc, _ := json.Marshal(lease) if err := s.store.PutLease(ctx, store.Lease{ NodeID: nodeID, IssuedAt: lease.IssuedAt, RenewAfter: lease.RenewAfter, ValidUntil: lease.ValidUntil, Document: string(doc), }); err != nil { s.log.Error("Lease speichern", "node", nodeID, "err", err) return } s.log.Info("Lease ausgestellt", "node", nodeID, "valid_until", terms.ValidUntil, "renew_after", terms.RenewAfter) s.sendLease(nodeID, doc) } func (s *Server) sendLease(nodeID string, doc []byte) { if err := s.hub.Send(nodeID, stream.Event{Type: stream.EventLeaseRenew, Payload: doc}); err != nil { s.log.Debug("Lease nicht zugestellt", "node", nodeID, "err", err) } } // leaseValid meldet, ob der Knoten eine noch gültige Lease hat. func (s *Server) leaseValid(nodeID string) bool { l, err := s.store.GetLease(context.Background(), nodeID) return err == nil && time.Now().Before(l.ValidUntil) } // dispatchCommands stellt Befehle aus der Warteschlange zu. func (s *Server) dispatchCommands(nodeID string) { ctx := context.Background() cmds, err := s.store.PendingNodeCommands(ctx, nodeID) if err != nil { s.log.Error("Befehle lesen", "node", nodeID, "err", err) return } for _, c := range cmds { payload, _ := json.Marshal(stream.NodeCommand{Command: c.Command, Reason: c.Reason}) if err := s.hub.Send(nodeID, stream.Event{Type: stream.EventNodeCommand, Payload: payload}); err != nil { return // später erneut (nächster Refresh oder nächste Verbindung) } if err := s.store.MarkNodeCommandSent(ctx, c.ID); err != nil { s.log.Error("Befehl als gesendet markieren", "node", nodeID, "err", err) return } s.log.Info("Befehl zugestellt", "node", nodeID, "command", c.Command, "reason", c.Reason) } }