Skip to content

Commit 653c342

Browse files
committed
Adding drives builder
Adds a drives builder to help users with building a series of drives without needing to know the intricate details of firecracker's API.
1 parent 3c1f5c3 commit 653c342

File tree

4 files changed

+284
-8
lines changed

4 files changed

+284
-8
lines changed

drives.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package firecracker
2+
3+
import (
4+
"strconv"
5+
6+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
7+
)
8+
9+
const rootDriveName = "root-drive"
10+
11+
// DrivesBuilder is a builder that will build an array of drives used to set up
12+
// the firecracker microVM. The DriveID will be an incrementing number starting
13+
// at one
14+
type DrivesBuilder struct {
15+
rootDrive models.Drive
16+
drives []models.Drive
17+
}
18+
19+
// NewDrivesBuilder will return a new DrivesBuilder with a given rootfs.
20+
func NewDrivesBuilder(rootDrivePath string) DrivesBuilder {
21+
return DrivesBuilder{}.WithRootDrive(rootDrivePath)
22+
}
23+
24+
// DriveOpt represents an optional function used to allow for specific
25+
// customization of the models.Drive structure.
26+
type DriveOpt func(*models.Drive)
27+
28+
// WithRootDrive will set the given builder with the a new root path. The root
29+
// drive will be set to read and write by default.
30+
func (b DrivesBuilder) WithRootDrive(rootDrivePath string, opts ...DriveOpt) DrivesBuilder {
31+
b.rootDrive = models.Drive{
32+
DriveID: String(rootDriveName),
33+
PathOnHost: &rootDrivePath,
34+
IsRootDevice: Bool(true),
35+
IsReadOnly: Bool(false),
36+
}
37+
38+
for _, opt := range opts {
39+
opt(&b.rootDrive)
40+
}
41+
42+
return b
43+
}
44+
45+
// AddDrive will add a new drive to the given builder.
46+
func (b DrivesBuilder) AddDrive(path string, readOnly bool, opts ...DriveOpt) DrivesBuilder {
47+
drive := models.Drive{
48+
DriveID: String(strconv.Itoa(len(b.drives))),
49+
PathOnHost: &path,
50+
IsRootDevice: Bool(false),
51+
IsReadOnly: &readOnly,
52+
}
53+
54+
for _, opt := range opts {
55+
opt(&drive)
56+
}
57+
58+
b.drives = append(b.drives, drive)
59+
return b
60+
}
61+
62+
// Build will construct an array of drives with the root drive at the very end.
63+
func (b DrivesBuilder) Build() []models.Drive {
64+
return append(b.drives, b.rootDrive)
65+
}

drives_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package firecracker
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
8+
)
9+
10+
func TestDrivesBuilder(t *testing.T) {
11+
expectedPath := "/path/to/rootfs"
12+
expectedDrives := []models.Drive{
13+
{
14+
DriveID: String(rootDriveName),
15+
PathOnHost: &expectedPath,
16+
IsRootDevice: Bool(true),
17+
IsReadOnly: Bool(false),
18+
},
19+
}
20+
21+
drives := NewDrivesBuilder(expectedPath).Build()
22+
if e, a := expectedDrives, drives; !reflect.DeepEqual(e, a) {
23+
t.Errorf("expected drives %v, but received %v", e, a)
24+
}
25+
}
26+
27+
func TestDrivesBuilderWithRootDrive(t *testing.T) {
28+
expectedPath := "/path/to/rootfs"
29+
expectedDrives := []models.Drive{
30+
{
31+
DriveID: String("foo"),
32+
PathOnHost: &expectedPath,
33+
IsRootDevice: Bool(true),
34+
IsReadOnly: Bool(false),
35+
},
36+
}
37+
38+
b := NewDrivesBuilder(expectedPath)
39+
drives := b.WithRootDrive(expectedPath, func(drive *models.Drive) {
40+
drive.DriveID = String("foo")
41+
}).Build()
42+
43+
if e, a := expectedDrives, drives; !reflect.DeepEqual(e, a) {
44+
t.Errorf("expected drives %v, but received %v", e, a)
45+
}
46+
}
47+
48+
func TestDrivesBuilderAddDrive(t *testing.T) {
49+
rootPath := "/root/path"
50+
drivesToAdd := []struct {
51+
Path string
52+
ReadOnly bool
53+
Opt func(drive *models.Drive)
54+
}{
55+
{
56+
Path: "/2",
57+
ReadOnly: true,
58+
},
59+
{
60+
Path: "/3",
61+
ReadOnly: false,
62+
},
63+
{
64+
Path: "/4",
65+
ReadOnly: false,
66+
Opt: func(drive *models.Drive) {
67+
drive.Partuuid = "uuid"
68+
},
69+
},
70+
}
71+
expectedDrives := []models.Drive{
72+
{
73+
DriveID: String("0"),
74+
PathOnHost: String("/2"),
75+
IsRootDevice: Bool(false),
76+
IsReadOnly: Bool(true),
77+
},
78+
{
79+
DriveID: String("1"),
80+
PathOnHost: String("/3"),
81+
IsRootDevice: Bool(false),
82+
IsReadOnly: Bool(false),
83+
},
84+
{
85+
DriveID: String("2"),
86+
PathOnHost: String("/4"),
87+
IsRootDevice: Bool(false),
88+
IsReadOnly: Bool(false),
89+
Partuuid: "uuid",
90+
},
91+
{
92+
DriveID: String(rootDriveName),
93+
PathOnHost: &rootPath,
94+
IsRootDevice: Bool(true),
95+
IsReadOnly: Bool(false),
96+
},
97+
}
98+
99+
b := NewDrivesBuilder(rootPath)
100+
for _, drive := range drivesToAdd {
101+
if drive.Opt != nil {
102+
b = b.AddDrive(drive.Path, drive.ReadOnly, drive.Opt)
103+
} else {
104+
b = b.AddDrive(drive.Path, drive.ReadOnly)
105+
}
106+
}
107+
108+
drives := b.Build()
109+
if e, a := expectedDrives, drives; !reflect.DeepEqual(e, a) {
110+
t.Errorf("expected drives %v, but received %v", e, a)
111+
}
112+
}

