package agentd import ( "context" agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1" ) // runSubscribe opens the Command stream (resuming from the last durably processed // command id) and applies each command idempotently, Ack'ing after apply // (at-least-once). It returns when ctx is cancelled or the stream errors, so the // session can reconnect and resume. func (a *Agent) runSubscribe(ctx context.Context, client agentv1.AgentServiceClient) error { nodeUUID := a.NodeUUID() stream, err := client.Subscribe(ctx, &agentv1.SubscribeRequest{ NodeUUID: nodeUUID, LastCommandID: a.lastCommandID.Load(), }) if err != nil { return err } for { cmd, err := stream.Recv() if err != nil { return err } a.handleCommand(cmd) // Ack after apply. A failed Ack just causes redelivery, which the // dedup-by-command-id + idempotent apply below tolerates. if _, err := client.Ack(ctx, &agentv1.AckRequest{ NodeUUID: nodeUUID, CommandID: cmd.CommandID, }); err != nil { return err } } } // handleCommand applies one command. Commands at or below the high-water mark are // skipped (already applied) — this makes redelivery after a dropped Ack safe. func (a *Agent) handleCommand(cmd *agentv1.Command) { if cmd.CommandID != 0 && cmd.CommandID <= a.lastCommandID.Load() { return } switch cmd.Type { case agentv1.CommandTypeUpsert: if cmd.Upsert != nil && cmd.Upsert.Credential != nil { a.sb.Upsert(credFromPB(cmd.Upsert.Credential)) } case agentv1.CommandTypeRevoke: if cmd.Revoke != nil { a.sb.Revoke(cmd.Revoke.DpUUID) } case agentv1.CommandTypeRotateCredential: if cmd.Rotate != nil && cmd.Rotate.NewCredential != nil { a.sb.Rotate(cmd.Rotate.OldDpUUID, credFromPB(cmd.Rotate.NewCredential), cmd.Rotate.GraceUntilUnix) } case agentv1.CommandTypeApplyConfig: if cmd.ApplyConfig != nil { a.sb.ApplyConfig(cmd.ApplyConfig, cmd.ApplyConfig.Credentials != nil) } case agentv1.CommandTypeLifecycle: a.handleLifecycle(cmd.Lifecycle) default: logf("ignoring command id=%d with unknown type=%d", cmd.CommandID, cmd.Type) } if cmd.CommandID > a.lastCommandID.Load() { a.lastCommandID.Store(cmd.CommandID) } } func (a *Agent) handleLifecycle(p *agentv1.LifecyclePayload) { if p == nil { return } switch p.Action { case agentv1.LifecycleActionDrain: // Draining is steered by control-plane node weight; the data plane keeps // serving existing sessions. Nothing to mutate locally. logf("lifecycle: drain") case agentv1.LifecycleActionResume: logf("lifecycle: resume") case agentv1.LifecycleActionShutdown: logf("lifecycle: shutdown requested") if a.shutdown != nil { a.shutdown() } default: logf("lifecycle: unknown action %d", p.Action) } } func credFromPB(c *agentv1.Credential) *Cred { flow := c.Flow if flow == "" { flow = DefaultFlow } return &Cred{ DpUUID: c.DpUUID, Protocol: c.Protocol, Flow: flow, ExpiresAt: c.ExpiresAtUnix, } }