@@ -5,7 +5,10 @@ package guestagent
55
66import (
77 "context"
8+ "encoding/json"
9+ "errors"
810 "os"
11+ "path/filepath"
912 "reflect"
1013 "time"
1114
@@ -19,7 +22,7 @@ import (
1922 "github.com/lima-vm/lima/v2/pkg/guestagent/timesync"
2023)
2124
22- func New (ctx context.Context , ticker ticker.Ticker ) (Agent , error ) {
25+ func New (ctx context.Context , ticker ticker.Ticker , runtimeDir string ) (Agent , error ) {
2326 socketsLister , err := sockets .NewLister ()
2427 if err != nil {
2528 return nil , err
@@ -28,25 +31,37 @@ func New(ctx context.Context, ticker ticker.Ticker) (Agent, error) {
2831 ticker : ticker ,
2932 socketLister : socketsLister ,
3033 kubernetesServiceWatcher : kubernetesservice .NewServiceWatcher (),
34+ runtimeDir : runtimeDir ,
3135 }
3236
3337 go a .kubernetesServiceWatcher .Start (ctx )
34- go a .fixSystemTimeSkew ()
38+ go a .fixSystemTimeSkew (ctx )
39+
40+ go func () {
41+ <- ctx .Done ()
42+ logrus .Debug ("Closing the agent" )
43+ if err := a .Close (); err != nil {
44+ logrus .Errorf ("error on agent.Close(): %v" , err )
45+ }
46+ }()
3547
3648 return a , nil
3749}
3850
51+ var _ Agent = (* agent )(nil )
52+
3953type agent struct {
4054 // Ticker is like time.Ticker.
4155 // We can't use inotify for /proc/net/tcp, so we need this ticker to
4256 // reload /proc/net/tcp.
4357 ticker ticker.Ticker
4458 socketLister * sockets.Lister
4559 kubernetesServiceWatcher * kubernetesservice.ServiceWatcher
60+ runtimeDir string
4661}
4762
4863type eventState struct {
49- ports []* api.IPPort
64+ Ports []* api.IPPort `json:"ports,omitempty"`
5065}
5166
5267func comparePorts (old , neww []* api.IPPort ) (added , removed []* api.IPPort ) {
@@ -82,13 +97,13 @@ func (a *agent) collectEvent(ctx context.Context, st eventState) (*api.Event, ev
8297 err error
8398 )
8499 newSt := st
85- newSt .ports , err = a .LocalPorts (ctx )
100+ newSt .Ports , err = a .LocalPorts (ctx )
86101 if err != nil {
87102 ev .Errors = append (ev .Errors , err .Error ())
88103 ev .Time = timestamppb .Now ()
89104 return ev , newSt
90105 }
91- ev .AddedLocalPorts , ev .RemovedLocalPorts = comparePorts (st .ports , newSt .ports )
106+ ev .AddedLocalPorts , ev .RemovedLocalPorts = comparePorts (st .Ports , newSt .Ports )
92107 ev .Time = timestamppb .Now ()
93108 return ev , newSt
94109}
@@ -102,8 +117,16 @@ func isEventEmpty(ev *api.Event) bool {
102117func (a * agent ) Events (ctx context.Context , ch chan * api.Event ) {
103118 defer close (ch )
104119 tickerCh := a .ticker .Chan ()
105- defer a .ticker .Stop ()
106- var st eventState
120+
121+ st , err := a .LoadEventState ()
122+ if err != nil {
123+ logrus .Errorf ("failed to load state: %v" , err )
124+ }
125+ defer func () {
126+ if err := a .SaveEventState (st ); err != nil {
127+ logrus .Errorf ("failed to save state: %v" , err )
128+ }
129+ }()
107130 for {
108131 var ev * api.Event
109132 ev , st = a .collectEvent (ctx , st )
@@ -115,6 +138,7 @@ func (a *agent) Events(ctx context.Context, ch chan *api.Event) {
115138 return
116139 case _ , ok := <- tickerCh :
117140 if ! ok {
141+ logrus .Debug ("ticker channel closed" )
118142 return
119143 }
120144 logrus .Debug ("tick!" )
@@ -190,7 +214,7 @@ func (a *agent) Info(ctx context.Context) (*api.Info, error) {
190214
191215const deltaLimit = 2 * time .Second
192216
193- func (a * agent ) fixSystemTimeSkew () {
217+ func (a * agent ) fixSystemTimeSkew (ctx context. Context ) {
194218 logrus .Info ("fixSystemTimeSkew(): monitoring system time skew" )
195219 for {
196220 ok , err := timesync .HasRTC ()
@@ -217,6 +241,13 @@ func (a *agent) fixSystemTimeSkew() {
217241 logrus .Infof ("fixSystemTimeSkew: system time synchronized with rtc" )
218242 break
219243 }
244+ select {
245+ case <- ctx .Done ():
246+ logrus .Debug ("fixSystemTimeSkew: context done, exiting" )
247+ ticker .Stop ()
248+ return
249+ default :
250+ }
220251 }
221252 ticker .Stop ()
222253 }
@@ -239,5 +270,42 @@ func (a *agent) Close() error {
239270 return err
240271 }
241272 }
273+ a .ticker .Stop ()
242274 return nil
243275}
276+
277+ const eventStateFileName = "event-state.json"
278+
279+ // LoadEventState loads the event state from a file in JSON format.
280+ // If the file does not exist, it returns an empty eventState with no error.
281+ // The saved eventState is expected to be removed on OS restart.
282+ func (a * agent ) LoadEventState () (eventState , error ) {
283+ logrus .Debug ("Loading event state" )
284+ path := filepath .Join (a .runtimeDir , eventStateFileName )
285+ data , err := os .ReadFile (path )
286+ if err != nil {
287+ if errors .Is (err , os .ErrNotExist ) {
288+ return eventState {}, nil
289+ }
290+ return eventState {}, err
291+ }
292+ var st eventState
293+ if err := json .Unmarshal (data , & st ); err != nil {
294+ return eventState {}, err
295+ }
296+ // We don't remove the file after loading for debugging purposes.
297+ return st , nil
298+ }
299+
300+ // SaveEventState saves the event state to a file in JSON format.
301+ // It overwrites the file if it already exists.
302+ // The saved eventState is expected to be removed on OS restart.
303+ func (a * agent ) SaveEventState (st eventState ) error {
304+ logrus .Debug ("Saving event state" )
305+ data , err := json .Marshal (st )
306+ if err != nil {
307+ return err
308+ }
309+ path := filepath .Join (a .runtimeDir , eventStateFileName )
310+ return os .WriteFile (path , data , 0o644 )
311+ }
0 commit comments