Skip to content

Commit 744552f

Browse files
authored
Merge pull request #244 from kzys/patch-drive-2
Make sure the error message regarding stub drives is clear
2 parents 516b3ef + e897807 commit 744552f

File tree

5 files changed

+97
-46
lines changed

5 files changed

+97
-46
lines changed

runtime/drive_handler.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,41 @@ type stubDriveHandler struct {
5151
mutex sync.Mutex
5252
}
5353

54-
func newStubDriveHandler(path string, logger *logrus.Entry) stubDriveHandler {
55-
return stubDriveHandler{
54+
func newStubDriveHandler(path string, logger *logrus.Entry, count int) (*stubDriveHandler, error) {
55+
h := stubDriveHandler{
5656
RootPath: path,
5757
logger: logger,
5858
}
59+
drives, err := h.createStubDrives(count)
60+
if err != nil {
61+
return nil, err
62+
}
63+
h.drives = drives
64+
return &h, nil
5965
}
6066

61-
// StubDrivePaths will create stub drives and return the paths associated with
67+
func (h *stubDriveHandler) createStubDrives(stubDriveCount int) ([]models.Drive, error) {
68+
paths, err := h.stubDrivePaths(stubDriveCount)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
stubDrives := make([]models.Drive, 0, stubDriveCount)
74+
for i, path := range paths {
75+
stubDrives = append(stubDrives, models.Drive{
76+
DriveID: firecracker.String(fmt.Sprintf("stub%d", i)),
77+
IsReadOnly: firecracker.Bool(false),
78+
PathOnHost: firecracker.String(path),
79+
IsRootDevice: firecracker.Bool(false),
80+
})
81+
}
82+
83+
return stubDrives, nil
84+
}
85+
86+
// stubDrivePaths will create stub drives and return the paths associated with
6287
// the stub drives.
63-
func (h *stubDriveHandler) StubDrivePaths(count int) ([]string, error) {
88+
func (h *stubDriveHandler) stubDrivePaths(count int) ([]string, error) {
6489
paths := []string{}
6590
for i := 0; i < count; i++ {
6691
driveID := fmt.Sprintf("stub%d", i)
@@ -125,13 +150,6 @@ func (h *stubDriveHandler) createStubDrive(driveID, path string) error {
125150
return nil
126151
}
127152

128-
// SetDrives will set the given drives and the offset to which the stub drives
129-
// start.
130-
func (h *stubDriveHandler) SetDrives(offset int64, d []models.Drive) {
131-
h.drives = d
132-
h.stubDriveIndex = offset
133-
}
134-
135153
// GetDrives returns the associated stub drives
136154
func (h *stubDriveHandler) GetDrives() []models.Drive {
137155
return h.drives

runtime/drive_handler_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ func TestStubDriveHandler(t *testing.T) {
4040
}()
4141

4242
logger := log.G(context.Background())
43-
handler := newStubDriveHandler(tempPath, logger)
44-
paths, err := handler.StubDrivePaths(5)
43+
handler, err := newStubDriveHandler(tempPath, logger, 5)
4544
assert.NoError(t, err)
46-
assert.Equal(t, 5, len(paths))
45+
assert.Equal(t, 5, len(handler.GetDrives()))
4746

4847
infos, err := ioutil.ReadDir(tempPath)
4948
assert.NoError(t, err)
@@ -236,9 +235,11 @@ func TestCreateStubDrive(t *testing.T) {
236235
c := c // see https://github.com/kyoh86/scopelint/issues/4
237236
t.Run(c.Name, func(t *testing.T) {
238237
logger := log.G(context.Background())
239-
handler := newStubDriveHandler(path, logger)
238+
handler, err := newStubDriveHandler(path, logger, 0)
239+
assert.NoError(t, err)
240+
240241
stubDrivePath := filepath.Join(path, c.Name)
241-
err := handler.createStubDrive(c.DriveID, stubDrivePath)
242+
err = handler.createStubDrive(c.DriveID, stubDrivePath)
242243
assert.Equal(t, c.ExpectedError, err != nil, "invalid error: %v", err)
243244

244245
info, err := os.Stat(stubDrivePath)

runtime/service.go

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package main
1515

1616
import (
1717
"context"
18-
"fmt"
1918
"math"
2019
"net"
2120
"os"
@@ -43,7 +42,6 @@ import (
4342
"github.com/containerd/fifo"
4443
"github.com/containerd/ttrpc"
4544
"github.com/firecracker-microvm/firecracker-go-sdk"
46-
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
4745
"github.com/gofrs/uuid"
4846
ptypes "github.com/gogo/protobuf/types"
4947
"github.com/golang/protobuf/ptypes/empty"
@@ -124,7 +122,7 @@ type service struct {
124122
vmStartOnce sync.Once
125123
agentClient taskAPI.TaskService
126124
eventBridgeClient eventbridge.Getter
127-
stubDriveHandler stubDriveHandler
125+
stubDriveHandler *stubDriveHandler
128126
exitAfterAllTasksDeleted bool // exit the VM and shim when all tasks are deleted
129127

130128
machine *firecracker.Machine
@@ -198,7 +196,6 @@ func NewService(shimCtx context.Context, id string, remotePublisher shim.Publish
198196
vmReady: make(chan struct{}),
199197
}
200198

201-
s.stubDriveHandler = newStubDriveHandler(s.shimDir.RootPath(), logger)
202199
s.startEventForwarders(remotePublisher)
203200

204201
err = s.serveFCControl()
@@ -588,25 +585,6 @@ func (s *service) SetVMMetadata(requestCtx context.Context, request *proto.SetVM
588585
return &empty.Empty{}, nil
589586
}
590587

591-
func (s *service) createStubDrives(stubDriveCount int) ([]models.Drive, error) {
592-
paths, err := s.stubDriveHandler.StubDrivePaths(stubDriveCount)
593-
if err != nil {
594-
return nil, errors.Wrap(err, "failed to retrieve stub drive paths")
595-
}
596-
597-
stubDrives := make([]models.Drive, 0, stubDriveCount)
598-
for i, path := range paths {
599-
stubDrives = append(stubDrives, models.Drive{
600-
DriveID: firecracker.String(fmt.Sprintf("stub%d", i)),
601-
IsReadOnly: firecracker.Bool(false),
602-
PathOnHost: firecracker.String(path),
603-
IsRootDevice: firecracker.Bool(false),
604-
})
605-
}
606-
607-
return stubDrives, nil
608-
}
609-
610588
func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker.Config, error) {
611589
logger := s.logger.WithField("cid", s.machineCID)
612590

@@ -646,11 +624,11 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
646624
}
647625

648626
// Create stub drives first and let stub driver handler manage the drives
649-
stubDrives, err := s.createStubDrives(containerCount)
627+
handler, err := newStubDriveHandler(s.shimDir.RootPath(), logger, containerCount)
650628
if err != nil {
651629
return nil, errors.Wrap(err, "failed to create stub drives")
652630
}
653-
s.stubDriveHandler.SetDrives(0, stubDrives)
631+
s.stubDriveHandler = handler
654632

655633
var driveBuilder firecracker.DrivesBuilder
656634
// Create non-stub drives
@@ -665,8 +643,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
665643
}
666644

667645
// a micro VM must know all drives
668-
// nolint: gocritic
669-
cfg.Drives = append(stubDrives, driveBuilder.Build()...)
646+
cfg.Drives = append(handler.GetDrives(), driveBuilder.Build()...)
670647

671648
// Setup network interfaces
672649

@@ -715,9 +692,11 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask
715692
for _, mnt := range request.Rootfs {
716693
driveID, err = s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, mnt.Source)
717694
if err != nil {
695+
if err == ErrDrivesExhausted {
696+
return nil, errors.Wrapf(errdefs.ErrUnavailable, "no remaining stub drives to be used")
697+
}
718698
return nil, errors.Wrapf(err, "failed to patch stub drive")
719699
}
720-
721700
}
722701

723702
ociConfigBytes, err := bundleDir.OCIConfig().Bytes()

runtime/service_integ_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/opencontainers/runtime-spec/specs-go"
4141
"github.com/pkg/errors"
4242
"github.com/shirou/gopsutil/process"
43+
"github.com/stretchr/testify/assert"
4344
"github.com/stretchr/testify/require"
4445

4546
_ "github.com/firecracker-microvm/firecracker-containerd/firecracker-control"
@@ -676,3 +677,54 @@ func TestCreateContainerWithSameName_Isolated(t *testing.T) {
676677
vmID := fmt.Sprintf("same-vm-%d", time.Now().UnixNano())
677678
testCreateContainerWithSameName(t, vmID)
678679
}
680+
681+
func TestCreateTooManyContainers_Isolated(t *testing.T) {
682+
internal.RequiresIsolation(t)
683+
assert := assert.New(t)
684+
685+
ctx := namespaces.WithNamespace(context.Background(), "default")
686+
687+
client, err := containerd.New(containerdSockPath, containerd.WithDefaultRuntime(firecrackerRuntime))
688+
require.NoError(t, err, "unable to create client to containerd service at %s, is containerd running?", containerdSockPath)
689+
defer client.Close()
690+
691+
image, err := client.Pull(ctx, guestDockerImage, containerd.WithPullUnpack, containerd.WithPullSnapshotter(naiveSnapshotterName))
692+
require.NoError(t, err, "failed to pull image %s, is the the %s snapshotter running?", guestDockerImage, naiveSnapshotterName)
693+
694+
runEchoHello := containerd.WithNewSpec(oci.WithProcessArgs("echo", "-n", "hello"), firecrackeroci.WithVMID("reuse-same-vm"))
695+
696+
c1, err := client.NewContainer(ctx,
697+
"c1",
698+
containerd.WithSnapshotter(naiveSnapshotterName),
699+
containerd.WithNewSnapshot("c1", image),
700+
runEchoHello,
701+
)
702+
assert.Equal("hello", startAndWaitTask(ctx, t, c1))
703+
require.NoError(t, err, "failed to create a container")
704+
705+
defer func() {
706+
err = c1.Delete(ctx, containerd.WithSnapshotCleanup)
707+
require.NoError(t, err, "failed to delete a container")
708+
}()
709+
710+
c2, err := client.NewContainer(ctx,
711+
"c2",
712+
containerd.WithSnapshotter(naiveSnapshotterName),
713+
containerd.WithNewSnapshot("c2", image),
714+
runEchoHello,
715+
)
716+
require.NoError(t, err, "failed to create a container")
717+
718+
defer func() {
719+
err := c2.Delete(ctx, containerd.WithSnapshotCleanup)
720+
require.NoError(t, err, "failed to delete a container")
721+
}()
722+
723+
var stdout bytes.Buffer
724+
var stderr bytes.Buffer
725+
726+
// When we reuse a VM explicitly, we cannot start multiple containers unless we pre-allocate stub drives.
727+
_, err = c2.NewTask(ctx, cio.NewCreator(cio.WithStreams(nil, &stdout, &stderr)))
728+
assert.Equal("no remaining stub drives to be used: unavailable: unknown", err.Error())
729+
require.Error(t, err)
730+
}

runtime/service_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"testing"
2424

2525
"github.com/firecracker-microvm/firecracker-containerd/internal"
26+
"github.com/firecracker-microvm/firecracker-containerd/internal/vm"
2627
"github.com/firecracker-microvm/firecracker-containerd/proto"
2728
"github.com/firecracker-microvm/firecracker-go-sdk"
2829
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
@@ -216,7 +217,7 @@ func TestBuildVMConfiguration(t *testing.T) {
216217
assert.NoError(t, err)
217218
defer os.RemoveAll(tempDir)
218219

219-
svc.stubDriveHandler = newStubDriveHandler(tempDir, svc.logger)
220+
svc.shimDir = vm.Dir(tempDir)
220221
// For values that remain constant between tests, they are written here
221222
tc.expectedCfg.SocketPath = svc.shimDir.FirecrackerSockPath()
222223
tc.expectedCfg.VsockDevices = []firecracker.VsockDevice{{Path: "root", CID: svc.machineCID}}
@@ -293,7 +294,7 @@ func TestDebugConfig(t *testing.T) {
293294
stubDrivePath := filepath.Join(path, fmt.Sprintf("%d", i))
294295
err := os.MkdirAll(stubDrivePath, os.ModePerm)
295296
assert.NoError(t, err, "failed to create stub drive path")
296-
c.service.stubDriveHandler = newStubDriveHandler(stubDrivePath, c.service.logger)
297+
c.service.shimDir = vm.Dir(stubDrivePath)
297298
req := proto.CreateVMRequest{}
298299

299300
cfg, err := c.service.buildVMConfiguration(&req)

0 commit comments

Comments
 (0)