Skip to content

Commit abd0815

Browse files
authored
Implement memory ballooning feature (#332)
Memory ballooning support on firecracker-go-sdk. Add wrappers for the Firecracker endpoints "/ballloon", "/balloon/statistics". Signed-off-by: Royce Zhao <qiqinzha@amazon.com>
1 parent 0f07b62 commit abd0815

File tree

6 files changed

+344
-2
lines changed

6 files changed

+344
-2
lines changed

balloon.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
package firecracker
14+
15+
import (
16+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
17+
)
18+
19+
// BalloonDevice is a builder that will create a balloon used to set up
20+
// the firecracker microVM.
21+
type BalloonDevice struct {
22+
balloon models.Balloon
23+
}
24+
25+
type BalloonOpt func(*models.Balloon)
26+
27+
// NewBalloonDevice will return a new BalloonDevice.
28+
func NewBalloonDevice(amountMib int64, deflateOnOom bool, opts ...BalloonOpt) BalloonDevice {
29+
b := models.Balloon{
30+
AmountMib: &amountMib,
31+
DeflateOnOom: &deflateOnOom,
32+
}
33+
34+
for _, opt := range opts {
35+
opt(&b)
36+
}
37+
38+
return BalloonDevice{balloon: b}
39+
}
40+
41+
// Build will return a new balloon
42+
func (b BalloonDevice) Build() models.Balloon {
43+
return b.balloon
44+
}
45+
46+
// WithStatsPollingIntervals is a functional option which sets the time in seconds between refreshing statistics.
47+
func WithStatsPollingIntervals(statsPollingIntervals int64) BalloonOpt {
48+
return func(d *models.Balloon) {
49+
d.StatsPollingIntervals = statsPollingIntervals
50+
}
51+
}
52+
53+
// UpdateAmountMiB sets the target size of the balloon
54+
func (b BalloonDevice) UpdateAmountMib(amountMib int64) BalloonDevice {
55+
b.balloon.AmountMib = &amountMib
56+
return b
57+
}
58+
59+
// UpdateStatsPollingIntervals sets the time in seconds between refreshing statistics.
60+
// A non-zero value will enable the statistics. Defaults to 0.
61+
func (b BalloonDevice) UpdateStatsPollingIntervals(statsPollingIntervals int64) BalloonDevice {
62+
b.balloon.StatsPollingIntervals = statsPollingIntervals
63+
return b
64+
}

balloon_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package firecracker
15+
16+
import (
17+
"reflect"
18+
"testing"
19+
20+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
21+
)
22+
23+
var (
24+
expectedAmountMib = int64(6)
25+
expectedDeflateOnOom = true
26+
expectedStatsPollingIntervals = int64(1)
27+
28+
expectedBalloon = models.Balloon{
29+
AmountMib: &expectedAmountMib,
30+
DeflateOnOom: &expectedDeflateOnOom,
31+
StatsPollingIntervals: expectedStatsPollingIntervals,
32+
}
33+
)
34+
35+
func TestNewBalloonDevice(t *testing.T) {
36+
balloon := NewBalloonDevice(expectedAmountMib, expectedDeflateOnOom, WithStatsPollingIntervals(expectedStatsPollingIntervals)).Build()
37+
if e, a := expectedBalloon, balloon; !reflect.DeepEqual(e, a) {
38+
t.Errorf("expected balloon %v, but received %v", e, a)
39+
}
40+
}
41+
42+
func TestUpdateAmountMiB(t *testing.T) {
43+
BalloonDevice := NewBalloonDevice(int64(1), expectedDeflateOnOom, WithStatsPollingIntervals(expectedStatsPollingIntervals))
44+
balloon := BalloonDevice.UpdateAmountMib(expectedAmountMib).Build()
45+
46+
if e, a := expectedBalloon, balloon; !reflect.DeepEqual(e, a) {
47+
t.Errorf("expected balloon %v, but received %v", e, a)
48+
}
49+
}
50+
51+
func TestUpdateStatsPollingIntervals(t *testing.T) {
52+
BalloonDevice := NewBalloonDevice(expectedAmountMib, expectedDeflateOnOom)
53+
balloon := BalloonDevice.UpdateStatsPollingIntervals(expectedStatsPollingIntervals).Build()
54+
55+
if e, a := expectedBalloon, balloon; !reflect.DeepEqual(e, a) {
56+
t.Errorf("expected balloon %v, but received %v", e, a)
57+
}
58+
}

firecracker.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,80 @@ func (f *Client) PatchGuestDriveByID(ctx context.Context, driveID, pathOnHost st
397397

398398
return f.client.Operations.PatchGuestDriveByID(params)
399399
}
400+
401+
// PutBalloonOpt is a functional option to be used for the
402+
// PutBalloon API in setting any additional optional fields.
403+
type PutBalloonOpt func(*ops.PutBalloonParams)
404+
405+
// PutBalloonOpt is a wrapper for the swagger generated client to make
406+
// calling of the API easier.
407+
func (f *Client) PutBalloon(ctx context.Context, balloon *models.Balloon, opts ...PutBalloonOpt) (*ops.PutBalloonNoContent, error) {
408+
timeout, cancel := context.WithTimeout(ctx, time.Duration(f.firecrackerRequestTimeout)*time.Millisecond)
409+
defer cancel()
410+
411+
params := ops.NewPutBalloonParamsWithContext(timeout)
412+
params.SetBody(balloon)
413+
for _, opt := range opts {
414+
opt(params)
415+
}
416+
417+
return f.client.Operations.PutBalloon(params)
418+
}
419+
420+
// DescribeBalloonConfig is a wrapper for the swagger generated client to make
421+
// calling of the API easier.
422+
func (f *Client) DescribeBalloonConfig(ctx context.Context) (*ops.DescribeBalloonConfigOK, error) {
423+
params := ops.NewDescribeBalloonConfigParams()
424+
params.SetContext(ctx)
425+
params.SetTimeout(time.Duration(f.firecrackerRequestTimeout) * time.Millisecond)
426+
427+
return f.client.Operations.DescribeBalloonConfig(params)
428+
}
429+
430+
// PatchBalloonOpt is a functional option to be used for the PatchBalloon API in setting
431+
// any additional optional fields.
432+
type PatchBalloonOpt func(*ops.PatchBalloonParams)
433+
434+
// PatchBalloon is a wrapper for the swagger generated client to make calling of the
435+
// API easier.
436+
func (f *Client) PatchBalloon(ctx context.Context, ballonUpdate *models.BalloonUpdate, opts ...PatchBalloonOpt) (*ops.PatchBalloonNoContent, error) {
437+
timeout, cancel := context.WithTimeout(ctx, time.Duration(f.firecrackerRequestTimeout)*time.Millisecond)
438+
defer cancel()
439+
440+
params := ops.NewPatchBalloonParamsWithContext(timeout)
441+
params.SetBody(ballonUpdate)
442+
for _, opt := range opts {
443+
opt(params)
444+
}
445+
446+
return f.client.Operations.PatchBalloon(params)
447+
}
448+
449+
// DescribeBalloonStats is a wrapper for the swagger generated client to make calling of the
450+
// API easier.
451+
func (f *Client) DescribeBalloonStats(ctx context.Context) (*ops.DescribeBalloonStatsOK, error) {
452+
params := ops.NewDescribeBalloonStatsParams()
453+
params.SetContext(ctx)
454+
params.SetTimeout(time.Duration(f.firecrackerRequestTimeout) * time.Millisecond)
455+
456+
return f.client.Operations.DescribeBalloonStats(params)
457+
}
458+
459+
// PatchBalloonStatsIntervalOpt is a functional option to be used for the PatchBalloonStatsInterval API in setting
460+
// any additional optional fields.
461+
type PatchBalloonStatsIntervalOpt func(*ops.PatchBalloonStatsIntervalParams)
462+
463+
// PatchBalloonStatsInterval is a wrapper for the swagger generated client to make calling of the
464+
// API easier.
465+
func (f *Client) PatchBalloonStatsInterval(ctx context.Context, balloonStatsUpdate *models.BalloonStatsUpdate, opts ...PatchBalloonStatsIntervalOpt) (*ops.PatchBalloonStatsIntervalNoContent, error) {
466+
timeout, cancel := context.WithTimeout(ctx, time.Duration(f.firecrackerRequestTimeout)*time.Millisecond)
467+
defer cancel()
468+
469+
params := ops.NewPatchBalloonStatsIntervalParamsWithContext(timeout)
470+
params.SetBody(balloonStatsUpdate)
471+
for _, opt := range opts {
472+
opt(params)
473+
}
474+
475+
return f.client.Operations.PatchBalloonStatsInterval(params)
476+
}

handlers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
LinkFilesToRootFSHandlerName = "fcinit.LinkFilesToRootFS"
3535
SetupNetworkHandlerName = "fcinit.SetupNetwork"
3636
SetupKernelArgsHandlerName = "fcinit.SetupKernelArgs"
37+
CreateBalloonHandlerName = "fcint.CreateBalloon"
3738

3839
ValidateCfgHandlerName = "validate.Cfg"
3940
ValidateJailerCfgHandlerName = "validate.JailerCfg"
@@ -268,6 +269,17 @@ var ConfigMmdsHandler = Handler{
268269
},
269270
}
270271