example_test.go

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,7 @@ func ExampleWithProcessRunner_logging() {
1414
cfg := firecracker.Config{
1515
SocketPath: socketPath,
1616
KernelImagePath: "/path/to/kernel",
17-
Drives: []models.Drive{
18-
models.Drive{
19-
DriveID: firecracker.String("1"),
20-
IsRootDevice: firecracker.Bool(true),
21-
IsReadOnly: firecracker.Bool(false),
22-
PathOnHost: firecracker.String("/path/to/root/drive"),
23-
},
24-
},
17+
Drives: firecracker.NewDrivesBuilder("/path/to/rootfs").Build(),
2518
MachineCfg: models.MachineConfiguration{
2619
VcpuCount: 1,
2720
},
@@ -67,3 +60,94 @@ func ExampleWithProcessRunner_logging() {
6760
panic(err)
6861
}
6962
}
63+
64+
func ExampleDrivesBuilder() {
65+
drivesParams := []struct {
66+
Path string
67+
ReadOnly bool
68+
}{
69+
{
70+
Path: "/first/path/drive.img",
71+
ReadOnly: true,
72+
},
73+
{
74+
Path: "/second/path/drive.img",
75+
ReadOnly: false,
76+
},
77+
}
78+
79+
// construct a new builder with the given rootfs path
80+
b := firecracker.NewDrivesBuilder("/path/to/rootfs")
81+
for _, param := range drivesParams {
82+
// add our additional drives
83+
b = b.AddDrive(param.Path, param.ReadOnly)
84+
}
85+
86+
const socketPath = "/tmp/firecracker.sock"
87+
cfg := firecracker.Config{
88+
SocketPath: socketPath,
89+
KernelImagePath: "/path/to/kernel",
90+
// build our drives into the machine's configuration
91+
Drives: b.Build(),
92+
MachineCfg: models.MachineConfiguration{
93+
VcpuCount: 1,
94+
},
95+
}
96+
97+
ctx := context.Background()
98+
m, err := firecracker.NewMachine(ctx, cfg)
99+
if err != nil {
100+
panic(fmt.Errorf("failed to create new machine: %v", err))
101+
}
102+
103+
if err := m.Start(ctx); err != nil {
104+
panic(fmt.Errorf("failed to initialize machine: %v", err))
105+
}
106+
107+
// wait for VMM to execute
108+
if err := m.Wait(ctx); err != nil {
109+
panic(err)
110+
}
111+
}
112+
113+
func ExampleDrivesBuilder_DriveOpt() {
114+
drives := firecracker.NewDrivesBuilder("/path/to/rootfs").
115+
AddDrive("/path/to/drive1.img", true).
116+
AddDrive("/path/to/drive2.img", false, func(drive *models.Drive) {
117+
// set our custom bandwidth rate limiter
118+
drive.RateLimiter = &models.RateLimiter{
119+
Bandwidth: &models.TokenBucket{
120+
OneTimeBurst: firecracker.Int64(1024 * 1024),
121+
RefillTime: firecracker.Int64(500),
122+
Size: firecracker.Int64(1024 * 1024),
123+
},
124+
}
125+
}).
126+
Build()
127+
128+
const socketPath = "/tmp/firecracker.sock"
129+
cfg := firecracker.Config{
130+
SocketPath: socketPath,
131+
KernelImagePath: "/path/to/kernel",
132+
// build our drives into the machine's configuration
133+
Drives: drives,
134+
MachineCfg: models.MachineConfiguration{
135+
VcpuCount: 1,
136+
},
137+
}
138+
139+
ctx := context.Background()
140+
m, err := firecracker.NewMachine(ctx, cfg)
141+
if err != nil {
142+
panic(fmt.Errorf("failed to create new machine: %v", err))
143+
}
144+
145+
if err := m.Start(ctx); err != nil {
146+
panic(fmt.Errorf("failed to initialize machine: %v", err))
147+
}
148+
149+
// wait for VMM to execute
150+
if err := m.Wait(ctx); err != nil {
151+
panic(err)
152+
}
153+
}

pointer_helpers.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,18 @@ func StringValue(str *string) string {
2929
func String(str string) *string {
3030
return &str
3131
}
32+
33+
// Int64 will return a pointer value of the given parameter.
34+
func Int64(v int64) *int64 {
35+
return &v
36+
}
37+
38+
// Int64Value will return an int64 value. If the pointer is nil, then zero will
39+
// be returned.
40+
func Int64Value(v *int64) int64 {
41+
if v == nil {
42+
return 0
43+
}
44+
45+
return *v
46+
}

0 commit comments

Comments
 (0)