@@ -22,6 +22,7 @@ import (
22
22
"os/exec"
23
23
"os/signal"
24
24
"strconv"
25
+ "sync"
25
26
"syscall"
26
27
"time"
27
28
@@ -34,6 +35,8 @@ const (
34
35
userAgent = "firecracker-go-sdk"
35
36
)
36
37
38
+ var ErrAlreadyStarted = errors .New ("firecracker: machine already started" )
39
+
37
40
// Firecracker is an interface that can be used to mock
38
41
// out an Firecracker agent for testing purposes.
39
42
type Firecracker interface {
@@ -134,17 +137,23 @@ func (cfg *Config) Validate() error {
134
137
135
138
// Machine is the main object for manipulating Firecracker microVMs
136
139
type Machine struct {
140
+ // Metadata is the associated metadata that will be sent to the firecracker
141
+ // process
142
+ Metadata interface {}
143
+ // Handlers holds the set of handlers that are run for validation and start
144
+ Handlers Handlers
145
+
137
146
cfg Config
138
147
client Firecracker
139
148
cmd * exec.Cmd
140
149
logger * log.Entry
141
150
machineConfig models.MachineConfiguration // The actual machine config as reported by Firecracker
142
-
143
- // Metadata is the associated metadata that will be sent to the firecracker
144
- // process
145
- Metadata interface {}
146
- errCh chan error
147
- Handlers Handlers
151
+ // startOnce ensures that the machine can only be started once
152
+ startOnce sync. Once
153
+ // exitCh is a channel which gets closed when the VMM exits
154
+ exitCh chan struct {}
155
+ // err records any error executing the VMM
156
+ err error
148
157
}
149
158
150
159
// Logger returns a logrus logger appropriate for logging hypervisor messages
@@ -196,7 +205,9 @@ func NewMachine(ctx context.Context, cfg Config, opts ...Opt) (*Machine, error)
196
205
return nil , err
197
206
}
198
207
199
- m := & Machine {}
208
+ m := & Machine {
209
+ exitCh : make (chan struct {}),
210
+ }
200
211
logger := log .New ()
201
212
202
213
if cfg .Debug {
@@ -226,22 +237,35 @@ func NewMachine(ctx context.Context, cfg Config, opts ...Opt) (*Machine, error)
226
237
// Start will iterate through the handler list and call each handler. If an
227
238
// error occurred during handler execution, that error will be returned. If the
228
239
// handlers succeed, then this will start the VMM instance.
240
+ // Start may only be called once per Machine. Subsequent calls will return
241
+ // ErrAlreadyStarted.
229
242
func (m * Machine ) Start (ctx context.Context ) error {
230
243
m .logger .Debug ("Called Machine.Start()" )
244
+ alreadyStarted := true
245
+ m .startOnce .Do (func () {
246
+ m .logger .Debug ("Marking Machine as Started" )
247
+ alreadyStarted = false
248
+ })
249
+ if alreadyStarted {
250
+ return ErrAlreadyStarted
251
+ }
252
+
231
253
if err := m .Handlers .Run (ctx , m ); err != nil {
232
254
return err
233
255
}
234
256
235
- return m .StartInstance (ctx )
257
+ return m .startInstance (ctx )
236
258
}
237
259
238
- // Wait will wait until the firecracker process has finished
260
+ // Wait will wait until the firecracker process has finished. Wait is safe to
261
+ // call concurrently, and will deliver the same error to all callers, subject to
262
+ // each caller's context cancellation.
239
263
func (m * Machine ) Wait (ctx context.Context ) error {
240
264
select {
241
265
case <- ctx .Done ():
242
266
return ctx .Err ()
243
- case err := <- m .errCh :
244
- return err
267
+ case <- m .exitCh :
268
+ return m . err
245
269
}
246
270
}
247
271
@@ -281,11 +305,12 @@ func (m *Machine) attachDrives(ctx context.Context, drives ...models.Drive) erro
281
305
func (m * Machine ) startVMM (ctx context.Context ) error {
282
306
m .logger .Printf ("Called startVMM(), setting up a VMM on %s" , m .cfg .SocketPath )
283
307
284
- m . errCh = make (chan error )
308
+ errCh : = make (chan error )
285
309
286
310
err := m .cmd .Start ()
287
311
if err != nil {
288
312
m .logger .Errorf ("Failed to start VMM: %s" , err )
313
+ close (m .exitCh )
289
314
return err
290
315
}
291
316
m .logger .Debugf ("VMM started socket path is %s" , m .cfg .SocketPath )
@@ -300,11 +325,10 @@ func (m *Machine) startVMM(ctx context.Context) error {
300
325
os .Remove (m .cfg .SocketPath )
301
326
os .Remove (m .cfg .LogFifo )
302
327
os .Remove (m .cfg .MetricsFifo )
303
- m . errCh <- err
328
+ errCh <- err
304
329
}()
305
330
306
331
// Set up a signal handler and pass INT, QUIT, and TERM through to firecracker
307
- vmchan := make (chan error )
308
332
sigchan := make (chan os.Signal )
309
333
signal .Notify (sigchan , os .Interrupt ,
310
334
syscall .SIGQUIT ,
@@ -313,22 +337,24 @@ func (m *Machine) startVMM(ctx context.Context) error {
313
337
syscall .SIGABRT )
314
338
m .logger .Debugf ("Setting up signal handler" )
315
339
go func () {
316
- select {
317
- case sig := <- sigchan :
318
- m .logger .Printf ("Caught signal %s" , sig )
319
- m .cmd .Process .Signal (sig )
320
- case err = <- vmchan :
321
- m .errCh <- err
322
- }
340
+ sig := <- sigchan
341
+ m .logger .Printf ("Caught signal %s" , sig )
342
+ m .cmd .Process .Signal (sig )
323
343
}()
324
344
325
345
// Wait for firecracker to initialize:
326
- err = m .waitForSocket (3 * time .Second , m . errCh )
346
+ err = m .waitForSocket (3 * time .Second , errCh )
327
347
if err != nil {
328
348
msg := fmt .Sprintf ("Firecracker did not create API socket %s: %s" , m .cfg .SocketPath , err )
329
349
err = errors .New (msg )
350
+ close (m .exitCh )
330
351
return err
331
352
}
353
+ go func () {
354
+ err := <- errCh
355
+ m .err = err
356
+ close (m .exitCh )
357
+ }()
332
358
333
359
m .logger .Debugf ("returning from startVMM()" )
334
360
return nil
@@ -517,11 +543,6 @@ func (m *Machine) addVsock(ctx context.Context, dev VsockDevice) error {
517
543
return nil
518
544
}
519
545
520
- // StartInstance starts the Firecracker microVM
521
- func (m * Machine ) StartInstance (ctx context.Context ) error {
522
- return m .startInstance (ctx )
523
- }
524
-
525
546
func (m * Machine ) startInstance (ctx context.Context ) error {
526
547
info := models.InstanceActionInfo {
527
548
ActionType : models .InstanceActionInfoActionTypeInstanceStart ,
@@ -575,33 +596,25 @@ func (m *Machine) waitForSocket(timeout time.Duration, exitchan chan error) erro
575
596
ctx , cancel := context .WithTimeout (context .Background (), timeout )
576
597
defer cancel ()
577
598
578
- done := make (chan error )
579
599
ticker := time .NewTicker (10 * time .Millisecond )
580
600
581
- go func () {
582
- for {
583
- select {
584
- case <- ctx .Done ():
585
- done <- ctx .Err ()
586
- return
587
- case err := <- exitchan :
588
- done <- err
589
- return
590
- case <- ticker .C :
591
- if _ , err := os .Stat (m .cfg .SocketPath ); err != nil {
592
- continue
593
- }
594
-
595
- // Send test HTTP request to make sure socket is available
596
- if _ , err := m .client .GetMachineConfig (); err != nil {
597
- continue
598
- }
599
-
600
- done <- nil
601
- return
601
+ for {
602
+ select {
603
+ case <- ctx .Done ():
604
+ return ctx .Err ()
605
+ case err := <- exitchan :
606
+ return err
607
+ case <- ticker .C :
608
+ if _ , err := os .Stat (m .cfg .SocketPath ); err != nil {
609
+ continue
602
610
}
603
- }
604
- }()
605
611
606
- return <- done
612
+ // Send test HTTP request to make sure socket is available
613
+ if _ , err := m .client .GetMachineConfig (); err != nil {
614
+ continue
615
+ }
616
+
617
+ return nil
618
+ }
619
+ }
607
620
}
0 commit comments