Skip to content

Commit 7662d45

Browse files
committed
Implemented snapshot loading capabilities
Signed-off-by: David Son <[email protected]>
1 parent f0a967e commit 7662d45

File tree

6 files changed

+166
-3
lines changed

6 files changed

+166
-3
lines changed

firecracker.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,23 @@ func (f *Client) CreateSnapshot(ctx context.Context, snapshotParams *models.Snap
265265
return f.client.Operations.CreateSnapshot(params)
266266
}
267267

268+
// LoadSnapshotOpt is a functional option to be used for the
269+
// LoadSnapshot API in setting any additional optional fields.
270+
type LoadSnapshotOpt func(*ops.LoadSnapshotParams)
271+
272+
// LoadSnapshot is a wrapper for the swagger generated client to make
273+
// calling of the API easier.
274+
func (f *Client) LoadSnapshot(ctx context.Context, snapshotParams *models.SnapshotLoadParams, opts ...LoadSnapshotOpt) (*ops.LoadSnapshotNoContent, error) {
275+
params := ops.NewLoadSnapshotParamsWithContext(ctx)
276+
params.SetBody(snapshotParams)
277+
278+
for _, opt := range opts {
279+
opt(params)
280+
}
281+
282+
return f.client.Operations.LoadSnapshot(params)
283+
}
284+
268285
// CreateSyncActionOpt is a functional option to be used for the
269286
// CreateSyncAction API in setting any additional optional fields.
270287
type CreateSyncActionOpt func(*ops.CreateSyncActionParams)

handlers.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ const (
3434
LinkFilesToRootFSHandlerName = "fcinit.LinkFilesToRootFS"
3535
SetupNetworkHandlerName = "fcinit.SetupNetwork"
3636
SetupKernelArgsHandlerName = "fcinit.SetupKernelArgs"
37-
CreateBalloonHandlerName = "fcint.CreateBalloon"
37+
CreateBalloonHandlerName = "fcinit.CreateBalloon"
38+
LoadSnapshotHandlerName = "fcinit.LoadSnapshot"
3839

3940
ValidateCfgHandlerName = "validate.Cfg"
4041
ValidateJailerCfgHandlerName = "validate.JailerCfg"
@@ -280,6 +281,17 @@ func NewCreateBalloonHandler(amountMib int64, deflateOnOom bool, StatsPollingInt
280281
}
281282
}
282283

