44 "context"
55 "crypto/rand"
66 "fmt"
7- "io"
87 "time"
98
109 "github.com/projecteru2/core/log"
@@ -36,8 +35,10 @@ func (h Handler) Reseed(cmd *cobra.Command, args []string) error {
3635 return reseedVM (ctx , vm , regenMachineID )
3736}
3837
39- // reseedVM pushes fresh entropy and a CRNG reseed order over vsock, retrying because the
40- // guest agent re-listens shortly after a clone/restore resume.
38+ // reseedVM pushes fresh entropy and a CRNG reseed order over vsock. Only a failed
39+ // dial is retried — the guest agent re-listens shortly after a clone/restore resume;
40+ // once a live agent answers, its reply (success, version-skew rejection, or failure)
41+ // is final, so an old agent isn't billed the whole retry budget.
4142func reseedVM (ctx context.Context , vm * types.VM , regenMachineID bool ) error {
4243 if vm .VsockSocket == "" {
4344 return fmt .Errorf ("reseed: %w (recreate the VM to enable agent reseed)" , ErrVsockNotConfigured )
@@ -47,44 +48,66 @@ func reseedVM(ctx context.Context, vm *types.VM, regenMachineID bool) error {
4748 return fmt .Errorf ("reseed: generate entropy: %w" , err )
4849 }
4950
50- var err error
51+ var dialErr error
5152 for attempt := 1 ; attempt <= reseedMaxAttempts ; attempt ++ {
52- if err = ctx .Err (); err != nil {
53+ if err : = ctx .Err (); err != nil {
5354 return err
5455 }
55- var conn io.ReadWriteCloser
56- if conn , err = dialHybridVsock (ctx , vm .VsockSocket , hypervisor .VsockAgentPort ); err == nil {
57- err = client .Reseed (ctx , conn , entropy , regenMachineID )
58- conn .Close () //nolint:errcheck,gosec
59- if err == nil {
60- return nil
56+ conn , err := dialHybridVsock (ctx , vm .VsockSocket , hypervisor .VsockAgentPort )
57+ if err != nil {
58+ dialErr = err
59+ if attempt < reseedMaxAttempts {
60+ select {
61+ case <- ctx .Done ():
62+ return ctx .Err ()
63+ case <- time .After (reseedRetryDelay ):
64+ }
6165 }
66+ continue
6267 }
63- if attempt < reseedMaxAttempts {
64- select {
65- case <- ctx .Done ():
66- return ctx .Err ()
67- case <- time .After (reseedRetryDelay ):
68- }
68+ err = client .Reseed (ctx , conn , entropy , regenMachineID )
69+ conn .Close () //nolint:errcheck,gosec
70+ if err != nil {
71+ return fmt .Errorf ("reseed: %w" , err )
6972 }
73+ return nil
7074 }
71- return fmt .Errorf ("reseed: %w" , err )
75+ return fmt .Errorf ("reseed: dial agent: %w" , dialErr )
76+ }
77+
78+ // reseedAfterResume re-inspects then fires the best-effort reseed. Clone/Restore return a
79+ // *types.VM built in-process that never passed through ToVM, so its VsockSocket is zero;
80+ // pairing refresh with signal keeps a caller from silently no-op-ing on a stale value.
81+ func (h Handler ) reseedAfterResume (ctx context.Context , hyper hypervisor.Hypervisor , vm * types.VM , regenMachineID bool ) {
82+ signalReseed (ctx , refreshVM (ctx , hyper , vm ), regenMachineID )
7283}
7384
74- // signalReseed is the non-fatal wrapper called from clone/restore paths: skips silently on
75- // Windows guests (agent verb is Linux-only) or legacy VMs without vsock, and never fails the
76- // calling command on agent version skew.
77- func signalReseed (ctx context.Context , vm * types.VM , regenMachineID bool ) {
85+ // signalReseed is the non-fatal wrapper for clone/restore paths: it reports whether a reseed
86+ // was attempted (false = skipped for a Windows guest or a legacy VM without vsock) and never
87+ // fails the calling command on agent version skew.
88+ func signalReseed (ctx context.Context , vm * types.VM , regenMachineID bool ) bool {
7889 logger := log .WithFunc ("cmd.vm.reseed" )
7990 if vm .Config .Windows {
8091 logger .Debug (ctx , "skip reseed signal: Windows guest" )
81- return
92+ return false
8293 }
8394 if vm .VsockSocket == "" {
8495 logger .Debug (ctx , "skip reseed signal: vsock not configured" )
85- return
96+ return false
8697 }
8798 if err := reseedVM (ctx , vm , regenMachineID ); err != nil {
8899 logger .Warnf (ctx , "reseed signal failed (agent >= v0.1.6 required): %v; run 'cocoon vm reseed %s' after fixing" , err , vm .ID )
89100 }
101+ return true
102+ }
103+
104+ // refreshVM re-inspects to recover runtime-only fields (VsockSocket, PID, SocketPath) that
105+ // ToVM sets but Clone/Restore's in-process return value lacks; keeps the original on error.
106+ func refreshVM (ctx context.Context , hyper hypervisor.Hypervisor , vm * types.VM ) * types.VM {
107+ info , err := hyper .Inspect (ctx , vm .ID )
108+ if err != nil {
109+ log .WithFunc ("cmd.vm.reseed" ).Debugf (ctx , "refresh VM %s before reseed: %v" , vm .ID , err )
110+ return vm
111+ }
112+ return info
90113}
0 commit comments