Skip to content

Commit 121ef9a

Browse files
committed
add handler lists to handle initialization
This refactor introduces handlers which allow for easability of testing and extendability. The machine should not be required to know what it is running, per se, but whether or not the commands were successful. This removes CPUCount, HtEnabled, CPUTemplate, and MemInMiB from the Config, and instead these values can be set on the MachineCfg field.
1 parent 0fd9825 commit 121ef9a

File tree

7 files changed

+910
-302
lines changed

7 files changed

+910
-302
lines changed

cmd/firectl/main.go

Lines changed: 143 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ import (
1818
"encoding/json"
1919
"errors"
2020
"fmt"
21+
"io"
22+
"io/ioutil"
2123
"os"
24+
"path/filepath"
2225
"strconv"
2326
"strings"
2427

2528
"github.com/firecracker-microvm/firecracker-go-sdk"
29+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
2630
"github.com/jessevdk/go-flags"
2731
log "github.com/sirupsen/logrus"
2832
)
@@ -43,28 +47,37 @@ const (
4347
executableMask = 0111
4448
)
4549

46-
func parseBlockDevices(entries []string) ([]firecracker.BlockDevice, error) {
47-
var devices []firecracker.BlockDevice
48-
for _, entry := range entries {
49-
var path string
50+
func parseBlockDevices(entries []string) ([]models.Drive, error) {
51+
devices := []models.Drive{}
52+
53+
for i, entry := range entries {
54+
path := ""
55+
readOnly := true
56+
5057
if strings.HasSuffix(entry, ":rw") {
58+
readOnly = false
5159
path = strings.TrimSuffix(entry, ":rw")
5260
} else if strings.HasSuffix(entry, ":ro") {
5361
path = strings.TrimSuffix(entry, ":ro")
5462
} else {
5563
msg := fmt.Sprintf("Invalid drive specification. Must have :rw or :ro suffix")
56-
return []firecracker.BlockDevice{}, errors.New(msg)
64+
return nil, errors.New(msg)
5765
}
66+
5867
if path == "" {
5968
return nil, errors.New("Invalid drive specification")
6069
}
61-
_, err := os.Stat(path)
62-
if err != nil {
70+
71+
if _, err := os.Stat(path); err != nil {
6372
return nil, err
6473
}
65-
e := firecracker.BlockDevice{
66-
HostPath: path,
67-
Mode: "rw",
74+
75+
e := models.Drive{
76+
// i + 2 represents the drive ID. We will reserve 1 for root.
77+
DriveID: firecracker.String(strconv.Itoa(i + 2)),
78+
PathOnHost: firecracker.String(path),
79+
IsReadOnly: firecracker.Bool(readOnly),
80+
IsRootDevice: firecracker.Bool(false),
6881
}
6982
devices = append(devices, e)
7083
}
@@ -105,6 +118,76 @@ func parseVsocks(devices []string) ([]firecracker.VsockDevice, error) {
105118
return result, nil
106119
}
107120

121+
func createFifoFileLogs(fifoPath string) (*os.File, error) {
122+
return os.OpenFile(fifoPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
123+
}
124+
125+
// handleFifos will see if any fifos need to be generated and if a fifo log
126+
// file should be created.
127+
func handleFifos(opts *options) (io.Writer, []func() error, error) {
128+
// these booleans are used to check whether or not the fifo queue or metrics
129+
// fifo queue needs to be generated. If any which need to be generated, then
130+
// we know we need to create a temporary directory. Otherwise, a temporary
131+
// directory does not need to be created.
132+
generateFifoFilename := false
133+
generateMetricFifoFilename := false
134+
cleanupFns := []func() error{}
135+
var err error
136+
137+
var fifo io.WriteCloser
138+
if len(opts.FcFifoLogFile) > 0 {
139+
if len(opts.FcLogFifo) > 0 {
140+
return nil, cleanupFns, fmt.Errorf("vmm-log-fifo and firecracker-log cannot be used together")
141+
}
142+
143+
generateFifoFilename = true
144+
// if a fifo log file was specified via the CLI then we need to check if
145+
// metric fifo was also specified. If not, we will then generate that fifo
146+
if len(opts.FcMetricsFifo) == 0 {
147+
generateMetricFifoFilename = true
148+
}
149+
150+
if fifo, err = createFifoFileLogs(opts.FcFifoLogFile); err != nil {
151+
return fifo, cleanupFns, fmt.Errorf("Failed to create fifo log file: %v", err)
152+
}
153+
154+
cleanupFns = append(cleanupFns, func() error {
155+
return fifo.Close()
156+
})
157+
} else if len(opts.FcLogFifo) > 0 || len(opts.FcMetricsFifo) > 0 {
158+
// this checks to see if either one of the fifos was set. If at least one
159+
// has been set we check to see if any of the others were not set. If one
160+
// isn't set, we will generate the proper file path.
161+
if len(opts.FcLogFifo) == 0 {
162+
generateFifoFilename = true
163+
}
164+
165+
if len(opts.FcMetricsFifo) == 0 {
166+
generateMetricFifoFilename = true
167+
}
168+
}
169+
170+
if generateFifoFilename || generateMetricFifoFilename {
171+
dir, err := ioutil.TempDir(os.TempDir(), "fcfifo")
172+
if err != nil {
173+
return fifo, cleanupFns, fmt.Errorf("Fail to create temporary directory: %v", err)
174+
}
175+
176+
cleanupFns = append(cleanupFns, func() error {
177+
return os.RemoveAll(dir)
178+
})
179+
if generateFifoFilename {
180+
opts.FcLogFifo = filepath.Join(dir, "fc_fifo")
181+
}
182+
183+
if generateMetricFifoFilename {
184+
opts.FcMetricsFifo = filepath.Join(dir, "fc_metrics_fifo")
185+
}
186+
}
187+
188+
return fifo, cleanupFns, nil
189+
}
190+
108191
type options struct {
109192
FcBinary string `long:"firecracker-binary" description:"Path to firecracker binary"`
110193
FcKernelImage string `long:"kernel" description:"Path to the kernel image" default:"./vmlinux"`
@@ -117,12 +200,12 @@ type options struct {
117200
FcLogFifo string `long:"vmm-log-fifo" description:"FIFO for firecracker logs"`
118201
FcLogLevel string `long:"log-level" description:"vmm log level" default:"Debug"`
119202
FcMetricsFifo string `long:"metrics-fifo" description:"FIFO for firecracker metrics"`
120-
FcCaptureFifoLogs bool `long:"capture-fifo-logs" description:"pipes fifo and metric fifo's contents to files"`
121203
FcDisableHt bool `long:"disable-hyperthreading" short:"t" description:"Disable CPU Hyperthreading"`
122204
FcCPUCount int64 `long:"ncpus" short:"c" description:"Number of CPUs" default:"1"`
123205
FcCPUTemplate string `long:"cpu-template" description:"Firecracker CPU Template (C3 or T2)"`
124206
FcMemSz int64 `long:"memory" short:"m" description:"VM memory, in MiB" default:"512"`
125207
FcMetadata string `long:"metadata" description:"Firecracker Meatadata for MMDS (json)"`
208+
FcFifoLogFile string `long:"firecracker-log" short:"l" description:"pipes the fifo contents to the specified file"`
126209
Debug bool `long:"debug" short:"d" description:"Enable debug output"`
127210
Help bool `long:"help" short:"h" description:"Show usage"`
128211
}
@@ -165,7 +248,7 @@ func main() {
165248
if err != nil {
166249
log.Fatalf("Unable to parse NIC config: %s", err)
167250
} else {
168-
log.Printf("Adding tap device %s", tapDev)
251+
log.Error("Adding tap device %s", tapDev)
169252
allowMDDS := metadata != nil
170253
NICs = []firecracker.NetworkInterface{
171254
firecracker.NetworkInterface{
@@ -177,36 +260,57 @@ func main() {
177260
}
178261
}
179262

180-
rootDrive := firecracker.BlockDevice{HostPath: opts.FcRootDrivePath, Mode: "rw"}
181-
182263
blockDevices, err := parseBlockDevices(opts.FcAdditionalDrives)
183264
if err != nil {
184265
log.Fatalf("Invalid block device specification: %s", err)
185266
}
186267

268+
rootDrive := models.Drive{
269+
DriveID: firecracker.String("1"),
270+
PathOnHost: &opts.FcRootDrivePath,
271+
IsRootDevice: firecracker.Bool(true),
272+
IsReadOnly: firecracker.Bool(false),
273+
Partuuid: opts.FcRootPartUUID,
274+
}
275+
blockDevices = append(blockDevices, rootDrive)
276+
187277
vsocks, err := parseVsocks(opts.FcVsockDevices)
188278
if err != nil {
189279
log.Fatalf("Invalid vsock specification: %s", err)
190280
}
191281

282+
fifo, cleanFns, err := handleFifos(&opts)
283+
// we call cleanup first due to errors returning at different points which
284+
// may result in a file handle being opened.
285+
defer func() {
286+
for _, fn := range cleanFns {
287+
if err := fn(); err != nil {
288+
log.WithError(err).Error("Failed to cleanup")
289+
}
290+
}
291+
}()
292+
if err != nil {
293+
log.Fatalf("%v", err)
294+
}
295+
192296
fcCfg := firecracker.Config{
193-
SocketPath: "./firecracker.sock",
194-
LogFifo: opts.FcLogFifo,
195-
LogLevel: opts.FcLogLevel,
196-
MetricsFifo: opts.FcMetricsFifo,
197-
CaptureFifoLogsToFile: opts.FcCaptureFifoLogs,
198-
KernelImagePath: opts.FcKernelImage,
199-
KernelArgs: opts.FcKernelCmdLine,
200-
RootDrive: rootDrive,
201-
RootPartitionUUID: opts.FcRootPartUUID,
202-
AdditionalDrives: blockDevices,
203-
NetworkInterfaces: NICs,
204-
VsockDevices: vsocks,
205-
CPUCount: opts.FcCPUCount,
206-
CPUTemplate: firecracker.CPUTemplate(opts.FcCPUTemplate),
207-
HtEnabled: !opts.FcDisableHt,
208-
MemInMiB: opts.FcMemSz,
209-
Debug: opts.Debug,
297+
SocketPath: "./firecracker.sock",
298+
LogFifo: opts.FcLogFifo,
299+
LogLevel: opts.FcLogLevel,
300+
MetricsFifo: opts.FcMetricsFifo,
301+
FifoLogWriter: fifo,
302+
KernelImagePath: opts.FcKernelImage,
303+
KernelArgs: opts.FcKernelCmdLine,
304+
Drives: blockDevices,
305+
NetworkInterfaces: NICs,
306+
VsockDevices: vsocks,
307+
MachineCfg: models.MachineConfiguration{
308+
VcpuCount: opts.FcCPUCount,
309+
CPUTemplate: models.CPUTemplate(opts.FcCPUTemplate),
310+
HtEnabled: !opts.FcDisableHt,
311+
MemSizeMib: opts.FcMemSz,
312+
},
313+
Debug: opts.Debug,
210314
}
211315

212316
if len(os.Args) == 1 {
@@ -249,29 +353,23 @@ func main() {
249353
machineOpts = append(machineOpts, firecracker.WithProcessRunner(cmd))
250354
}
251355

252-
m, err := firecracker.NewMachine(fcCfg, machineOpts...)
356+
m, err := firecracker.NewMachine(vmmCtx, fcCfg, machineOpts...)
253357
if err != nil {
254358
log.Fatalf("Failed creating machine: %s", err)
255359
}
256360

257-
if err := m.Init(vmmCtx); err != nil {
258-
log.Fatalf("Firecracker Init returned error %s", err)
259-
}
260-
261361
if metadata != nil {
262-
err := m.SetMetadata(vmmCtx, metadata)
263-
if err != nil {
264-
log.Fatalf("Firecracker SetMetadata returned error %s", err)
265-
}
362+
m.EnableMetadata(metadata)
266363
}
267364

268-
if err := m.StartInstance(vmmCtx); err != nil {
269-
log.Fatalf("Failed to start instance: %v", err)
365+
if err := m.Start(vmmCtx); err != nil {
366+
log.Fatalf("Failed to start machine: %v", err)
270367
}
368+
defer m.StopVMM()
271369

272370
// wait for the VMM to exit
273-
if err := <-m.ErrCh; err != nil {
274-
log.Fatalf("startVMM returned error %s", err)
371+
if err := m.Wait(vmmCtx); err != nil {
372+
log.Fatalf("Wait returned an error %s", err)
275373
}
276-
log.Printf("startVMM was happy")
374+
log.Printf("Start machine was happy")
277375
}

example_test.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@ import (
66
"os"
77

88
"github.com/firecracker-microvm/firecracker-go-sdk"
9+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
910
)
1011

1112
func ExampleWithProcessRunner_logging() {
1213
const socketPath = "/tmp/firecracker.sock"
1314
cfg := firecracker.Config{
1415
SocketPath: socketPath,
1516
KernelImagePath: "/path/to/kernel",
16-
RootDrive: firecracker.BlockDevice{
17-
HostPath: "/path/to/root/drive",
18-
Mode: "rw",
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+
},
25+
MachineCfg: models.MachineConfiguration{
26+
VcpuCount: 1,
1927
},
20-
CPUCount: 1,
2128
}
2229

2330
// stdout will be directed to this file
@@ -44,22 +51,19 @@ func ExampleWithProcessRunner_logging() {
4451
WithStderr(stderr).
4552
Build(ctx)
4653

47-
m, err := firecracker.NewMachine(cfg, firecracker.WithProcessRunner(cmd))
54+
m, err := firecracker.NewMachine(ctx, cfg, firecracker.WithProcessRunner(cmd))
4855
if err != nil {
4956
panic(fmt.Errorf("failed to create new machine: %v", err))
5057
}
5158

5259
defer os.Remove(cfg.SocketPath)
5360

54-
ch, err := m.Init(ctx)
55-
if err != nil {
61+
if err := m.Start(ctx); err != nil {
5662
panic(fmt.Errorf("failed to initialize machine: %v", err))
5763
}
5864

59-
if err := m.StartInstance(ctx); err != nil {
60-
panic(fmt.Errorf("Failed to start instance: %v", err))
61-
}
62-
6365
// wait for VMM to execute
64-
<-ch
66+
if err := m.Wait(ctx); err != nil {
67+
panic(err)
68+
}
6569
}

0 commit comments

Comments
 (0)