272+
// NewCreateBalloonHandler is a named handler that put a memory balloon into the
273+
// firecracker process.
274+
func NewCreateBalloonHandler(amountMib int64, deflateOnOom bool, StatsPollingIntervals int64) Handler {
275+
return Handler{
276+
Name: CreateBalloonHandlerName,
277+
Fn: func(ctx context.Context, m *Machine) error {
278+
return m.CreateBalloon(ctx, amountMib, deflateOnOom, StatsPollingIntervals)
279+
},
280+
}
281+
}
282+
271283
var defaultFcInitHandlerList = HandlerList{}.Append(
272284
SetupNetworkHandler,
273285
SetupKernelArgsHandler,

machine.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,3 +1084,79 @@ func (m *Machine) CreateSnapshot(ctx context.Context, memFilePath, snapshotPath
10841084
m.logger.Debug("snapshot created successfully")
10851085
return nil
10861086
}
1087+
1088+
// CreateBalloon creates a balloon device if one does not exist
1089+
func (m *Machine) CreateBalloon(ctx context.Context, amountMib int64, deflateOnOom bool, statsPollingIntervals int64, opts ...PutBalloonOpt) error {
1090+
balloon := models.Balloon{
1091+
AmountMib: &amountMib,
1092+
DeflateOnOom: &deflateOnOom,
1093+
StatsPollingIntervals: statsPollingIntervals,
1094+
}
1095+
_, err := m.client.PutBalloon(ctx, &balloon, opts...)
1096+
1097+
if err != nil {
1098+
m.logger.Errorf("Create balloon device failed : %s", err)
1099+
return err
1100+
}
1101+
1102+
m.logger.Debug("Created balloon device successful")
1103+
return nil
1104+
}
1105+
1106+
// GetBalloonConfig gets the current balloon device configuration.
1107+
func (m *Machine) GetBalloonConfig(ctx context.Context) (models.Balloon, error) {
1108+
var balloonConfig models.Balloon
1109+
resp, err := m.client.DescribeBalloonConfig(ctx)
1110+
if err != nil {
1111+
m.logger.Errorf("Getting balloonConfig: %s", err)
1112+
return balloonConfig, err
1113+
}
1114+
1115+
balloonConfig = *resp.Payload
1116+
m.logger.Debug("GetBalloonConfig successful")
1117+
return balloonConfig, err
1118+
}
1119+
1120+
// UpdateBalloon will update an existing balloon device, before or after machine startup
1121+
func (m *Machine) UpdateBalloon(ctx context.Context, amountMib int64, opts ...PatchBalloonOpt) error {
1122+
ballonUpdate := models.BalloonUpdate{
1123+
AmountMib: &amountMib,
1124+
}
1125+
_, err := m.client.PatchBalloon(ctx, &ballonUpdate, opts...)
1126+
if err != nil {
1127+
m.logger.Errorf("Update balloon device failed : %s", err)
1128+
return err
1129+
}
1130+
1131+
m.logger.Debug("Update balloon device successful")
1132+
return nil
1133+
}
1134+
1135+
// GetBalloonStats gets the latest balloon device statistics, only if enabled pre-boot.
1136+
func (m *Machine) GetBalloonStats(ctx context.Context) (models.BalloonStats, error) {
1137+
var balloonStats models.BalloonStats
1138+
resp, err := m.client.DescribeBalloonStats(ctx)
1139+
if err != nil {
1140+
m.logger.Errorf("Getting balloonStats: %s", err)
1141+
return balloonStats, err
1142+
}
1143+
balloonStats = *resp.Payload
1144+
m.logger.Debug("GetBalloonStats successful")
1145+
return balloonStats, nil
1146+
}
1147+
1148+
// UpdateBalloon will update a balloon device statistics polling interval.
1149+
// Statistics cannot be turned on/off after boot.
1150+
func (m *Machine) UpdateBalloonStats(ctx context.Context, statsPollingIntervals int64, opts ...PatchBalloonStatsIntervalOpt) error {
1151+
balloonStatsUpdate := models.BalloonStatsUpdate{
1152+
StatsPollingIntervals: &statsPollingIntervals,
1153+
}
1154+
1155+
if _, err := m.client.PatchBalloonStatsInterval(ctx, &balloonStatsUpdate, opts...); err != nil {
1156+
m.logger.Errorf("UpdateBalloonStats failed: %v", err)
1157+
return err
1158+
}
1159+
1160+
m.logger.Debug("UpdateBalloonStats successful")
1161+
return nil
1162+
}

0 commit comments

Comments
 (0)