284+
// NewLoadSnapshotHandler is a named handler that loads a snapshot
285+
// from the specified filepath
286+
func NewLoadSnapshotHandler(memFilePath, snapshotPath string, opts ...LoadSnapshotOpt) Handler {
287+
return Handler{
288+
Name: LoadSnapshotHandlerName,
289+
Fn: func(ctx context.Context, m *Machine) error {
290+
return m.loadSnapshot(ctx, memFilePath, snapshotPath, opts...)
291+
},
292+
}
293+
}
294+
283295
var defaultFcInitHandlerList = HandlerList{}.Append(
284296
SetupNetworkHandler,
285297
SetupKernelArgsHandler,

machine.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ func NewMachine(ctx context.Context, cfg Config, opts ...Opt) (*Machine, error)
381381
// handlers succeed, then this will start the VMM instance.
382382
// Start may only be called once per Machine. Subsequent calls will return
383383
// ErrAlreadyStarted.
384-
func (m *Machine) Start(ctx context.Context) error {
384+
func (m *Machine) Start(ctx context.Context, opts ...StartOpt) error {
385385
m.logger.Debug("Called Machine.Start()")
386386
alreadyStarted := true
387387
m.startOnce.Do(func() {
@@ -402,6 +402,10 @@ func (m *Machine) Start(ctx context.Context) error {
402402
}
403403
}()
404404

405+
for _, opt := range opts {
406+
opt(m)
407+
}
408+
405409
err = m.Handlers.Run(ctx, m)
406410
if err != nil {
407411
return err
@@ -1093,6 +1097,20 @@ func (m *Machine) CreateSnapshot(ctx context.Context, memFilePath, snapshotPath
10931097
return nil
10941098
}
10951099

1100+
func (m *Machine) loadSnapshot(ctx context.Context, memFilePath, snapshotPath string, opts ...LoadSnapshotOpt) error {
1101+
snapshotParams := &models.SnapshotLoadParams{
1102+
MemFilePath: String(memFilePath),
1103+
SnapshotPath: String(snapshotPath),
1104+
}
1105+
1106+
if _, err := m.client.LoadSnapshot(ctx, snapshotParams, opts...); err != nil {
1107+
return fmt.Errorf("failed to load a snapshot for VM: %v", err)
1108+
}
1109+
1110+
m.logger.Debug("snapshot loaded successfully")
1111+
return nil
1112+
}
1113+
10961114
// CreateBalloon creates a balloon device if one does not exist
10971115
func (m *Machine) CreateBalloon(ctx context.Context, amountMib int64, deflateOnOom bool, statsPollingIntervals int64, opts ...PutBalloonOpt) error {
10981116
balloon := models.Balloon{

machine_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,113 @@ func TestCreateSnapshot(t *testing.T) {
17281728
}
17291729
}
17301730

1731+
func TestLoadSnapshot(t *testing.T) {
1732+
fctesting.RequiresKVM(t)
1733+
fctesting.RequiresRoot(t)
1734+
1735+
dir, err := ioutil.TempDir("", t.Name())
1736+
require.NoError(t, err)
1737+
defer os.RemoveAll(dir)
1738+
1739+
cases := []struct {
1740+
name string
1741+
createSnapshot func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string)
1742+
loadSnapshot func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string)
1743+
}{
1744+
{
1745+
name: "TestLoadSnapshot",
1746+
createSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) {
1747+
// Create a snapshot
1748+
cfg := createValidConfig(t, socketPath+".create")
1749+
m, err := NewMachine(ctx, cfg, func(m *Machine) {
1750+
// Rewriting m.cmd partially wouldn't work since Cmd has
1751+
// some unexported members
1752+
args := m.cmd.Args[1:]
1753+
m.cmd = exec.Command(getFirecrackerBinaryPath(), args...)
1754+
}, WithLogger(logrus.NewEntry(machineLogger)))
1755+
require.NoError(t, err)
1756+
1757+
err = m.Start(ctx)
1758+
require.NoError(t, err)
1759+
1760+
err = m.PauseVM(ctx)
1761+
require.NoError(t, err)
1762+
1763+
err = m.CreateSnapshot(ctx, memPath, snapPath)
1764+
require.NoError(t, err)
1765+
1766+
err = m.StopVMM()
1767+
require.NoError(t, err)
1768+
},
1769+
1770+
loadSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) {
1771+
cfg := createValidConfig(t, socketPath+".load")
1772+
m, err := NewMachine(ctx, cfg, func(m *Machine) {
1773+
// Rewriting m.cmd partially wouldn't work since Cmd has
1774+
// some unexported members
1775+
args := m.cmd.Args[1:]
1776+
m.cmd = exec.Command(getFirecrackerBinaryPath(), args...)
1777+
}, WithLogger(logrus.NewEntry(machineLogger)))
1778+
require.NoError(t, err)
1779+
1780+
err = m.Start(ctx, WithSnapshot(memPath, snapPath))
1781+
require.NoError(t, err)
1782+
1783+
t.Errorf("%v", m.Handlers)
1784+
1785+
err = m.ResumeVM(ctx)
1786+
require.NoError(t, err)
1787+
1788+
err = m.StopVMM()
1789+
require.NoError(t, err)
1790+
},
1791+
},
1792+
{
1793+
name: "TestLoadSnapshot without create",
1794+
createSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) {
1795+
1796+
},
1797+
1798+
loadSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) {
1799+
cfg := createValidConfig(t, socketPath+".load")
1800+
m, err := NewMachine(ctx, cfg, func(m *Machine) {
1801+
// Rewriting m.cmd partially wouldn't work since Cmd has
1802+
// some unexported members
1803+
args := m.cmd.Args[1:]
1804+
m.cmd = exec.Command(getFirecrackerBinaryPath(), args...)
1805+
}, WithLogger(logrus.NewEntry(machineLogger)))
1806+
require.NoError(t, err)
1807+
1808+
err = m.Start(ctx, WithSnapshot(memPath, snapPath))
1809+
require.Error(t, err)
1810+
},
1811+
},
1812+
}
1813+
1814+
for _, c := range cases {
1815+
t.Run(c.name, func(t *testing.T) {
1816+
ctx := context.Background()
1817+
1818+
// Set snap and mem paths
1819+
socketPath := filepath.Join(dir, fsSafeTestName.Replace(t.Name()))
1820+
snapPath := socketPath + "SnapFile"
1821+
memPath := socketPath + "MemFile"
1822+
defer os.Remove(socketPath)
1823+
defer os.Remove(snapPath)
1824+
defer os.Remove(memPath)
1825+
1826+
// Tee logs for validation:
1827+
var logBuffer bytes.Buffer
1828+
machineLogger := logrus.New()
1829+
machineLogger.Out = io.MultiWriter(os.Stderr, &logBuffer)
1830+
1831+
c.createSnapshot(ctx, machineLogger, socketPath, snapPath, memPath)
1832+
c.loadSnapshot(ctx, machineLogger, socketPath, snapPath, memPath)
1833+
})
1834+
}
1835+
1836+
}
1837+
17311838
func testCreateBalloon(ctx context.Context, t *testing.T, m *Machine) {
17321839
if err := m.CreateBalloon(ctx, testBalloonMemory, testBalloonDeflateOnOom, testStatsPollingIntervals); err != nil {
17331840
t.Errorf("Create balloon device failed from testAttachBalloon: %s", err)

machineiface.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var _ MachineIface = (*Machine)(nil)
2323
// MachineIface can be used for mocking and testing of the Machine. The Machine
2424
// is subject to change, meaning this interface would change.
2525
type MachineIface interface {
26-
Start(context.Context) error
26+
Start(context.Context, ...StartOpt) error
2727
StopVMM() error
2828
Shutdown(context.Context) error
2929
Wait(context.Context) error

opts.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
// Opt represents a functional option to help modify functionality of a Machine.
2323
type Opt func(*Machine)
24+
type StartOpt func(*Machine)
2425

2526
// WithClient will use the client in place rather than the client constructed
2627
// during bootstrapping of the machine. This option is useful for mocking out
@@ -47,3 +48,11 @@ func WithProcessRunner(cmd *exec.Cmd) Opt {
4748
machine.cmd = cmd
4849
}
4950
}
51+
52+
// WithSnapshot will allow for the machine to start using a given snapshot.
53+
func WithSnapshot(memFilePath, snapshotPath string, opts ...LoadSnapshotOpt) StartOpt {
54+
return func(m *Machine) {
55+
m.Handlers.FcInit.AppendAfter("SetupKernelArgsHandler",
56+
NewLoadSnapshotHandler(memFilePath, snapshotPath, opts...))
57+
}
58+
}

0 commit comments

Comments
 (0)