Skip to content

Commit cab13fd

Browse files
committed
machine: enforce Start can only be called once
Signed-off-by: Samuel Karp <[email protected]>
1 parent 33a913f commit cab13fd

File tree

2 files changed

+85
-18
lines changed

2 files changed

+85
-18
lines changed

machine.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"os/exec"
2323
"os/signal"
2424
"strconv"
25+
"sync"
2526
"syscall"
2627
"time"
2728

@@ -34,6 +35,8 @@ const (
3435
userAgent = "firecracker-go-sdk"
3536
)
3637

38+
var ErrAlreadyStarted = errors.New("firecracker: machine already started")
39+
3740
// Firecracker is an interface that can be used to mock
3841
// out an Firecracker agent for testing purposes.
3942
type Firecracker interface {
@@ -146,6 +149,7 @@ type Machine struct {
146149
logger *log.Entry
147150
machineConfig models.MachineConfiguration // The actual machine config as reported by Firecracker
148151
errCh chan error
152+
startOnce sync.Once
149153
}
150154

151155
// Logger returns a logrus logger appropriate for logging hypervisor messages
@@ -227,13 +231,24 @@ func NewMachine(ctx context.Context, cfg Config, opts ...Opt) (*Machine, error)
227231
// Start will iterate through the handler list and call each handler. If an
228232
// error occurred during handler execution, that error will be returned. If the
229233
// handlers succeed, then this will start the VMM instance.
234+
// Start may only be called once per Machine. Subsequent calls will return
235+
// ErrAlreadyStarted.
230236
func (m *Machine) Start(ctx context.Context) error {
231237
m.logger.Debug("Called Machine.Start()")
238+
alreadyStarted := true
239+
m.startOnce.Do(func() {
240+
m.logger.Debug("Marking Machine as Started")
241+
alreadyStarted = false
242+
})
243+
if alreadyStarted {
244+
return ErrAlreadyStarted
245+
}
246+
232247
if err := m.Handlers.Run(ctx, m); err != nil {
233248
return err
234249
}
235250

236-
return m.StartInstance(ctx)
251+
return m.startInstance(ctx)
237252
}
238253

239254
// Wait will wait until the firecracker process has finished

machine_test.go

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/firecracker-microvm/firecracker-go-sdk/fctesting"
3535
"github.com/golang/mock/gomock"
3636
log "github.com/sirupsen/logrus"
37+
"github.com/stretchr/testify/assert"
3738
)
3839

3940
const (
@@ -92,13 +93,7 @@ func TestMicroVMExecution(t *testing.T) {
9293
os.Remove(metricsFifo)
9394
}()
9495

95-
vmlinuxPath := filepath.Join(testDataPath, "./vmlinux")
96-
if _, err := os.Stat(vmlinuxPath); err != nil {
97-
t.Fatalf("Cannot find vmlinux file: %s\n"+
98-
`Verify that you have a vmlinux file at "%s" or set the `+
99-
"`%s` environment variable to the correct location.",
100-
err, vmlinuxPath, testDataPathEnv)
101-
}
96+
vmlinuxPath := getVmlinuxPath(t)
10297

10398
cfg := Config{
10499
SocketPath: socketPath,
@@ -183,19 +178,64 @@ func TestStartVMM(t *testing.T) {
183178
defer cancel()
184179
err = m.startVMM(timeout)
185180
if err != nil {
186-
t.Errorf("startVMM failed: %s", err)
187-
} else {
188-
defer m.StopVMM()
181+
t.Fatalf("startVMM failed: %s", err)
182+
}
183+
defer m.StopVMM()
189184

190-
select {
191-
case <-timeout.Done():
192-
if timeout.Err() == context.DeadlineExceeded {
193-
t.Log("firecracker ran for 250ms")
194-
} else {
195-
t.Errorf("startVMM returned %s", m.Wait(ctx))
196-
}
185+
select {
186+
case <-timeout.Done():
187+
if timeout.Err() == context.DeadlineExceeded {
188+
t.Log("firecracker ran for 250ms")
189+
} else {
190+
t.Errorf("startVMM returned %s", m.Wait(ctx))
197191
}
198192
}
193+
194+
}
195+
196+
func TestStartVMMOnce(t *testing.T) {
197+
socketPath := filepath.Join("testdata", "fc-start-vmm-test.sock")
198+
defer os.Remove(socketPath)
199+
200+
cfg := Config{
201+
SocketPath: socketPath,
202+
DisableValidation: true,
203+
KernelImagePath: getVmlinuxPath(t),
204+
MachineCfg: models.MachineConfiguration{
205+
VcpuCount: 1,
206+
},
207+
}
208+
ctx := context.Background()
209+
cmd := VMCommandBuilder{}.
210+
WithSocketPath(cfg.SocketPath).
211+
WithBin(getFirecrackerBinaryPath()).
212+
Build(ctx)
213+
m, err := NewMachine(ctx, cfg, WithProcessRunner(cmd))
214+
if err != nil {
215+
t.Fatalf("unexpected error: %v", err)
216+
}
217+
defer m.StopVMM()
218+
219+
timeout, cancel := context.WithTimeout(ctx, 250*time.Millisecond)
220+
defer cancel()
221+
err = m.Start(timeout)
222+
if err != nil {
223+
t.Fatalf("startVMM failed: %s", err)
224+
}
225+
defer m.StopVMM()
226+
err = m.Start(timeout)
227+
assert.Error(t, err, "should return an error when Start is called multiple times")
228+
assert.Equal(t, ErrAlreadyStarted, err, "should be ErrAlreadyStarted")
229+
230+
select {
231+
case <-timeout.Done():
232+
if timeout.Err() == context.DeadlineExceeded {
233+
t.Log("firecracker ran for 250ms")
234+
} else {
235+
t.Errorf("startVMM returned %s", m.Wait(ctx))
236+
}
237+
}
238+
199239
}
200240

201241
func getFirecrackerBinaryPath() string {
@@ -205,6 +245,18 @@ func getFirecrackerBinaryPath() string {
205245
return filepath.Join(testDataPath, firecrackerBinaryPath)
206246
}
207247

248+
func getVmlinuxPath(t *testing.T) string {
249+
t.Helper()
250+
vmlinuxPath := filepath.Join(testDataPath, "./vmlinux")
251+
if _, err := os.Stat(vmlinuxPath); err != nil {
252+
t.Fatalf("Cannot find vmlinux file: %s\n"+
253+
`Verify that you have a vmlinux file at "%s" or set the `+
254+
"`%s` environment variable to the correct location.",
255+
err, vmlinuxPath, testDataPathEnv)
256+
}
257+
return vmlinuxPath
258+
}
259+
208260
func testCreateMachine(ctx context.Context, t *testing.T, m *Machine) {
209261
err := m.createMachine(ctx)
210262
if err != nil {

0 commit comments

Comments
 (0)