From 58a2d9457f3d0063c2cfbde76781f7cb29993ba7 Mon Sep 17 00:00:00 2001 From: Erik Sipsma Date: Thu, 17 Oct 2019 22:22:05 +0000 Subject: [PATCH 1/2] Support DriveMount API in CreateVM. This implements the proposal found in docs/drive-mounts-proposal.md, with the exception for supporting RateLimiters and IsReadOnly in the DriveMount objects, due to the need for further refactoring of the internal stub drive code (will be followed up in #296). Signed-off-by: Erik Sipsma --- agent/drive_handler.go | 122 ++++ agent/drive_handler_test.go | 126 +++++ agent/main.go | 18 +- agent/service.go | 187 ++++--- internal/bundle/bundle.go | 33 +- internal/fsutil.go | 115 ++++ proto/Makefile | 2 + proto/events.pb.go | 12 +- proto/firecracker.pb.go | 113 ++-- proto/firecracker.proto | 8 +- proto/service/drivemount/Makefile | 29 + proto/service/drivemount/drivemount.proto | 16 + .../service/drivemount/ttrpc/drivemount.pb.go | 522 ++++++++++++++++++ proto/types.pb.go | 323 ++++++----- proto/types.proto | 46 +- runtime/cni_integ_test.go | 8 - runtime/drive_handler.go | 10 +- runtime/drive_handler_test.go | 2 +- runtime/helpers.go | 13 - runtime/helpers_test.go | 12 - runtime/service.go | 88 ++- runtime/service_integ_test.go | 193 ++++++- runtime/service_test.go | 12 +- 23 files changed, 1621 insertions(+), 389 deletions(-) create mode 100644 internal/fsutil.go create mode 100644 proto/service/drivemount/Makefile create mode 100644 proto/service/drivemount/drivemount.proto create mode 100644 proto/service/drivemount/ttrpc/drivemount.pb.go diff --git a/agent/drive_handler.go b/agent/drive_handler.go index bbb899716..aeec3acd3 100644 --- a/agent/drive_handler.go +++ b/agent/drive_handler.go @@ -14,12 +14,20 @@ package main import ( + "context" + "fmt" "io/ioutil" "os" "path/filepath" "strings" + "time" + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/mount" "github.com/firecracker-microvm/firecracker-containerd/internal" + drivemount "github.com/firecracker-microvm/firecracker-containerd/proto/service/drivemount/ttrpc" + "github.com/golang/protobuf/ptypes/empty" + "github.com/pkg/errors" ) const ( @@ -28,6 +36,14 @@ const ( blockMajorMinor = "dev" ) +var ( + bannedSystemDirs = []string{ + "/proc", + "/sys", + "/dev", + } +) + type drive struct { Name string DriveID string @@ -45,6 +61,8 @@ type driveHandler struct { DrivePath string } +var _ drivemount.DriveMounterService = &driveHandler{} + func newDriveHandler(blockPath, drivePath string) (*driveHandler, error) { d := &driveHandler{ drives: map[string]drive{}, @@ -146,3 +164,107 @@ func isStubDrive(d drive) bool { return internal.IsStubDrive(f) } + +func (dh driveHandler) MountDrive(ctx context.Context, req *drivemount.MountDriveRequest) (*empty.Empty, error) { + logger := log.G(ctx) + logger.Debugf("%+v", req.String()) + logger = logger.WithField("drive_id", req.DriveID) + + drive, ok := dh.GetDrive(req.DriveID) + if !ok { + return nil, fmt.Errorf("drive %q could not be found", req.DriveID) + } + logger = logger.WithField("drive_path", drive.Path()) + + // Do a basic check that we won't be mounting over any important system directories + resolvedDest, err := evalAnySymlinks(req.DestinationPath) + if err != nil { + return nil, errors.Wrapf(err, + "failed to evaluate any symlinks in drive mount destination %q", req.DestinationPath) + } + + for _, systemDir := range bannedSystemDirs { + if isOrUnderDir(resolvedDest, systemDir) { + return nil, errors.Errorf( + "drive mount destination %q resolves to path %q under banned system directory %q", + req.DestinationPath, resolvedDest, systemDir, + ) + } + } + + err = os.MkdirAll(req.DestinationPath, 0700) + if err != nil { + return nil, errors.Wrapf(err, "failed to create drive mount destination %q", req.DestinationPath) + } + + // Retry the mount in the case of failure a fixed number of times. This works around a rare issue + // where we get to this mount attempt before the guest OS has realized a drive was patched: + // https://github.com/firecracker-microvm/firecracker-containerd/issues/214 + const ( + maxRetries = 100 + retryDelay = 10 * time.Millisecond + ) + + for i := 0; i < maxRetries; i++ { + err := mount.All([]mount.Mount{{ + Source: drive.Path(), + Type: req.FilesytemType, + Options: req.Options, + }}, req.DestinationPath) + if err == nil { + return &empty.Empty{}, nil + } + + if isRetryableMountError(err) { + logger.WithError(err).Warnf("retryable failure mounting drive") + time.Sleep(retryDelay) + continue + } + + return nil, errors.Wrapf(err, "non-retryable failure mounting drive from %q to %q", + drive.Path(), req.DestinationPath) + } + + return nil, errors.Errorf("exhausted retries mounting drive from %q to %q", + drive.Path(), req.DestinationPath) +} + +// evalAnySymlinks is similar to filepath.EvalSymlinks, except it will not return an error if part of the +// provided path does not exist. It will evaluate symlinks present in the path up to a component that doesn't +// exist, at which point it will just append the rest of the provided path to what has been resolved so far. +// We validate earlier that input to this function is an absolute path. +func evalAnySymlinks(path string) (string, error) { + curPath := "/" + pathSplit := strings.Split(filepath.Clean(path), "/") + for len(pathSplit) > 0 { + curPath = filepath.Join(curPath, pathSplit[0]) + pathSplit = pathSplit[1:] + + resolvedPath, err := filepath.EvalSymlinks(curPath) + if os.IsNotExist(err) { + return filepath.Join(append([]string{curPath}, pathSplit...)...), nil + } + if err != nil { + return "", err + } + curPath = resolvedPath + } + + return curPath, nil +} + +// returns whether the given path is the provided baseDir or is under it +func isOrUnderDir(path, baseDir string) bool { + path = filepath.Clean(path) + baseDir = filepath.Clean(baseDir) + + if baseDir == "/" { + return true + } + + if path == baseDir { + return true + } + + return strings.HasPrefix(path, baseDir+"/") +} diff --git a/agent/drive_handler_test.go b/agent/drive_handler_test.go index e76c94277..267ee54d2 100644 --- a/agent/drive_handler_test.go +++ b/agent/drive_handler_test.go @@ -14,9 +14,14 @@ package main import ( + "os" + "path/filepath" + "strconv" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDiscoverDrives(t *testing.T) { @@ -39,3 +44,124 @@ func TestDiscoverDrives(t *testing.T) { }) } } + +func TestEvalAnySymlinks(t *testing.T) { + existingSymlink := "/proc/self/cwd" // will always exist and be a symlink to our cwd + nonExistentPath := filepath.Join(strconv.Itoa(int(time.Now().UnixNano())), "foo") + testPath := filepath.Join(existingSymlink, nonExistentPath) + + resolvedPath, err := evalAnySymlinks(testPath) + require.NoError(t, err, "failed to evaluate symlinks in %q", testPath) + + cwd, err := os.Getwd() + require.NoError(t, err, "failed to get current working dir") + assert.Equal(t, filepath.Join(cwd, nonExistentPath), resolvedPath) +} + +func TestIsOrUnderDir(t *testing.T) { + type testcase struct { + baseDir string + path string + expectedTrue bool + } + + for _, tc := range []testcase{ + { + baseDir: "/foo", + path: "/foo/bar", + expectedTrue: true, + }, + { + baseDir: "/foo/bar", + path: "/foo/bar/baz", + expectedTrue: true, + }, + { + baseDir: "/foo", + path: "/foo", + expectedTrue: true, + }, + { + baseDir: "/foo/bar", + path: "/foo/bar", + expectedTrue: true, + }, + { + baseDir: "/foo", + path: "/foobar", + expectedTrue: false, + }, + { + baseDir: "/foo", + path: "/bar", + expectedTrue: false, + }, + { + baseDir: "/foo/bar", + path: "/bar", + expectedTrue: false, + }, + { + baseDir: "/foo/bar", + path: "/foo", + expectedTrue: false, + }, + { + baseDir: "/foo/bar", + path: "/bar/bar", + expectedTrue: false, + }, + { + baseDir: "/foo", + path: "foo", + expectedTrue: false, + }, + { + baseDir: "/foo", + path: "bar", + expectedTrue: false, + }, + { + baseDir: "/foo", + path: "/foo/../foo", + expectedTrue: true, + }, + { + baseDir: "/foo/bar", + path: "/foo/../foo/bar", + expectedTrue: true, + }, + { + baseDir: "/foo", + path: "/foo/../bar", + expectedTrue: false, + }, + { + baseDir: "/foo", + path: "/foo/..bar", + expectedTrue: true, + }, + { + baseDir: "/foo", + path: "/foo/..bar/baz", + expectedTrue: true, + }, + { + baseDir: "/", + path: "/", + expectedTrue: true, + }, + { + baseDir: "/foo", + path: "/", + expectedTrue: false, + }, + { + baseDir: "/", + path: "/foo", + expectedTrue: true, + }, + } { + assert.Equalf(t, tc.expectedTrue, isOrUnderDir(tc.path, tc.baseDir), "unexpected output for isOrUnderDir case %+v", tc) + } +} diff --git a/agent/main.go b/agent/main.go index f2018bcf2..d6b2dd1fc 100644 --- a/agent/main.go +++ b/agent/main.go @@ -34,6 +34,8 @@ import ( "github.com/firecracker-microvm/firecracker-containerd/eventbridge" "github.com/firecracker-microvm/firecracker-containerd/internal/event" "github.com/firecracker-microvm/firecracker-containerd/internal/vm" + + drivemount "github.com/firecracker-microvm/firecracker-containerd/proto/service/drivemount/ttrpc" ) const ( @@ -77,19 +79,25 @@ func main() { log.G(shimCtx).Info("creating task service") + server, err := ttrpc.NewServer() + if err != nil { + log.G(shimCtx).WithError(err).Fatal("failed to create ttrpc server") + } + eventExchange := &event.ExchangeCloser{Exchange: exchange.NewExchange()} + eventbridge.RegisterGetterService(server, eventbridge.NewGetterService(shimCtx, eventExchange)) + taskService, err := NewTaskService(shimCtx, shimCancel, eventExchange) if err != nil { log.G(shimCtx).WithError(err).Fatal("failed to create task service") } + taskAPI.RegisterTaskService(server, taskService) - server, err := ttrpc.NewServer() + dh, err := newDriveHandler(blockPath, drivePath) if err != nil { - log.G(shimCtx).WithError(err).Fatal("failed to create ttrpc server") + log.G(shimCtx).WithError(err).Fatal("failed to create drive handler") } - - taskAPI.RegisterTaskService(server, taskService) - eventbridge.RegisterGetterService(server, eventbridge.NewGetterService(shimCtx, eventExchange)) + drivemount.RegisterDriveMounterService(server, dh) // Run ttrpc over vsock diff --git a/agent/service.go b/agent/service.go index 31e3b8b9c..3d0484a1e 100644 --- a/agent/service.go +++ b/agent/service.go @@ -17,10 +17,7 @@ import ( "context" "fmt" "os" - "path/filepath" "runtime/debug" - "strings" - "time" "github.com/containerd/containerd/cio" "github.com/containerd/containerd/log" @@ -28,18 +25,17 @@ import ( "github.com/containerd/containerd/runtime/v2/shim" taskAPI "github.com/containerd/containerd/runtime/v2/task" "github.com/gogo/protobuf/types" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + "github.com/containerd/containerd/mount" "github.com/firecracker-microvm/firecracker-containerd/internal/bundle" "github.com/firecracker-microvm/firecracker-containerd/internal/vm" "github.com/firecracker-microvm/firecracker-containerd/proto" ) -const ( - containerRootDir = "/container" -) - // TaskService represents inner shim wrapper over runc in order to: // - Add default namespace to ctx as it's not passed by ttrpc over vsock // - Add debug logging to simplify debugging @@ -48,6 +44,9 @@ type TaskService struct { taskManager vm.TaskManager runcService taskAPI.TaskService + // map of (exec,task id, as returned by taskExecID func) -> (callback for cleaning up state for the exec) + execCleanups map[string][]func() error + publisher shim.Publisher // Normally, it's ill-advised to store a context object in a struct. However, @@ -64,13 +63,21 @@ type TaskService struct { // // This approach is also taken by containerd's current reference runc shim // v2 implementation - shimCtx context.Context - shimCancel context.CancelFunc - driveHandler *driveHandler + shimCtx context.Context + shimCancel context.CancelFunc +} + +// provides a unique string for a given (taskID, execID) pair +func taskExecID(taskID, execID string) string { + return fmt.Sprintf("exec-%s-task-%s", execID, taskID) } // NewTaskService creates new runc shim wrapper -func NewTaskService(shimCtx context.Context, shimCancel context.CancelFunc, publisher shim.Publisher) (taskAPI.TaskService, error) { +func NewTaskService( + shimCtx context.Context, + shimCancel context.CancelFunc, + publisher shim.Publisher, +) (taskAPI.TaskService, error) { // We provide an empty string for "id" as the service manages multiple tasks; there is no single // "id" being managed. As noted in the comments of the called code, the "id" arg is only used by // the Cleanup function, so it will never be invoked as part of the task service API, which is all @@ -80,19 +87,14 @@ func NewTaskService(shimCtx context.Context, shimCancel context.CancelFunc, publ return nil, err } - dh, err := newDriveHandler(blockPath, drivePath) - if err != nil { - return nil, err - } - return &TaskService{ - taskManager: vm.NewTaskManager(shimCtx, log.G(shimCtx)), - runcService: runcService, + taskManager: vm.NewTaskManager(shimCtx, log.G(shimCtx)), + runcService: runcService, + execCleanups: make(map[string][]func() error), - publisher: publisher, - shimCtx: shimCtx, - shimCancel: shimCancel, - driveHandler: dh, + publisher: publisher, + shimCtx: shimCtx, + shimCancel: shimCancel, }, nil } @@ -113,12 +115,26 @@ func unmarshalExtraData(marshalled *types.Any) (*proto.ExtraData, error) { return extraData, nil } -func fifoName(taskID, execID string) string { - return fmt.Sprintf("exec-%s-task-%s", execID, taskID) +func (ts *TaskService) addCleanup(taskID, execID string, cleanup func() error) { + id := taskExecID(taskID, execID) + ts.execCleanups[id] = append(ts.execCleanups[id], cleanup) +} + +func (ts *TaskService) doCleanup(taskID, execID string) error { + id := taskExecID(taskID, execID) + + var err *multierror.Error + // iterate in reverse order so changes are "unwound" (similar to a defer) + for i := len(ts.execCleanups[id]) - 1; i >= 0; i-- { + err = multierror.Append(err, ts.execCleanups[id][i]()) + } + + delete(ts.execCleanups, id) + return err.ErrorOrNil() } // Create creates a new initial process and container using runc -func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTaskRequest) (*taskAPI.CreateTaskResponse, error) { +func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTaskRequest) (_ *taskAPI.CreateTaskResponse, err error) { defer logPanicAndDie(log.G(requestCtx)) taskID := req.ID execID := "" // the exec ID of the initial process in a task is an empty string by containerd convention @@ -126,6 +142,15 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas logger := log.G(requestCtx).WithField("TaskID", taskID).WithField("ExecID", execID) logger.Info("create") + defer func() { + if err != nil { + cleanupErr := ts.doCleanup(taskID, execID) + if cleanupErr != nil { + logger.WithError(cleanupErr).Error("failed to cleanup task") + } + } + }() + extraData, err := unmarshalExtraData(req.Options) if err != nil { return nil, errors.Wrap(err, "failed to unmarshal extra data") @@ -134,69 +159,48 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas // Just provide runc the options it knows about, not our wrapper req.Options = extraData.RuncOptions - // Override the bundle dir and rootfs paths, which were set on the Host and thus not valid here in the Guest - bundleDir := bundle.Dir(filepath.Join(containerRootDir, taskID)) - err = bundleDir.Create() - if err != nil { - return nil, errors.Wrap(err, "failed to create bundle dir") - } - - defer func() { + bundleDir := bundle.Dir(req.Bundle) + ts.addCleanup(taskID, execID, func() error { + err := os.RemoveAll(bundleDir.RootPath()) if err != nil { - removeErr := os.RemoveAll(bundleDir.RootPath()) - if removeErr != nil { - logger.WithError(removeErr).Error("failed to cleanup bundle dir") - } + return errors.Wrapf(err, "failed to remove bundle path %q", bundleDir.RootPath()) } - }() + return nil + }) - err = bundleDir.OCIConfig().Write(extraData.JsonSpec) + // check the rootfs dir has been created (presumed to be by a previous MountDrive call) + rootfsStat, err := os.Stat(bundleDir.RootfsPath()) if err != nil { - return nil, errors.Wrap(err, "failed to write oci config file") + return nil, errors.Wrapf(err, "failed to stat bundle's rootfs path %q", bundleDir.RootfsPath()) } - - driveID := strings.TrimSpace(extraData.DriveID) - drive, ok := ts.driveHandler.GetDrive(driveID) - if !ok { - return nil, fmt.Errorf("Drive %q could not be found", driveID) + if !rootfsStat.IsDir() { + return nil, errors.Errorf("bundle's rootfs path %q is not a dir", bundleDir.RootfsPath()) } - - const ( - maxRetries = 100 - retryDelay = 10 * time.Millisecond - ) - - // We retry here due to guest kernel needing some time to populate the guest - // drive. - // https://github.com/firecracker-microvm/firecracker/issues/1159 - for i := 0; i < maxRetries; i++ { - if err = bundleDir.MountRootfs(drive.Path(), "ext4", nil); isRetryableMountError(err) { - logger.WithError(err).Warnf("retrying to mount rootfs %q", drive.Path()) - - time.Sleep(retryDelay) - continue + ts.addCleanup(taskID, execID, func() error { + err := mount.UnmountAll(bundleDir.RootfsPath(), unix.MNT_DETACH) + if err != nil { + return errors.Wrapf(err, "failed to unmount bundle rootfs %q", bundleDir.RootfsPath()) } + return nil + }) - break - } - + err = bundleDir.OCIConfig().Write(extraData.JsonSpec) if err != nil { - return nil, errors.Wrapf(err, "failed to mount rootfs %q", drive.Path()) + return nil, errors.Wrap(err, "failed to write oci config file") } - req.Bundle = bundleDir.RootPath() - req.Rootfs = nil - var ioConnectorSet vm.IOProxy if vm.IsAgentOnlyIO(req.Stdout, logger) { ioConnectorSet = vm.NewNullIOProxy() } else { // Override the incoming stdio FIFOs, which have paths from the host that we can't use - fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName(taskID, execID), req.Terminal) + fifoName := taskExecID(taskID, execID) + fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName, req.Terminal) if err != nil { - logger.WithError(err).Error("failed opening stdio FIFOs") - return nil, errors.Wrap(err, "failed to open stdio FIFOs") + err = errors.Wrap(err, "failed to open stdio FIFOs") + logger.WithError(err).Error() + return nil, err } var stdinConnectorPair *vm.IOConnectorPair @@ -231,7 +235,7 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas resp, err := ts.taskManager.CreateTask(requestCtx, req, ts.runcService, ioConnectorSet) if err != nil { - return nil, errors.Wrap(err, "failed to create runc shim for task") + return nil, errors.Wrap(err, "failed to create runc shim") } logger.WithField("pid", resp.Pid).Debugf("create succeeded") @@ -273,16 +277,23 @@ func (ts *TaskService) Start(requestCtx context.Context, req *taskAPI.StartReque return resp, nil } -// Delete deletes the initial process and container +// Delete deletes the process with the provided exec ID func (ts *TaskService) Delete(requestCtx context.Context, req *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) { defer logPanicAndDie(log.G(requestCtx)) - log.G(requestCtx).WithFields(logrus.Fields{"id": req.ID, "exec_id": req.ExecID}).Debug("delete") + taskID := req.ID + execID := req.ExecID + log.G(requestCtx).WithFields(logrus.Fields{"id": taskID, "exec_id": execID}).Debug("delete") resp, err := ts.taskManager.DeleteProcess(requestCtx, req, ts.runcService) if err != nil { return nil, err } + err = ts.doCleanup(taskID, execID) + if err != nil { + return nil, errors.Wrapf(err, "failed to cleanup task %q exec %q", taskID, execID) + } + log.G(requestCtx).WithFields(logrus.Fields{ "pid": resp.Pid, "exit_status": resp.ExitStatus, @@ -366,7 +377,7 @@ func (ts *TaskService) Kill(requestCtx context.Context, req *taskAPI.KillRequest } // Exec runs an additional process inside the container -func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcessRequest) (*types.Empty, error) { +func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcessRequest) (_ *types.Empty, err error) { defer logPanicAndDie(log.G(requestCtx)) taskID := req.ID @@ -375,6 +386,15 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess logger := log.G(requestCtx).WithField("TaskID", taskID).WithField("ExecID", execID) logger.Debug("exec") + defer func() { + if err != nil { + cleanupErr := ts.doCleanup(taskID, execID) + if cleanupErr != nil { + logger.WithError(cleanupErr).Error("failed to cleanup task") + } + } + }() + extraData, err := unmarshalExtraData(req.Spec) if err != nil { return nil, errors.Wrap(err, "failed to unmarshal extra data") @@ -383,7 +403,7 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess // Just provide runc the options it knows about, not our wrapper req.Spec = extraData.RuncOptions - bundleDir := bundle.Dir(filepath.Join(containerRootDir, taskID)) + bundleDir := bundle.VMBundleDir(taskID) var ioConnectorSet vm.IOProxy @@ -391,10 +411,12 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess ioConnectorSet = vm.NewNullIOProxy() } else { // Override the incoming stdio FIFOs, which have paths from the host that we can't use - fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName(taskID, execID), req.Terminal) + fifoName := taskExecID(taskID, execID) + fifoSet, err := cio.NewFIFOSetInDir(bundleDir.RootPath(), fifoName, req.Terminal) if err != nil { - logger.WithError(err).Error("failed opening stdio FIFOs") - return nil, errors.Wrap(err, "failed to open stdio FIFOs") + err = errors.Wrap(err, "failed to open stdio FIFOs") + logger.WithError(err).Error() + return nil, err } var stdinConnectorPair *vm.IOConnectorPair @@ -404,6 +426,9 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess ReadConnector: vm.VSockAcceptConnector(extraData.StdinPort), WriteConnector: vm.FIFOConnector(fifoSet.Stdin), } + ts.addCleanup(taskID, execID, func() error { + return os.RemoveAll(req.Stdin) + }) } var stdoutConnectorPair *vm.IOConnectorPair @@ -413,6 +438,9 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess ReadConnector: vm.FIFOConnector(fifoSet.Stdout), WriteConnector: vm.VSockAcceptConnector(extraData.StdoutPort), } + ts.addCleanup(taskID, execID, func() error { + return os.RemoveAll(req.Stdout) + }) } var stderrConnectorPair *vm.IOConnectorPair @@ -422,6 +450,9 @@ func (ts *TaskService) Exec(requestCtx context.Context, req *taskAPI.ExecProcess ReadConnector: vm.FIFOConnector(fifoSet.Stderr), WriteConnector: vm.VSockAcceptConnector(extraData.StderrPort), } + ts.addCleanup(taskID, execID, func() error { + return os.RemoveAll(req.Stderr) + }) } ioConnectorSet = vm.NewIOConnectorProxy(stdinConnectorPair, stdoutConnectorPair, stderrConnectorPair) diff --git a/internal/bundle/bundle.go b/internal/bundle/bundle.go index 775285ae9..428eb6126 100644 --- a/internal/bundle/bundle.go +++ b/internal/bundle/bundle.go @@ -19,13 +19,21 @@ import ( "os" "path/filepath" - "github.com/containerd/containerd/mount" - "github.com/firecracker-microvm/firecracker-containerd/internal" "github.com/firecracker-microvm/firecracker-containerd/runtime/firecrackeroci" "github.com/pkg/errors" ) +const ( + vmBundleRoot = "/container" +) + +// VMBundleDir returns the directory inside a VM at which the bundle directory for +// the provided taskID should exist. +func VMBundleDir(taskID string) Dir { + return Dir(filepath.Join(vmBundleRoot, taskID)) +} + // Dir represents the root of a container bundle dir. It's just a type wrapper // around a string, where the string is the path of the bundle dir. type Dir string @@ -35,12 +43,6 @@ func (d Dir) RootPath() string { return string(d) } -// Create will mkdir the bundle RootPath with correct permissions (or no-op if it -// already exists) -func (d Dir) Create() error { - return os.MkdirAll(d.RootPath(), 0700) -} - // AddrFilePath is the path to the address file as found in the bundleDir. // Even though the shim address is set per-VM, not per-container, containerd expects // to find the shim addr file in the bundle dir, so we still have to create it or @@ -60,21 +62,6 @@ func (d Dir) RootfsPath() string { return filepath.Join(d.RootPath(), internal.BundleRootfsName) } -// MountRootfs creates the RootfsPath if it does not exist and mounts the -// provided sourcePath there. -func (d Dir) MountRootfs(sourcePath string, mountType string, mountOpts []string) error { - err := os.MkdirAll(d.RootfsPath(), 0700) - if err != nil { - return err - } - - return mount.All([]mount.Mount{{ - Source: sourcePath, - Type: mountType, - Options: mountOpts, - }}, d.RootfsPath()) -} - // OCIConfigPath returns the path to the bundle's config.json func (d Dir) OCIConfigPath() string { return filepath.Join(d.RootPath(), internal.OCIConfigName) diff --git a/internal/fsutil.go b/internal/fsutil.go new file mode 100644 index 000000000..f90614d69 --- /dev/null +++ b/internal/fsutil.go @@ -0,0 +1,115 @@ +// Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package internal + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +// CreateFSImg will create a file containing a filesystem image of the provided type containing +// the provided files. It returns the path at which the image file can be found. +func CreateFSImg(ctx context.Context, t *testing.T, fsType string, testFiles ...FSImgFile) string { + t.Helper() + + switch fsType { + case "ext3", "ext4": + return createTestExtImg(ctx, t, fsType, testFiles...) + default: + require.FailNowf(t, "unsupported fs type %q", fsType) + return "" + } +} + +// FSImgFile represents a file intended to be place in a filesystem image (at the provided subpath +// with the provided contents) +type FSImgFile struct { + Subpath string + Contents string +} + +func createTestExtImg(ctx context.Context, t *testing.T, extName string, testFiles ...FSImgFile) string { + t.Helper() + + tempdir, err := ioutil.TempDir("", "") + require.NoError(t, err, "failed to create temp dir for ext img") + + for _, testFile := range testFiles { + destPath := filepath.Join(tempdir, testFile.Subpath) + + err = os.MkdirAll(filepath.Dir(destPath), 0750) + require.NoError(t, err, "failed to mkdir for contents of ext img file") + + err = ioutil.WriteFile(destPath, []byte(testFile.Contents), 0750) + require.NoError(t, err, "failed to write file for contents of ext img") + } + + imgFile, err := ioutil.TempFile("", "") + require.NoError(t, err, "failed to obtain temp file for ext img") + + output, err := exec.CommandContext(ctx, "mkfs."+extName, "-d", tempdir, imgFile.Name(), "65536").CombinedOutput() + require.NoErrorf(t, err, "failed to create ext img, command output:\n %s", string(output)) + return imgFile.Name() +} + +// MountInfo holds data parsed from a line of /proc/mounts +type MountInfo struct { + SourcePath string + DestPath string + Type string + Options []string +} + +// ParseProcMountLines converts the provided strings, presumed to be lines read from /proc/mounts +// into MountInfo objects holding the parsed data about each mount. +func ParseProcMountLines(lines ...string) ([]MountInfo, error) { + mountInfos := []MountInfo{} + for _, line := range lines { + if line == "" { + continue + } + + // see man 5 fstab for the format of /proc/mounts + var ( + source string + dest string + fstype string + optionsStr string + dumpFreq int + passno int + ) + _, err := fmt.Sscanf(line, "%s %s %s %s %d %d", &source, &dest, &fstype, &optionsStr, &dumpFreq, &passno) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse /proc/mount line %q", line) + } + + mountInfos = append(mountInfos, MountInfo{ + SourcePath: source, + DestPath: dest, + Type: fstype, + Options: strings.Split(optionsStr, ","), + }) + } + + return mountInfos, nil +} diff --git a/proto/Makefile b/proto/Makefile index 40848b938..0798f39c9 100644 --- a/proto/Makefile +++ b/proto/Makefile @@ -21,9 +21,11 @@ $(PROTO_GEN_SRC): $(PROTO_SRC) proto: $(PROTO_GEN_SRC) PROTOPATH=$(CURDIR) $(MAKE) -C service/fccontrol proto + PROTOPATH=$(CURDIR) $(MAKE) -C service/drivemount proto clean: - rm -f $(PROTO_GEN_SRC) - $(MAKE) -C service/fccontrol clean + - $(MAKE) -C service/drivemount clean .PHONY: clean proto diff --git a/proto/events.pb.go b/proto/events.pb.go index a191bfd61..95486da1f 100644 --- a/proto/events.pb.go +++ b/proto/events.pb.go @@ -21,7 +21,7 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type VMStart struct { - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -59,7 +59,7 @@ func (m *VMStart) GetVMID() string { } type VMStop struct { - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -104,11 +104,11 @@ func init() { func init() { proto.RegisterFile("events.proto", fileDescriptor_8f22242cb04491f9) } var fileDescriptor_8f22242cb04491f9 = []byte{ - // 81 bytes of a gzipped FileDescriptorProto + // 84 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x92, 0xe5, 0x62, 0x0f, 0xf3, 0x0d, 0x2e, 0x49, 0x2c, 0x2a, 0x11, 0x12, 0xe2, 0x62, 0x09, 0xf3, 0xf5, 0x74, 0x91, 0x60, 0x54, 0x60, 0xd4, - 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0x64, 0xb8, 0xd8, 0x40, 0xd2, 0xf9, 0x05, 0xd8, 0x64, 0x93, 0xd8, - 0xc0, 0x66, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf6, 0x83, 0xd8, 0x3c, 0x53, 0x00, 0x00, - 0x00, + 0xe0, 0x0c, 0x62, 0x29, 0xf3, 0xf5, 0x74, 0x51, 0x92, 0xe1, 0x62, 0x03, 0x49, 0xe7, 0x17, 0x60, + 0x93, 0x4d, 0x62, 0x03, 0x9b, 0x61, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x8a, 0x85, 0x02, + 0x53, 0x00, 0x00, 0x00, } diff --git a/proto/firecracker.pb.go b/proto/firecracker.pb.go index 3837f5606..37af5ef6d 100644 --- a/proto/firecracker.pb.go +++ b/proto/firecracker.pb.go @@ -23,24 +23,24 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // CreateVMRequest specifies creation parameters for a new FC instance type CreateVMRequest struct { // VM identifier to assign - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` // Specifies the machine configuration for the VM - MachineCfg *FirecrackerMachineConfiguration `protobuf:"bytes,2,opt,name=MachineCfg,proto3" json:"MachineCfg,omitempty"` + MachineCfg *FirecrackerMachineConfiguration `protobuf:"bytes,2,opt,name=MachineCfg,json=machineCfg,proto3" json:"MachineCfg,omitempty"` // Specifies the file path where the kernel image is located - KernelImagePath string `protobuf:"bytes,3,opt,name=KernelImagePath,proto3" json:"KernelImagePath,omitempty"` + KernelImagePath string `protobuf:"bytes,3,opt,name=KernelImagePath,json=kernelImagePath,proto3" json:"KernelImagePath,omitempty"` // Specifies the commandline arguments that should be passed to the kernel - KernelArgs string `protobuf:"bytes,4,opt,name=KernelArgs,proto3" json:"KernelArgs,omitempty"` - // Specifies the root block device for the VM - RootDrive *FirecrackerDrive `protobuf:"bytes,5,opt,name=RootDrive,proto3" json:"RootDrive,omitempty"` - // Specifies the additional block device config for the VM. - AdditionalDrives []*FirecrackerDrive `protobuf:"bytes,6,rep,name=AdditionalDrives,proto3" json:"AdditionalDrives,omitempty"` + KernelArgs string `protobuf:"bytes,4,opt,name=KernelArgs,json=kernelArgs,proto3" json:"KernelArgs,omitempty"` + // Specifies drive containing the rootfs of the VM + RootDrive *FirecrackerRootDrive `protobuf:"bytes,5,opt,name=RootDrive,json=rootDrive,proto3" json:"RootDrive,omitempty"` + // Specifies additional drives whose contents will be mounted inside the VM on boot. + DriveMounts []*FirecrackerDriveMount `protobuf:"bytes,6,rep,name=DriveMounts,json=driveMounts,proto3" json:"DriveMounts,omitempty"` // Specifies the networking configuration for a VM - NetworkInterfaces []*FirecrackerNetworkInterface `protobuf:"bytes,7,rep,name=NetworkInterfaces,proto3" json:"NetworkInterfaces,omitempty"` + NetworkInterfaces []*FirecrackerNetworkInterface `protobuf:"bytes,7,rep,name=NetworkInterfaces,json=networkInterfaces,proto3" json:"NetworkInterfaces,omitempty"` // The number of dummy drives to reserve in advance before running FC instance. - ContainerCount int32 `protobuf:"varint,8,opt,name=ContainerCount,proto3" json:"ContainerCount,omitempty"` + ContainerCount int32 `protobuf:"varint,8,opt,name=ContainerCount,json=containerCount,proto3" json:"ContainerCount,omitempty"` // Whether the VM should exit after all tasks running in it have been deleted. - ExitAfterAllTasksDeleted bool `protobuf:"varint,9,opt,name=ExitAfterAllTasksDeleted,proto3" json:"ExitAfterAllTasksDeleted,omitempty"` - JailerConfig *JailerConfig `protobuf:"bytes,10,opt,name=JailerConfig,proto3" json:"JailerConfig,omitempty"` + ExitAfterAllTasksDeleted bool `protobuf:"varint,9,opt,name=ExitAfterAllTasksDeleted,json=exitAfterAllTasksDeleted,proto3" json:"ExitAfterAllTasksDeleted,omitempty"` + JailerConfig *JailerConfig `protobuf:"bytes,10,opt,name=JailerConfig,json=jailerConfig,proto3" json:"JailerConfig,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -98,16 +98,16 @@ func (m *CreateVMRequest) GetKernelArgs() string { return "" } -func (m *CreateVMRequest) GetRootDrive() *FirecrackerDrive { +func (m *CreateVMRequest) GetRootDrive() *FirecrackerRootDrive { if m != nil { return m.RootDrive } return nil } -func (m *CreateVMRequest) GetAdditionalDrives() []*FirecrackerDrive { +func (m *CreateVMRequest) GetDriveMounts() []*FirecrackerDriveMount { if m != nil { - return m.AdditionalDrives + return m.DriveMounts } return nil } @@ -141,8 +141,8 @@ func (m *CreateVMRequest) GetJailerConfig() *JailerConfig { } type StopVMRequest struct { - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` - TimeoutSeconds uint32 `protobuf:"varint,2,opt,name=TimeoutSeconds,proto3" json:"TimeoutSeconds,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` + TimeoutSeconds uint32 `protobuf:"varint,2,opt,name=TimeoutSeconds,json=timeoutSeconds,proto3" json:"TimeoutSeconds,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -187,7 +187,7 @@ func (m *StopVMRequest) GetTimeoutSeconds() uint32 { } type GetVMInfoRequest struct { - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -225,11 +225,11 @@ func (m *GetVMInfoRequest) GetVMID() string { } type GetVMInfoResponse struct { - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` - ContextID uint32 `protobuf:"varint,2,opt,name=ContextID,proto3" json:"ContextID,omitempty"` - SocketPath string `protobuf:"bytes,3,opt,name=SocketPath,proto3" json:"SocketPath,omitempty"` - LogFifoPath string `protobuf:"bytes,4,opt,name=LogFifoPath,proto3" json:"LogFifoPath,omitempty"` - MetricsFifoPath string `protobuf:"bytes,5,opt,name=MetricsFifoPath,proto3" json:"MetricsFifoPath,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` + ContextID uint32 `protobuf:"varint,2,opt,name=ContextID,json=contextID,proto3" json:"ContextID,omitempty"` + SocketPath string `protobuf:"bytes,3,opt,name=SocketPath,json=socketPath,proto3" json:"SocketPath,omitempty"` + LogFifoPath string `protobuf:"bytes,4,opt,name=LogFifoPath,json=logFifoPath,proto3" json:"LogFifoPath,omitempty"` + MetricsFifoPath string `protobuf:"bytes,5,opt,name=MetricsFifoPath,json=metricsFifoPath,proto3" json:"MetricsFifoPath,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -295,8 +295,8 @@ func (m *GetVMInfoResponse) GetMetricsFifoPath() string { } type SetVMMetadataRequest struct { - VMID string `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"` - Metadata string `protobuf:"bytes,2,opt,name=Metadata,proto3" json:"Metadata,omitempty"` + VMID string `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"` + Metadata string `protobuf:"bytes,2,opt,name=Metadata,json=metadata,proto3" json:"Metadata,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -382,35 +382,36 @@ func init() { func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_a73317e9fb8da571) } var fileDescriptor_a73317e9fb8da571 = []byte{ - // 467 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0xcf, 0x6f, 0xd3, 0x30, - 0x14, 0xc7, 0x15, 0xd6, 0x6e, 0xed, 0x2b, 0xed, 0x56, 0x8b, 0x83, 0x35, 0x4d, 0x28, 0xca, 0xa1, - 0xea, 0xa9, 0x08, 0xb8, 0x21, 0x21, 0x51, 0x5a, 0x8a, 0xba, 0x11, 0x84, 0xdc, 0xa9, 0x07, 0x6e, - 0x26, 0x79, 0xc9, 0xac, 0x66, 0x76, 0xb1, 0x5f, 0x61, 0xdc, 0xf9, 0x87, 0xf8, 0x0f, 0x51, 0x5c, - 0xb6, 0x66, 0xd9, 0xe8, 0x29, 0xf1, 0xe7, 0x7d, 0xdf, 0x0f, 0x3d, 0x7f, 0x0d, 0xfd, 0x4c, 0x59, - 0x4c, 0xac, 0x4c, 0x56, 0x68, 0x47, 0x6b, 0x6b, 0xc8, 0x9c, 0x76, 0xe8, 0xd7, 0x1a, 0xdd, 0xf6, - 0x10, 0xfd, 0x6e, 0xc0, 0xf1, 0xc4, 0xa2, 0x24, 0x5c, 0xc6, 0x02, 0xbf, 0x6f, 0xd0, 0x11, 0x63, - 0xd0, 0x58, 0xc6, 0xf3, 0x29, 0x0f, 0xc2, 0x60, 0xd8, 0x16, 0xfe, 0x9f, 0xbd, 0x03, 0x88, 0x65, - 0x72, 0xa5, 0x34, 0x4e, 0xb2, 0x9c, 0x3f, 0x09, 0x83, 0x61, 0xe7, 0x55, 0x38, 0x9a, 0xed, 0x8a, - 0xdf, 0x46, 0x8d, 0xce, 0x54, 0xbe, 0xb1, 0x92, 0x94, 0xd1, 0xa2, 0x92, 0xc3, 0x86, 0x70, 0x7c, - 0x81, 0x56, 0x63, 0x31, 0xbf, 0x96, 0x39, 0x7e, 0x91, 0x74, 0xc5, 0x0f, 0x7c, 0x83, 0x3a, 0x66, - 0xcf, 0x01, 0xb6, 0x68, 0x6c, 0x73, 0xc7, 0x1b, 0x5e, 0x54, 0x21, 0xec, 0x05, 0xb4, 0x85, 0x31, - 0x34, 0xb5, 0xea, 0x07, 0xf2, 0xa6, 0x1f, 0xa5, 0x5f, 0x1d, 0xc5, 0x07, 0xc4, 0x4e, 0xc3, 0xde, - 0xc2, 0xc9, 0x38, 0x4d, 0x55, 0x39, 0x92, 0x2c, 0x3c, 0x72, 0xfc, 0x30, 0x3c, 0x78, 0x3c, 0xef, - 0x81, 0x94, 0x9d, 0x43, 0xff, 0x33, 0xd2, 0x4f, 0x63, 0x57, 0x73, 0x4d, 0x68, 0x33, 0x99, 0xa0, - 0xe3, 0x47, 0x3e, 0xff, 0xac, 0x9a, 0x5f, 0x17, 0x89, 0x87, 0x69, 0x6c, 0x00, 0xbd, 0x89, 0xd1, - 0x24, 0x95, 0x46, 0x3b, 0x31, 0x1b, 0x4d, 0xbc, 0x15, 0x06, 0xc3, 0xa6, 0xa8, 0x51, 0xf6, 0x06, - 0xf8, 0x87, 0x1b, 0x45, 0xe3, 0x8c, 0xd0, 0x8e, 0x8b, 0xe2, 0x52, 0xba, 0x95, 0x9b, 0x62, 0x81, - 0x84, 0x29, 0x6f, 0x87, 0xc1, 0xb0, 0x25, 0xfe, 0x1b, 0x67, 0x2f, 0xe1, 0xe9, 0xb9, 0x54, 0x45, - 0x59, 0xaa, 0xbc, 0x0c, 0x0e, 0x7e, 0x45, 0xdd, 0x51, 0x15, 0x8a, 0x7b, 0x92, 0xe8, 0x02, 0xba, - 0x0b, 0x32, 0xeb, 0xfd, 0x1e, 0x18, 0x40, 0xef, 0x52, 0x5d, 0xa3, 0xd9, 0xd0, 0x02, 0x13, 0xa3, - 0x53, 0xe7, 0x7d, 0xd0, 0x15, 0x35, 0x1a, 0x0d, 0xe0, 0xe4, 0x23, 0xd2, 0x32, 0x9e, 0xeb, 0xcc, - 0xec, 0xa9, 0x17, 0xfd, 0x09, 0xa0, 0x5f, 0x11, 0xba, 0xb5, 0xd1, 0x0e, 0x1f, 0xed, 0x7c, 0x06, - 0xed, 0x72, 0x3f, 0x78, 0x43, 0xf3, 0xe9, 0xbf, 0xa6, 0x3b, 0x50, 0xfa, 0x65, 0x61, 0x92, 0x15, - 0x52, 0xc5, 0x54, 0x15, 0xc2, 0x42, 0xe8, 0x7c, 0x32, 0xf9, 0x4c, 0x65, 0xc6, 0x0b, 0xb6, 0x86, - 0xaa, 0xa2, 0xd2, 0x9b, 0x31, 0x92, 0x55, 0x89, 0xbb, 0x53, 0x35, 0xb7, 0xde, 0xac, 0xe1, 0x68, - 0x06, 0xcf, 0x16, 0xe5, 0xc8, 0x31, 0x92, 0x4c, 0x25, 0xc9, 0x7d, 0xfb, 0x3a, 0x85, 0xd6, 0xad, - 0xcc, 0x0f, 0xdd, 0x16, 0x77, 0xe7, 0xa8, 0x77, 0xff, 0x8e, 0xde, 0x1f, 0x7d, 0x6d, 0xfa, 0x07, - 0xf9, 0xed, 0xd0, 0x7f, 0x5e, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x98, 0x66, 0x19, 0x2a, 0xb9, - 0x03, 0x00, 0x00, + // 495 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x41, 0x6f, 0xd3, 0x4c, + 0x10, 0x95, 0xbf, 0x26, 0x6d, 0x3c, 0x6e, 0x93, 0x2f, 0x2b, 0x40, 0xab, 0xaa, 0x42, 0x96, 0x0f, + 0x91, 0x4f, 0x91, 0x68, 0x2f, 0x88, 0x13, 0x21, 0x21, 0xc8, 0x2d, 0x46, 0x68, 0x53, 0xe5, 0xc0, + 0x6d, 0x71, 0xc6, 0xa9, 0xb1, 0xb3, 0x1b, 0x76, 0x27, 0xa5, 0x9c, 0xf9, 0x45, 0xfc, 0x43, 0x64, + 0xa7, 0x49, 0x9c, 0x0a, 0x72, 0xf2, 0xce, 0x9b, 0xf7, 0x66, 0x46, 0x33, 0xcf, 0xd0, 0x4d, 0x33, + 0x83, 0x89, 0x91, 0x49, 0x8e, 0xa6, 0xbf, 0x34, 0x9a, 0xf4, 0xb9, 0x47, 0x3f, 0x97, 0x68, 0xd7, + 0x41, 0xf0, 0xab, 0x01, 0x9d, 0xa1, 0x41, 0x49, 0x38, 0x8d, 0x05, 0x7e, 0x5f, 0xa1, 0x25, 0xc6, + 0xa0, 0x31, 0x8d, 0xa3, 0x11, 0x77, 0x7c, 0x27, 0x74, 0x45, 0xe3, 0x3e, 0x8e, 0x46, 0xec, 0x2d, + 0x40, 0x2c, 0x93, 0xbb, 0x4c, 0xe1, 0x30, 0x9d, 0xf3, 0xff, 0x7c, 0x27, 0xf4, 0x2e, 0xfd, 0xfe, + 0x78, 0x57, 0x7c, 0x93, 0xd5, 0x2a, 0xcd, 0xe6, 0x2b, 0x23, 0x29, 0xd3, 0x4a, 0xc0, 0x62, 0xab, + 0x61, 0x21, 0x74, 0x6e, 0xd0, 0x28, 0x2c, 0xa2, 0x85, 0x9c, 0xe3, 0x67, 0x49, 0x77, 0xfc, 0xa8, + 0x6a, 0xd0, 0xc9, 0xf7, 0x61, 0xf6, 0x12, 0x60, 0xcd, 0x1c, 0x98, 0xb9, 0xe5, 0x8d, 0x8a, 0x04, + 0xf9, 0x16, 0x61, 0x57, 0xe0, 0x0a, 0xad, 0x69, 0x64, 0xb2, 0x7b, 0xe4, 0xcd, 0x6a, 0x94, 0xe7, + 0xf5, 0x51, 0xb6, 0x49, 0xe1, 0x9a, 0xcd, 0x93, 0xbd, 0x06, 0xaf, 0x7a, 0xc4, 0x7a, 0xa5, 0xc8, + 0xf2, 0x63, 0xff, 0x28, 0xf4, 0x2e, 0x5f, 0xd4, 0x65, 0xbb, 0xb4, 0xf0, 0x66, 0x3b, 0x2a, 0xbb, + 0x86, 0xee, 0x27, 0xa4, 0x1f, 0xda, 0xe4, 0x91, 0x22, 0x34, 0xa9, 0x4c, 0xd0, 0xf2, 0x93, 0x4a, + 0x7f, 0x51, 0xd7, 0x3f, 0x25, 0x89, 0xae, 0x7a, 0x2a, 0x63, 0x3d, 0x68, 0x0f, 0xb5, 0x22, 0x99, + 0x29, 0x34, 0xc3, 0xb2, 0x3c, 0x6f, 0xf9, 0x4e, 0xd8, 0x14, 0xed, 0x64, 0x0f, 0x65, 0x6f, 0x80, + 0xbf, 0x7f, 0xc8, 0x68, 0x90, 0x12, 0x9a, 0x41, 0x51, 0xdc, 0x4a, 0x9b, 0xdb, 0x11, 0x16, 0x48, + 0x38, 0xe3, 0xae, 0xef, 0x84, 0x2d, 0xc1, 0xf1, 0x1f, 0x79, 0xf6, 0x0a, 0x4e, 0xaf, 0x65, 0x56, + 0x94, 0xa5, 0xca, 0x5b, 0x70, 0xa8, 0x36, 0x74, 0xd6, 0xaf, 0x83, 0xe2, 0xf4, 0x5b, 0x2d, 0x0a, + 0x6e, 0xe0, 0x6c, 0x42, 0x7a, 0x79, 0xd8, 0x02, 0x3d, 0x68, 0xdf, 0x66, 0x0b, 0xd4, 0x2b, 0x9a, + 0x60, 0xa2, 0xd5, 0xcc, 0x56, 0x36, 0x38, 0x13, 0x6d, 0xda, 0x43, 0x83, 0x1e, 0xfc, 0xff, 0x01, + 0x69, 0x1a, 0x47, 0x2a, 0xd5, 0x07, 0xea, 0x05, 0xbf, 0x1d, 0xe8, 0xd6, 0x88, 0x76, 0xa9, 0x95, + 0xc5, 0xbf, 0x76, 0xbe, 0x00, 0xb7, 0xdc, 0x1a, 0x3e, 0x50, 0x34, 0x7a, 0x6c, 0xea, 0x26, 0x1b, + 0xa0, 0xb4, 0xcb, 0x44, 0x27, 0x39, 0x52, 0xcd, 0x53, 0x60, 0xb7, 0x08, 0xf3, 0xc1, 0xfb, 0xa8, + 0xe7, 0xe3, 0x2c, 0xd5, 0x15, 0x61, 0xed, 0x27, 0xaf, 0xd8, 0x41, 0xa5, 0x35, 0x63, 0x24, 0x93, + 0x25, 0x76, 0xcb, 0x6a, 0xae, 0xad, 0xb9, 0xd8, 0x87, 0x83, 0x31, 0x3c, 0x9b, 0x94, 0x23, 0xc7, + 0x48, 0x72, 0x26, 0x49, 0x1e, 0xda, 0xd7, 0x39, 0xb4, 0x36, 0xb4, 0x6a, 0x68, 0x57, 0xb4, 0x16, + 0x8f, 0x71, 0xd0, 0xde, 0xbf, 0xd1, 0xbb, 0x93, 0x2f, 0xcd, 0xea, 0x7f, 0xfc, 0x7a, 0x5c, 0x7d, + 0xae, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x5d, 0x86, 0x33, 0xb8, 0x03, 0x00, 0x00, } diff --git a/proto/firecracker.proto b/proto/firecracker.proto index bff0d4197..4034ff9c8 100644 --- a/proto/firecracker.proto +++ b/proto/firecracker.proto @@ -18,11 +18,11 @@ message CreateVMRequest { // Specifies the commandline arguments that should be passed to the kernel string KernelArgs = 4; - // Specifies the root block device for the VM - FirecrackerDrive RootDrive = 5; + // Specifies drive containing the rootfs of the VM + FirecrackerRootDrive RootDrive = 5; - // Specifies the additional block device config for the VM. - repeated FirecrackerDrive AdditionalDrives = 6; + // Specifies additional drives whose contents will be mounted inside the VM on boot. + repeated FirecrackerDriveMount DriveMounts = 6; // Specifies the networking configuration for a VM repeated FirecrackerNetworkInterface NetworkInterfaces = 7; diff --git a/proto/service/drivemount/Makefile b/proto/service/drivemount/Makefile new file mode 100644 index 000000000..a882e0da0 --- /dev/null +++ b/proto/service/drivemount/Makefile @@ -0,0 +1,29 @@ +# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may +# not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +PROTO_SRC := $(wildcard *.proto) +PROTO_GEN_SRC := $(PROTO_SRC:.proto=.pb.go) +PROTO_GEN_SRC_TTRPC := $(addprefix ttrpc/,$(PROTO_GEN_SRC)) + +$(PROTO_GEN_SRC_TTRPC): $(PROTO_SRC) + protoc -I. -I$(PROTOPATH)\ + --gogottrpc_out=plugins=ttrpc:ttrpc \ + $^ + + +proto: $(PROTO_GEN_SRC_TTRPC) + +clean: + - rm -f $(PROTO_GEN_SRC_TTRPC) + +.PHONY: clean proto diff --git a/proto/service/drivemount/drivemount.proto b/proto/service/drivemount/drivemount.proto new file mode 100644 index 000000000..5ebe0202c --- /dev/null +++ b/proto/service/drivemount/drivemount.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; + +option go_package = "drivemount"; + +service DriveMounter { + rpc MountDrive(MountDriveRequest) returns (google.protobuf.Empty); +} + +message MountDriveRequest { + string DriveID = 1; + string DestinationPath = 2; + string FilesytemType = 3; + repeated string Options = 4; +} \ No newline at end of file diff --git a/proto/service/drivemount/ttrpc/drivemount.pb.go b/proto/service/drivemount/ttrpc/drivemount.pb.go new file mode 100644 index 000000000..2beb99f74 --- /dev/null +++ b/proto/service/drivemount/ttrpc/drivemount.pb.go @@ -0,0 +1,522 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: drivemount.proto + +package drivemount + +import ( + context "context" + fmt "fmt" + github_com_containerd_ttrpc "github.com/containerd/ttrpc" + proto "github.com/gogo/protobuf/proto" + empty "github.com/golang/protobuf/ptypes/empty" + io "io" + math "math" + math_bits "math/bits" + reflect "reflect" + strings "strings" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type MountDriveRequest struct { + DriveID string `protobuf:"bytes,1,opt,name=DriveID,json=driveID,proto3" json:"DriveID,omitempty"` + DestinationPath string `protobuf:"bytes,2,opt,name=DestinationPath,json=destinationPath,proto3" json:"DestinationPath,omitempty"` + FilesytemType string `protobuf:"bytes,3,opt,name=FilesytemType,json=filesytemType,proto3" json:"FilesytemType,omitempty"` + Options []string `protobuf:"bytes,4,rep,name=Options,json=options,proto3" json:"Options,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MountDriveRequest) Reset() { *m = MountDriveRequest{} } +func (*MountDriveRequest) ProtoMessage() {} +func (*MountDriveRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_48455e9fea9a7da5, []int{0} +} +func (m *MountDriveRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MountDriveRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MountDriveRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MountDriveRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MountDriveRequest.Merge(m, src) +} +func (m *MountDriveRequest) XXX_Size() int { + return m.Size() +} +func (m *MountDriveRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MountDriveRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MountDriveRequest proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MountDriveRequest)(nil), "MountDriveRequest") +} + +func init() { proto.RegisterFile("drivemount.proto", fileDescriptor_48455e9fea9a7da5) } + +var fileDescriptor_48455e9fea9a7da5 = []byte{ + // 240 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x29, 0xca, 0x2c, + 0x4b, 0xcd, 0xcd, 0x2f, 0xcd, 0x2b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x97, 0x92, 0x4e, 0xcf, + 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0xf3, 0x92, 0x4a, 0xd3, 0xf4, 0x53, 0x73, 0x0b, 0x4a, 0x2a, + 0x21, 0x92, 0x4a, 0xd3, 0x19, 0xb9, 0x04, 0x7d, 0x41, 0x8a, 0x5d, 0x40, 0xda, 0x82, 0x52, 0x0b, + 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x24, 0xb8, 0xd8, 0xc1, 0x7c, 0x4f, 0x17, 0x09, 0x46, 0x05, 0x46, + 0x0d, 0xce, 0x20, 0xf6, 0x14, 0x08, 0x57, 0x48, 0x83, 0x8b, 0xdf, 0x25, 0xb5, 0xb8, 0x24, 0x33, + 0x2f, 0xb1, 0x24, 0x33, 0x3f, 0x2f, 0x20, 0xb1, 0x24, 0x43, 0x82, 0x09, 0xac, 0x82, 0x3f, 0x05, + 0x55, 0x58, 0x48, 0x85, 0x8b, 0xd7, 0x2d, 0x33, 0x27, 0xb5, 0xb8, 0xb2, 0x24, 0x35, 0x37, 0xa4, + 0xb2, 0x20, 0x55, 0x82, 0x19, 0xac, 0x8e, 0x37, 0x0d, 0x59, 0x10, 0x64, 0x93, 0x7f, 0x01, 0x48, + 0x4f, 0xb1, 0x04, 0x8b, 0x02, 0x33, 0xc8, 0xa6, 0x7c, 0x08, 0xd7, 0xc8, 0x83, 0x8b, 0x07, 0xec, + 0x06, 0xb0, 0xeb, 0x52, 0x8b, 0x84, 0x2c, 0xb8, 0xb8, 0x10, 0x0e, 0x15, 0x12, 0xd2, 0xc3, 0x70, + 0xb5, 0x94, 0x98, 0x1e, 0xc4, 0xa7, 0x7a, 0x30, 0x9f, 0xea, 0xb9, 0x82, 0x7c, 0xea, 0xa4, 0x72, + 0xe2, 0xa1, 0x1c, 0xc3, 0x8d, 0x87, 0x72, 0x0c, 0x0d, 0x8f, 0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63, + 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x28, 0x2e, 0x44, 0x60, 0x25, 0xb1, 0x81, + 0x75, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc1, 0xad, 0x30, 0xb3, 0x41, 0x01, 0x00, 0x00, +} + +func (m *MountDriveRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MountDriveRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MountDriveRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Options) > 0 { + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Options[iNdEx]) + copy(dAtA[i:], m.Options[iNdEx]) + i = encodeVarintDrivemount(dAtA, i, uint64(len(m.Options[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + if len(m.FilesytemType) > 0 { + i -= len(m.FilesytemType) + copy(dAtA[i:], m.FilesytemType) + i = encodeVarintDrivemount(dAtA, i, uint64(len(m.FilesytemType))) + i-- + dAtA[i] = 0x1a + } + if len(m.DestinationPath) > 0 { + i -= len(m.DestinationPath) + copy(dAtA[i:], m.DestinationPath) + i = encodeVarintDrivemount(dAtA, i, uint64(len(m.DestinationPath))) + i-- + dAtA[i] = 0x12 + } + if len(m.DriveID) > 0 { + i -= len(m.DriveID) + copy(dAtA[i:], m.DriveID) + i = encodeVarintDrivemount(dAtA, i, uint64(len(m.DriveID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintDrivemount(dAtA []byte, offset int, v uint64) int { + offset -= sovDrivemount(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MountDriveRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.DriveID) + if l > 0 { + n += 1 + l + sovDrivemount(uint64(l)) + } + l = len(m.DestinationPath) + if l > 0 { + n += 1 + l + sovDrivemount(uint64(l)) + } + l = len(m.FilesytemType) + if l > 0 { + n += 1 + l + sovDrivemount(uint64(l)) + } + if len(m.Options) > 0 { + for _, s := range m.Options { + l = len(s) + n += 1 + l + sovDrivemount(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovDrivemount(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDrivemount(x uint64) (n int) { + return sovDrivemount(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *MountDriveRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MountDriveRequest{`, + `DriveID:` + fmt.Sprintf("%v", this.DriveID) + `,`, + `DestinationPath:` + fmt.Sprintf("%v", this.DestinationPath) + `,`, + `FilesytemType:` + fmt.Sprintf("%v", this.FilesytemType) + `,`, + `Options:` + fmt.Sprintf("%v", this.Options) + `,`, + `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, + `}`, + }, "") + return s +} +func valueToStringDrivemount(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} + +type DriveMounterService interface { + MountDrive(ctx context.Context, req *MountDriveRequest) (*empty.Empty, error) +} + +func RegisterDriveMounterService(srv *github_com_containerd_ttrpc.Server, svc DriveMounterService) { + srv.Register("DriveMounter", map[string]github_com_containerd_ttrpc.Method{ + "MountDrive": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req MountDriveRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.MountDrive(ctx, &req) + }, + }) +} + +type driveMounterClient struct { + client *github_com_containerd_ttrpc.Client +} + +func NewDriveMounterClient(client *github_com_containerd_ttrpc.Client) DriveMounterService { + return &driveMounterClient{ + client: client, + } +} + +func (c *driveMounterClient) MountDrive(ctx context.Context, req *MountDriveRequest) (*empty.Empty, error) { + var resp empty.Empty + if err := c.client.Call(ctx, "DriveMounter", "MountDrive", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} +func (m *MountDriveRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDrivemount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MountDriveRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MountDriveRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DriveID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDrivemount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDrivemount + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDrivemount + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DriveID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationPath", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDrivemount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDrivemount + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDrivemount + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationPath = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FilesytemType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDrivemount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDrivemount + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDrivemount + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FilesytemType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Options", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDrivemount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDrivemount + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDrivemount + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Options = append(m.Options, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDrivemount(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDrivemount + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthDrivemount + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDrivemount(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDrivemount + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDrivemount + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDrivemount + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthDrivemount + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupDrivemount + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthDrivemount + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthDrivemount = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDrivemount = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupDrivemount = fmt.Errorf("proto: unexpected end of group") +) diff --git a/proto/types.pb.go b/proto/types.pb.go index 9c533be92..9b734809f 100644 --- a/proto/types.pb.go +++ b/proto/types.pb.go @@ -23,12 +23,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Message to store bundle/config.json bytes type ExtraData struct { - JsonSpec []byte `protobuf:"bytes,1,opt,name=JsonSpec,proto3" json:"JsonSpec,omitempty"` - RuncOptions *types.Any `protobuf:"bytes,2,opt,name=RuncOptions,proto3" json:"RuncOptions,omitempty"` - StdinPort uint32 `protobuf:"varint,3,opt,name=StdinPort,proto3" json:"StdinPort,omitempty"` - StdoutPort uint32 `protobuf:"varint,4,opt,name=StdoutPort,proto3" json:"StdoutPort,omitempty"` - StderrPort uint32 `protobuf:"varint,5,opt,name=StderrPort,proto3" json:"StderrPort,omitempty"` - DriveID string `protobuf:"bytes,6,opt,name=DriveID,proto3" json:"DriveID,omitempty"` + JsonSpec []byte `protobuf:"bytes,1,opt,name=JsonSpec,json=jsonSpec,proto3" json:"JsonSpec,omitempty"` + RuncOptions *types.Any `protobuf:"bytes,2,opt,name=RuncOptions,json=runcOptions,proto3" json:"RuncOptions,omitempty"` + StdinPort uint32 `protobuf:"varint,3,opt,name=StdinPort,json=stdinPort,proto3" json:"StdinPort,omitempty"` + StdoutPort uint32 `protobuf:"varint,4,opt,name=StdoutPort,json=stdoutPort,proto3" json:"StdoutPort,omitempty"` + StderrPort uint32 `protobuf:"varint,5,opt,name=StderrPort,json=stderrPort,proto3" json:"StderrPort,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -93,24 +92,17 @@ func (m *ExtraData) GetStderrPort() uint32 { return 0 } -func (m *ExtraData) GetDriveID() string { - if m != nil { - return m.DriveID - } - return "" -} - // Message to specify network config for a Firecracker VM type FirecrackerNetworkInterface struct { - AllowMMDS bool `protobuf:"varint,1,opt,name=AllowMMDS,proto3" json:"AllowMMDS,omitempty"` - InRateLimiter *FirecrackerRateLimiter `protobuf:"bytes,2,opt,name=InRateLimiter,proto3" json:"InRateLimiter,omitempty"` - OutRateLimiter *FirecrackerRateLimiter `protobuf:"bytes,3,opt,name=OutRateLimiter,proto3" json:"OutRateLimiter,omitempty"` + AllowMMDS bool `protobuf:"varint,1,opt,name=AllowMMDS,json=allowMMDS,proto3" json:"AllowMMDS,omitempty"` + InRateLimiter *FirecrackerRateLimiter `protobuf:"bytes,2,opt,name=InRateLimiter,json=inRateLimiter,proto3" json:"InRateLimiter,omitempty"` + OutRateLimiter *FirecrackerRateLimiter `protobuf:"bytes,3,opt,name=OutRateLimiter,json=outRateLimiter,proto3" json:"OutRateLimiter,omitempty"` // CNIConfiguration specifies CNI configuration that will be used to generate // a network interface for a Firecracker VM. - CNIConfig *CNIConfiguration `protobuf:"bytes,4,opt,name=CNIConfig,proto3" json:"CNIConfig,omitempty"` + CNIConfig *CNIConfiguration `protobuf:"bytes,4,opt,name=CNIConfig,json=cNIConfig,proto3" json:"CNIConfig,omitempty"` // StaticNetworkConfiguration specifies static configuration parameters for a // Firecracker VM's network interface - StaticConfig *StaticNetworkConfiguration `protobuf:"bytes,5,opt,name=StaticConfig,proto3" json:"StaticConfig,omitempty"` + StaticConfig *StaticNetworkConfiguration `protobuf:"bytes,5,opt,name=StaticConfig,json=staticConfig,proto3" json:"StaticConfig,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -181,23 +173,23 @@ type CNIConfiguration struct { // NetworkName is the name of a CNI network (as found in CNI // configuration files) that will be used to generate the // network interface. - NetworkName string `protobuf:"bytes,1,opt,name=NetworkName,proto3" json:"NetworkName,omitempty"` + NetworkName string `protobuf:"bytes,1,opt,name=NetworkName,json=networkName,proto3" json:"NetworkName,omitempty"` // InterfaceName corresponds to the CNI_IFNAME parameter that will be // provided to CNI plugins during invocation. - InterfaceName string `protobuf:"bytes,2,opt,name=InterfaceName,proto3" json:"InterfaceName,omitempty"` + InterfaceName string `protobuf:"bytes,2,opt,name=InterfaceName,json=interfaceName,proto3" json:"InterfaceName,omitempty"` // BinPath is a list of directories that will be searched when // looking for CNI plugin binaries. Defaults to just "/opt/cni/bin" - BinPath []string `protobuf:"bytes,3,rep,name=BinPath,proto3" json:"BinPath,omitempty"` + BinPath []string `protobuf:"bytes,3,rep,name=BinPath,json=binPath,proto3" json:"BinPath,omitempty"` // ConfDir is the directory in which CNI configuration will be sought. // If not specified, will default to "/etc/cni/conf.d". - ConfDir string `protobuf:"bytes,4,opt,name=ConfDir,proto3" json:"ConfDir,omitempty"` + ConfDir string `protobuf:"bytes,4,opt,name=ConfDir,json=confDir,proto3" json:"ConfDir,omitempty"` // CacheDir is the directory in which CNI results will be temporarily // cached by the runtime. If not specified, it will default to // "/var/lib/cni" - CacheDir string `protobuf:"bytes,5,opt,name=CacheDir,proto3" json:"CacheDir,omitempty"` + CacheDir string `protobuf:"bytes,5,opt,name=CacheDir,json=cacheDir,proto3" json:"CacheDir,omitempty"` // Args corresponds to the CNI_ARGS parameter that will be provided to // CNI plugins on invocation. - Args []*CNIConfiguration_CNIArg `protobuf:"bytes,6,rep,name=Args,proto3" json:"Args,omitempty"` + Args []*CNIConfiguration_CNIArg `protobuf:"bytes,6,rep,name=Args,json=args,proto3" json:"Args,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -270,8 +262,8 @@ func (m *CNIConfiguration) GetArgs() []*CNIConfiguration_CNIArg { } type CNIConfiguration_CNIArg struct { - Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"` + Key string `protobuf:"bytes,1,opt,name=Key,json=key,proto3" json:"Key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=Value,json=value,proto3" json:"Value,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -318,12 +310,12 @@ func (m *CNIConfiguration_CNIArg) GetValue() string { // Message to specify static configuration parameters for a // Firecracker VM's network interface type StaticNetworkConfiguration struct { - MacAddress string `protobuf:"bytes,1,opt,name=MacAddress,proto3" json:"MacAddress,omitempty"` - HostDevName string `protobuf:"bytes,2,opt,name=HostDevName,proto3" json:"HostDevName,omitempty"` + MacAddress string `protobuf:"bytes,1,opt,name=MacAddress,json=macAddress,proto3" json:"MacAddress,omitempty"` + HostDevName string `protobuf:"bytes,2,opt,name=HostDevName,json=hostDevName,proto3" json:"HostDevName,omitempty"` // IPConfig optionally provides static IP configuration that will be configured // on the VM's internal networking interface. If not specified, no IP // configuration will be applied to the VM's internal nic automatically. - IPConfig *IPConfiguration `protobuf:"bytes,3,opt,name=IPConfig,proto3" json:"IPConfig,omitempty"` + IPConfig *IPConfiguration `protobuf:"bytes,3,opt,name=IPConfig,json=iPConfig,proto3" json:"IPConfig,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -380,15 +372,15 @@ type IPConfiguration struct { // PrimaryAddr specifies, in CIDR notation, the primary address // and subnet that a network interface will be assigned inside // the VM. - PrimaryAddr string `protobuf:"bytes,1,opt,name=PrimaryAddr,proto3" json:"PrimaryAddr,omitempty"` + PrimaryAddr string `protobuf:"bytes,1,opt,name=PrimaryAddr,json=primaryAddr,proto3" json:"PrimaryAddr,omitempty"` // GatewayAddr specifies the default gateway that a network interface // should use inside the VM. - GatewayAddr string `protobuf:"bytes,3,opt,name=GatewayAddr,proto3" json:"GatewayAddr,omitempty"` + GatewayAddr string `protobuf:"bytes,3,opt,name=GatewayAddr,json=gatewayAddr,proto3" json:"GatewayAddr,omitempty"` // Nameservers is a list of nameservers that the VM will be configured // to use internally. Currently only up to 2 nameservers can be specified // (any more in the list will be ignored) and configuration is provided // to the VM via /proc/net/pnp. - Nameservers []string `protobuf:"bytes,4,rep,name=Nameservers,proto3" json:"Nameservers,omitempty"` + Nameservers []string `protobuf:"bytes,4,rep,name=Nameservers,json=nameservers,proto3" json:"Nameservers,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -441,13 +433,13 @@ func (m *IPConfiguration) GetNameservers() []string { // Message to set the machine config for a Firecracker VM type FirecrackerMachineConfiguration struct { - CPUTemplate string `protobuf:"bytes,1,opt,name=CPUTemplate,proto3" json:"CPUTemplate,omitempty"` - HtEnabled bool `protobuf:"varint,2,opt,name=HtEnabled,proto3" json:"HtEnabled,omitempty"` + CPUTemplate string `protobuf:"bytes,1,opt,name=CPUTemplate,json=cPUTemplate,proto3" json:"CPUTemplate,omitempty"` + HtEnabled bool `protobuf:"varint,2,opt,name=HtEnabled,json=htEnabled,proto3" json:"HtEnabled,omitempty"` // Specifies the memory size of VM // This lets us create a Firecracker VM of up to 4096 TiB, which // for a microVM should be large enough - MemSizeMib uint32 `protobuf:"varint,3,opt,name=MemSizeMib,proto3" json:"MemSizeMib,omitempty"` - VcpuCount uint32 `protobuf:"varint,4,opt,name=VcpuCount,proto3" json:"VcpuCount,omitempty"` + MemSizeMib uint32 `protobuf:"varint,3,opt,name=MemSizeMib,json=memSizeMib,proto3" json:"MemSizeMib,omitempty"` + VcpuCount uint32 `protobuf:"varint,4,opt,name=VcpuCount,json=vcpuCount,proto3" json:"VcpuCount,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -506,80 +498,153 @@ func (m *FirecrackerMachineConfiguration) GetVcpuCount() uint32 { } // Message to specify the block device config for a Firecracker VM -type FirecrackerDrive struct { - IsWritable bool `protobuf:"varint,1,opt,name=IsWritable,proto3" json:"IsWritable,omitempty"` - IsRootDevice bool `protobuf:"varint,2,opt,name=IsRootDevice,proto3" json:"IsRootDevice,omitempty"` - Partuuid string `protobuf:"bytes,3,opt,name=Partuuid,proto3" json:"Partuuid,omitempty"` - PathOnHost string `protobuf:"bytes,4,opt,name=PathOnHost,proto3" json:"PathOnHost,omitempty"` - RateLimiter *FirecrackerRateLimiter `protobuf:"bytes,5,opt,name=RateLimiter,proto3" json:"RateLimiter,omitempty"` +type FirecrackerRootDrive struct { + // (Required) HostPath is the path on the host to the filesystem image or device + // that will supply the rootfs of the VM. + HostPath string `protobuf:"bytes,1,opt,name=HostPath,json=hostPath,proto3" json:"HostPath,omitempty"` + // (Optional) If the HostPath points to a drive or image with multiple + // partitions, Partuuid specifies which partition will be used to boot + // the VM + Partuuid string `protobuf:"bytes,2,opt,name=Partuuid,json=partuuid,proto3" json:"Partuuid,omitempty"` + // (Optional) If set to true, IsWritable results in the VM Guest's rootfs + // being mounted as read-write. Defaults to false, in which case the + // rootfs is mounted as read-only. + IsWritable bool `protobuf:"varint,3,opt,name=IsWritable,json=isWritable,proto3" json:"IsWritable,omitempty"` + // (Optional) RateLimiter configuration that will be applied to the + // backing-drive for the VM's rootfs + RateLimiter *FirecrackerRateLimiter `protobuf:"bytes,4,opt,name=RateLimiter,json=rateLimiter,proto3" json:"RateLimiter,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *FirecrackerDrive) Reset() { *m = FirecrackerDrive{} } -func (m *FirecrackerDrive) String() string { return proto.CompactTextString(m) } -func (*FirecrackerDrive) ProtoMessage() {} -func (*FirecrackerDrive) Descriptor() ([]byte, []int) { +func (m *FirecrackerRootDrive) Reset() { *m = FirecrackerRootDrive{} } +func (m *FirecrackerRootDrive) String() string { return proto.CompactTextString(m) } +func (*FirecrackerRootDrive) ProtoMessage() {} +func (*FirecrackerRootDrive) Descriptor() ([]byte, []int) { return fileDescriptor_d938547f84707355, []int{6} } -func (m *FirecrackerDrive) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FirecrackerDrive.Unmarshal(m, b) +func (m *FirecrackerRootDrive) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FirecrackerRootDrive.Unmarshal(m, b) +} +func (m *FirecrackerRootDrive) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FirecrackerRootDrive.Marshal(b, m, deterministic) } -func (m *FirecrackerDrive) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FirecrackerDrive.Marshal(b, m, deterministic) +func (m *FirecrackerRootDrive) XXX_Merge(src proto.Message) { + xxx_messageInfo_FirecrackerRootDrive.Merge(m, src) } -func (m *FirecrackerDrive) XXX_Merge(src proto.Message) { - xxx_messageInfo_FirecrackerDrive.Merge(m, src) +func (m *FirecrackerRootDrive) XXX_Size() int { + return xxx_messageInfo_FirecrackerRootDrive.Size(m) } -func (m *FirecrackerDrive) XXX_Size() int { - return xxx_messageInfo_FirecrackerDrive.Size(m) +func (m *FirecrackerRootDrive) XXX_DiscardUnknown() { + xxx_messageInfo_FirecrackerRootDrive.DiscardUnknown(m) } -func (m *FirecrackerDrive) XXX_DiscardUnknown() { - xxx_messageInfo_FirecrackerDrive.DiscardUnknown(m) + +var xxx_messageInfo_FirecrackerRootDrive proto.InternalMessageInfo + +func (m *FirecrackerRootDrive) GetHostPath() string { + if m != nil { + return m.HostPath + } + return "" } -var xxx_messageInfo_FirecrackerDrive proto.InternalMessageInfo +func (m *FirecrackerRootDrive) GetPartuuid() string { + if m != nil { + return m.Partuuid + } + return "" +} -func (m *FirecrackerDrive) GetIsWritable() bool { +func (m *FirecrackerRootDrive) GetIsWritable() bool { if m != nil { return m.IsWritable } return false } -func (m *FirecrackerDrive) GetIsRootDevice() bool { +func (m *FirecrackerRootDrive) GetRateLimiter() *FirecrackerRateLimiter { if m != nil { - return m.IsRootDevice + return m.RateLimiter } - return false + return nil } -func (m *FirecrackerDrive) GetPartuuid() string { +type FirecrackerDriveMount struct { + // (Required) HostPath is the path on the host to the filesystem image or device + // that will be mounted inside the VM. + HostPath string `protobuf:"bytes,1,opt,name=HostPath,json=hostPath,proto3" json:"HostPath,omitempty"` + // (Required) VMPath is the path inside the VM guest at which the filesystem + // image or device will be mounted. + VMPath string `protobuf:"bytes,2,opt,name=VMPath,json=vMPath,proto3" json:"VMPath,omitempty"` + // (Required) FilesystemType is the filesystem type (i.e. ext4, xfs, etc.), as + // used when mounting the filesystem image inside the VM. The VM guest kernel + // is expected to have support for this filesystem. + FilesystemType string `protobuf:"bytes,3,opt,name=FilesystemType,json=filesystemType,proto3" json:"FilesystemType,omitempty"` + // (Optional) Options are fstab-style options that the mount will be performed + // within the VM (i.e. ["rw", "noatime"]). Defaults to none if not specified. + Options []string `protobuf:"bytes,4,rep,name=Options,json=options,proto3" json:"Options,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FirecrackerDriveMount) Reset() { *m = FirecrackerDriveMount{} } +func (m *FirecrackerDriveMount) String() string { return proto.CompactTextString(m) } +func (*FirecrackerDriveMount) ProtoMessage() {} +func (*FirecrackerDriveMount) Descriptor() ([]byte, []int) { + return fileDescriptor_d938547f84707355, []int{7} +} +func (m *FirecrackerDriveMount) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FirecrackerDriveMount.Unmarshal(m, b) +} +func (m *FirecrackerDriveMount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FirecrackerDriveMount.Marshal(b, m, deterministic) +} +func (m *FirecrackerDriveMount) XXX_Merge(src proto.Message) { + xxx_messageInfo_FirecrackerDriveMount.Merge(m, src) +} +func (m *FirecrackerDriveMount) XXX_Size() int { + return xxx_messageInfo_FirecrackerDriveMount.Size(m) +} +func (m *FirecrackerDriveMount) XXX_DiscardUnknown() { + xxx_messageInfo_FirecrackerDriveMount.DiscardUnknown(m) +} + +var xxx_messageInfo_FirecrackerDriveMount proto.InternalMessageInfo + +func (m *FirecrackerDriveMount) GetHostPath() string { if m != nil { - return m.Partuuid + return m.HostPath } return "" } -func (m *FirecrackerDrive) GetPathOnHost() string { +func (m *FirecrackerDriveMount) GetVMPath() string { if m != nil { - return m.PathOnHost + return m.VMPath } return "" } -func (m *FirecrackerDrive) GetRateLimiter() *FirecrackerRateLimiter { +func (m *FirecrackerDriveMount) GetFilesystemType() string { if m != nil { - return m.RateLimiter + return m.FilesystemType + } + return "" +} + +func (m *FirecrackerDriveMount) GetOptions() []string { + if m != nil { + return m.Options } return nil } // Message to specify an IO rate limiter with bytes/s and ops/s limits type FirecrackerRateLimiter struct { - Bandwidth *FirecrackerTokenBucket `protobuf:"bytes,1,opt,name=Bandwidth,proto3" json:"Bandwidth,omitempty"` - Ops *FirecrackerTokenBucket `protobuf:"bytes,2,opt,name=Ops,proto3" json:"Ops,omitempty"` + Bandwidth *FirecrackerTokenBucket `protobuf:"bytes,1,opt,name=Bandwidth,json=bandwidth,proto3" json:"Bandwidth,omitempty"` + Ops *FirecrackerTokenBucket `protobuf:"bytes,2,opt,name=Ops,json=ops,proto3" json:"Ops,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -589,7 +654,7 @@ func (m *FirecrackerRateLimiter) Reset() { *m = FirecrackerRateLimiter{} func (m *FirecrackerRateLimiter) String() string { return proto.CompactTextString(m) } func (*FirecrackerRateLimiter) ProtoMessage() {} func (*FirecrackerRateLimiter) Descriptor() ([]byte, []int) { - return fileDescriptor_d938547f84707355, []int{7} + return fileDescriptor_d938547f84707355, []int{8} } func (m *FirecrackerRateLimiter) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_FirecrackerRateLimiter.Unmarshal(m, b) @@ -625,9 +690,9 @@ func (m *FirecrackerRateLimiter) GetOps() *FirecrackerTokenBucket { // Message to specify a token buicket used to rate limit disk and network IO for a Firecracker VM type FirecrackerTokenBucket struct { - OneTimeBurst int64 `protobuf:"varint,1,opt,name=OneTimeBurst,proto3" json:"OneTimeBurst,omitempty"` - RefillTime int64 `protobuf:"varint,2,opt,name=RefillTime,proto3" json:"RefillTime,omitempty"` - Capacity int64 `protobuf:"varint,3,opt,name=Capacity,proto3" json:"Capacity,omitempty"` + OneTimeBurst int64 `protobuf:"varint,1,opt,name=OneTimeBurst,json=oneTimeBurst,proto3" json:"OneTimeBurst,omitempty"` + RefillTime int64 `protobuf:"varint,2,opt,name=RefillTime,json=refillTime,proto3" json:"RefillTime,omitempty"` + Capacity int64 `protobuf:"varint,3,opt,name=Capacity,json=capacity,proto3" json:"Capacity,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -637,7 +702,7 @@ func (m *FirecrackerTokenBucket) Reset() { *m = FirecrackerTokenBucket{} func (m *FirecrackerTokenBucket) String() string { return proto.CompactTextString(m) } func (*FirecrackerTokenBucket) ProtoMessage() {} func (*FirecrackerTokenBucket) Descriptor() ([]byte, []int) { - return fileDescriptor_d938547f84707355, []int{8} + return fileDescriptor_d938547f84707355, []int{9} } func (m *FirecrackerTokenBucket) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_FirecrackerTokenBucket.Unmarshal(m, b) @@ -686,7 +751,8 @@ func init() { proto.RegisterType((*StaticNetworkConfiguration)(nil), "StaticNetworkConfiguration") proto.RegisterType((*IPConfiguration)(nil), "IPConfiguration") proto.RegisterType((*FirecrackerMachineConfiguration)(nil), "FirecrackerMachineConfiguration") - proto.RegisterType((*FirecrackerDrive)(nil), "FirecrackerDrive") + proto.RegisterType((*FirecrackerRootDrive)(nil), "FirecrackerRootDrive") + proto.RegisterType((*FirecrackerDriveMount)(nil), "FirecrackerDriveMount") proto.RegisterType((*FirecrackerRateLimiter)(nil), "FirecrackerRateLimiter") proto.RegisterType((*FirecrackerTokenBucket)(nil), "FirecrackerTokenBucket") } @@ -694,55 +760,60 @@ func init() { func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) } var fileDescriptor_d938547f84707355 = []byte{ - // 789 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xdd, 0x8e, 0xe3, 0x34, - 0x14, 0x56, 0x26, 0xd3, 0xd9, 0xd6, 0x9d, 0x85, 0x62, 0xad, 0x20, 0x74, 0x11, 0x54, 0x11, 0x17, - 0x45, 0x5a, 0x65, 0xd0, 0x20, 0x90, 0xb8, 0x40, 0xab, 0xfe, 0x2c, 0x6c, 0x81, 0x4e, 0x2b, 0x77, - 0x58, 0x24, 0xee, 0x3c, 0xc9, 0x69, 0x6b, 0x35, 0xb5, 0x23, 0xc7, 0x99, 0x6e, 0xf7, 0x11, 0x10, - 0xef, 0xc0, 0x13, 0xf0, 0x28, 0x5c, 0xf3, 0x3a, 0xc8, 0x8e, 0x93, 0xba, 0xdd, 0xdd, 0xb9, 0x9a, - 0x39, 0xdf, 0xf7, 0x9d, 0x73, 0xec, 0xef, 0x1c, 0xa7, 0xa8, 0xad, 0xf6, 0x19, 0xe4, 0x51, 0x26, - 0x85, 0x12, 0xdd, 0x4f, 0x57, 0x42, 0xac, 0x52, 0xb8, 0x32, 0xd1, 0x5d, 0xb1, 0xbc, 0xa2, 0x7c, - 0x5f, 0x52, 0xe1, 0x7f, 0x1e, 0x6a, 0xbd, 0x78, 0xad, 0x24, 0x1d, 0x53, 0x45, 0x71, 0x17, 0x35, - 0x7f, 0xce, 0x05, 0x5f, 0x64, 0x10, 0x07, 0x5e, 0xcf, 0xeb, 0x5f, 0x92, 0x3a, 0xc6, 0xdf, 0xa1, - 0x36, 0x29, 0x78, 0x3c, 0xcb, 0x14, 0x13, 0x3c, 0x0f, 0xce, 0x7a, 0x5e, 0xbf, 0x7d, 0xfd, 0x24, - 0x2a, 0x4b, 0x47, 0x55, 0xe9, 0x68, 0xc0, 0xf7, 0xc4, 0x15, 0xe2, 0xcf, 0x50, 0x6b, 0xa1, 0x12, - 0xc6, 0xe7, 0x42, 0xaa, 0xc0, 0xef, 0x79, 0xfd, 0xc7, 0xe4, 0x00, 0xe0, 0xcf, 0x11, 0x5a, 0xa8, - 0x44, 0x14, 0xca, 0xd0, 0xe7, 0x86, 0x76, 0x10, 0xcb, 0x83, 0x94, 0x86, 0x6f, 0xd4, 0xbc, 0x45, - 0x70, 0x80, 0x1e, 0x8d, 0x25, 0xbb, 0x87, 0xc9, 0x38, 0xb8, 0xe8, 0x79, 0xfd, 0x16, 0xa9, 0xc2, - 0xf0, 0x9f, 0x33, 0xf4, 0xf4, 0x47, 0x26, 0x21, 0x96, 0x34, 0xde, 0x80, 0xbc, 0x01, 0xb5, 0x13, - 0x72, 0x33, 0xe1, 0x0a, 0xe4, 0x92, 0xc6, 0xa0, 0xcf, 0x35, 0x48, 0x53, 0xb1, 0x9b, 0x4e, 0xc7, - 0x0b, 0x73, 0xd9, 0x26, 0x39, 0x00, 0xf8, 0x07, 0xf4, 0x78, 0xc2, 0x09, 0x55, 0xf0, 0x2b, 0xdb, - 0x32, 0x05, 0xd2, 0xde, 0xf7, 0x93, 0xc8, 0x29, 0xe9, 0xd0, 0xe4, 0x58, 0x8d, 0x9f, 0xa3, 0x0f, - 0x66, 0x85, 0x72, 0xf3, 0xfd, 0x87, 0xf3, 0x4f, 0xe4, 0xf8, 0x0a, 0xb5, 0x46, 0x37, 0x93, 0x91, - 0xe0, 0x4b, 0xb6, 0x32, 0xb6, 0xb4, 0xaf, 0x3f, 0x8a, 0x6a, 0xa4, 0x90, 0x54, 0x9b, 0x4b, 0x0e, - 0x1a, 0xfc, 0x1c, 0x5d, 0x2e, 0x14, 0x55, 0x2c, 0xb6, 0x39, 0x0d, 0x93, 0xf3, 0x34, 0x2a, 0x41, - 0x7b, 0xfb, 0xe3, 0xec, 0xa3, 0x84, 0xf0, 0xcf, 0x33, 0xd4, 0x39, 0x6d, 0x80, 0x7b, 0xa8, 0x6d, - 0x53, 0x6f, 0xe8, 0x16, 0x8c, 0x4d, 0x2d, 0xe2, 0x42, 0xf8, 0x4b, 0x6d, 0x94, 0xf5, 0xd4, 0x68, - 0xce, 0x8c, 0xe6, 0x18, 0xd4, 0x63, 0x1a, 0x32, 0x3e, 0xa7, 0x6a, 0x1d, 0xf8, 0x3d, 0x5f, 0x8f, - 0xc9, 0x86, 0x9a, 0xd1, 0x2d, 0xc7, 0x4c, 0x9a, 0x6b, 0xb6, 0x48, 0x15, 0xea, 0x65, 0x1c, 0xd1, - 0x78, 0x0d, 0x9a, 0x6a, 0x18, 0xaa, 0x8e, 0xf1, 0x33, 0x74, 0x3e, 0x90, 0xab, 0x3c, 0xb8, 0xe8, - 0xf9, 0xfd, 0xf6, 0x75, 0xf0, 0x96, 0x33, 0x1a, 0x18, 0xc8, 0x15, 0x31, 0xaa, 0xee, 0xd7, 0xe8, - 0xa2, 0x8c, 0x71, 0x07, 0xf9, 0xbf, 0xc0, 0xde, 0xde, 0x43, 0xff, 0x8b, 0x9f, 0xa0, 0xc6, 0x2b, - 0x9a, 0x16, 0xd5, 0xb9, 0xcb, 0x20, 0xfc, 0xcb, 0x43, 0xdd, 0xf7, 0x3b, 0xa7, 0xb7, 0x72, 0x4a, - 0xe3, 0x41, 0x92, 0x48, 0xc8, 0x73, 0x5b, 0xcd, 0x41, 0xb4, 0x6d, 0x2f, 0x45, 0xae, 0xc6, 0x70, - 0xef, 0x58, 0xe2, 0x42, 0xf8, 0x19, 0x6a, 0x4e, 0xe6, 0x76, 0x54, 0xe5, 0x6a, 0x74, 0xa2, 0x0a, - 0xa8, 0xe6, 0x53, 0x2b, 0xc2, 0x1d, 0xfa, 0xf0, 0x84, 0xd4, 0x2d, 0xe6, 0x92, 0x6d, 0xa9, 0xdc, - 0xeb, 0xa6, 0xd5, 0x64, 0x1c, 0x48, 0x2b, 0x7e, 0xa2, 0x0a, 0x76, 0xb4, 0x54, 0xf8, 0xa5, 0xc2, - 0x81, 0xcc, 0x74, 0xe9, 0x16, 0x72, 0x90, 0xf7, 0x20, 0xf3, 0xe0, 0xdc, 0x4c, 0xc6, 0x85, 0xc2, - 0xbf, 0x3d, 0xf4, 0x85, 0xb3, 0xb1, 0x53, 0x1a, 0xaf, 0x19, 0x87, 0xb7, 0x4e, 0x32, 0x9a, 0xff, - 0x76, 0x0b, 0xdb, 0x2c, 0xa5, 0xaa, 0xde, 0x11, 0x07, 0xd2, 0x4f, 0xed, 0xa5, 0x7a, 0xc1, 0xe9, - 0x5d, 0x0a, 0x89, 0x31, 0xa3, 0x49, 0x0e, 0x80, 0x31, 0x13, 0xb6, 0x0b, 0xf6, 0x06, 0xa6, 0xec, - 0xce, 0x7e, 0x21, 0x1c, 0x44, 0x67, 0xbf, 0x8a, 0xb3, 0x62, 0x24, 0x0a, 0x5e, 0x7d, 0x21, 0x0e, - 0x40, 0xf8, 0xaf, 0x87, 0x3a, 0xce, 0x09, 0xcd, 0xeb, 0xd7, 0x25, 0x27, 0xf9, 0xef, 0x92, 0x29, - 0xdd, 0xc1, 0x3e, 0x6e, 0x07, 0xc1, 0x21, 0xba, 0x9c, 0xe4, 0x44, 0x08, 0x3d, 0x0e, 0x16, 0x83, - 0x3d, 0xd3, 0x11, 0xa6, 0xd7, 0x6f, 0x4e, 0xa5, 0x2a, 0x0a, 0x96, 0x58, 0xef, 0xea, 0x58, 0xd7, - 0xd7, 0xcb, 0x3b, 0xe3, 0x7a, 0xa4, 0x76, 0x6f, 0x1d, 0x04, 0x7f, 0x8f, 0xda, 0xee, 0xdb, 0x6f, - 0x3c, 0xfc, 0xf6, 0x5d, 0x6d, 0xf8, 0x06, 0x7d, 0xfc, 0x6e, 0x19, 0xfe, 0x16, 0xb5, 0x86, 0x94, - 0x27, 0x3b, 0x96, 0xa8, 0xb5, 0xb9, 0xd3, 0x49, 0xc9, 0x5b, 0xb1, 0x01, 0x3e, 0x2c, 0xe2, 0x0d, - 0x28, 0x72, 0x50, 0xe2, 0xaf, 0x90, 0x3f, 0xcb, 0xf2, 0x77, 0x7d, 0xbf, 0xdc, 0x04, 0xad, 0x09, - 0x5f, 0x1f, 0xf5, 0x76, 0x68, 0x6d, 0xd8, 0x8c, 0xc3, 0x2d, 0xdb, 0xc2, 0xb0, 0x90, 0xb9, 0x32, - 0xed, 0x7d, 0x72, 0x84, 0x69, 0x53, 0x08, 0x2c, 0x59, 0x9a, 0x6a, 0xc8, 0xf4, 0xf3, 0x89, 0x83, - 0x94, 0xef, 0x39, 0xa3, 0x31, 0x53, 0x7b, 0x63, 0xa8, 0x4f, 0xea, 0x78, 0xf8, 0xe8, 0x8f, 0x46, - 0xf9, 0x0b, 0x72, 0x61, 0xfe, 0x7c, 0xf3, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0xfe, 0x59, - 0xb8, 0xc0, 0x06, 0x00, 0x00, + // 868 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x95, 0xdd, 0x6e, 0x23, 0x35, + 0x14, 0xc7, 0x95, 0x4e, 0x93, 0xcc, 0x9c, 0x49, 0x4b, 0xb1, 0xca, 0x12, 0xba, 0x08, 0xa2, 0x11, + 0x42, 0x41, 0x5a, 0xa5, 0xa8, 0x08, 0x24, 0x2e, 0xd0, 0x2a, 0x1f, 0xbb, 0x6c, 0x80, 0xb4, 0x91, + 0x53, 0x8a, 0xc4, 0x9d, 0x33, 0x71, 0x12, 0x93, 0x8c, 0x3d, 0xb2, 0x3d, 0xc9, 0x66, 0x1f, 0x01, + 0xc1, 0x33, 0x70, 0xcd, 0x05, 0x8f, 0xc0, 0xbb, 0x21, 0x7b, 0x3c, 0xc9, 0xb4, 0xbb, 0xf4, 0x2a, + 0x3a, 0xbf, 0xff, 0x39, 0x73, 0xbe, 0x6c, 0x07, 0x42, 0xbd, 0x4b, 0xa9, 0xea, 0xa4, 0x52, 0x68, + 0x71, 0xf1, 0xd1, 0x42, 0x88, 0xc5, 0x9a, 0x5e, 0x5a, 0x6b, 0x9a, 0xcd, 0x2f, 0x09, 0xdf, 0xe5, + 0x52, 0xf4, 0x6f, 0x05, 0x82, 0x17, 0xaf, 0xb5, 0x24, 0x03, 0xa2, 0x09, 0xba, 0x00, 0xff, 0x07, + 0x25, 0xf8, 0x24, 0xa5, 0x71, 0xb3, 0xd2, 0xaa, 0xb4, 0x1b, 0xd8, 0xff, 0xcd, 0xd9, 0xe8, 0x1b, + 0x08, 0x71, 0xc6, 0xe3, 0x9b, 0x54, 0x33, 0xc1, 0x55, 0xf3, 0xa8, 0x55, 0x69, 0x87, 0x57, 0xe7, + 0x9d, 0xfc, 0xd3, 0x9d, 0xe2, 0xd3, 0x9d, 0x2e, 0xdf, 0xe1, 0x50, 0x1e, 0x1c, 0xd1, 0xc7, 0x10, + 0x4c, 0xf4, 0x8c, 0xf1, 0xb1, 0x90, 0xba, 0xe9, 0xb5, 0x2a, 0xed, 0x13, 0x1c, 0xa8, 0x02, 0xa0, + 0x4f, 0x00, 0x26, 0x7a, 0x26, 0x32, 0x6d, 0xe5, 0x63, 0x2b, 0x83, 0xda, 0x13, 0xa7, 0x53, 0x29, + 0xad, 0x5e, 0xdd, 0xeb, 0x8e, 0x44, 0xff, 0x1c, 0xc1, 0xd3, 0x97, 0x4c, 0xd2, 0x58, 0x92, 0x78, + 0x45, 0xe5, 0x35, 0xd5, 0x5b, 0x21, 0x57, 0x43, 0xae, 0xa9, 0x9c, 0x93, 0x98, 0x9a, 0xec, 0xdd, + 0xf5, 0x5a, 0x6c, 0x47, 0xa3, 0xc1, 0xc4, 0xb6, 0xe4, 0xe3, 0x80, 0x14, 0x00, 0x7d, 0x07, 0x27, + 0x43, 0x8e, 0x89, 0xa6, 0x3f, 0xb1, 0x84, 0x69, 0x2a, 0x5d, 0x57, 0x1f, 0x76, 0x4a, 0x9f, 0x2c, + 0xc9, 0xf8, 0x84, 0x95, 0xbd, 0xd1, 0x73, 0x38, 0xbd, 0xc9, 0x74, 0x39, 0xde, 0x7b, 0x3c, 0xfe, + 0x54, 0xdc, 0x73, 0x47, 0x97, 0x10, 0xf4, 0xaf, 0x87, 0x7d, 0xc1, 0xe7, 0x6c, 0x61, 0x9b, 0x0f, + 0xaf, 0xde, 0xef, 0xec, 0x49, 0x26, 0x89, 0x19, 0x21, 0x0e, 0xe2, 0x82, 0xa0, 0xe7, 0xd0, 0x98, + 0x68, 0xa2, 0x59, 0xec, 0x62, 0xaa, 0x36, 0xe6, 0x69, 0x27, 0x87, 0xae, 0xfb, 0xfb, 0xd1, 0x0d, + 0x55, 0x0a, 0x88, 0x7e, 0x3f, 0x82, 0xb3, 0x87, 0x09, 0x50, 0x0b, 0x42, 0x17, 0x7a, 0x4d, 0x12, + 0x6a, 0xc7, 0x14, 0xe0, 0x90, 0x1f, 0x10, 0xfa, 0xcc, 0x0c, 0xca, 0xcd, 0xd4, 0xfa, 0x1c, 0x59, + 0x9f, 0x13, 0x56, 0x86, 0xa8, 0x09, 0xf5, 0x1e, 0xe3, 0x63, 0xa2, 0x97, 0x4d, 0xaf, 0xe5, 0xb5, + 0x03, 0x5c, 0x9f, 0xe6, 0xa6, 0x51, 0x4c, 0xca, 0x01, 0x93, 0xb6, 0xcd, 0x00, 0xd7, 0xe3, 0xdc, + 0x34, 0x47, 0xae, 0x4f, 0xe2, 0x25, 0x35, 0x52, 0xd5, 0x4a, 0x7e, 0xec, 0x6c, 0xf4, 0x0c, 0x8e, + 0xbb, 0x72, 0xa1, 0x9a, 0xb5, 0x96, 0xd7, 0x0e, 0xaf, 0x9a, 0x6f, 0x4d, 0xc6, 0x80, 0xae, 0x5c, + 0xe0, 0x63, 0x22, 0x17, 0xea, 0xe2, 0x4b, 0xa8, 0xe5, 0x36, 0x3a, 0x03, 0xef, 0x47, 0xba, 0x73, + 0x7d, 0x78, 0x2b, 0xba, 0x43, 0xe7, 0x50, 0xbd, 0x23, 0xeb, 0xac, 0xa8, 0xbb, 0xba, 0x31, 0x46, + 0xf4, 0x47, 0x05, 0x2e, 0xfe, 0x7f, 0x72, 0xe6, 0xec, 0x8d, 0x48, 0xdc, 0x9d, 0xcd, 0x24, 0x55, + 0xca, 0x7d, 0x0d, 0x92, 0x3d, 0x31, 0x63, 0x7b, 0x25, 0x94, 0x1e, 0xd0, 0x4d, 0x69, 0x24, 0xe1, + 0xf2, 0x80, 0xd0, 0x33, 0xf0, 0x87, 0x63, 0xb7, 0xaa, 0xfc, 0x68, 0x9c, 0x75, 0x0a, 0x50, 0xec, + 0xc7, 0x67, 0x0e, 0x44, 0x5b, 0x78, 0xef, 0x81, 0x68, 0x52, 0x8c, 0x25, 0x4b, 0x88, 0xdc, 0x99, + 0xa4, 0xc5, 0x66, 0xd2, 0x03, 0x32, 0x1e, 0xdf, 0x13, 0x4d, 0xb7, 0x24, 0xf7, 0xf0, 0x72, 0x8f, + 0xc5, 0x01, 0xd9, 0xed, 0x92, 0x84, 0x2a, 0x2a, 0x37, 0x54, 0xaa, 0xe6, 0xb1, 0xdd, 0x4c, 0xc8, + 0x0f, 0x28, 0xfa, 0xab, 0x02, 0x9f, 0x96, 0x4e, 0xec, 0x88, 0xc4, 0x4b, 0xc6, 0xe9, 0x5b, 0x95, + 0xf4, 0xc7, 0x3f, 0xdf, 0xd2, 0x24, 0x5d, 0x13, 0xbd, 0x3f, 0x23, 0xf1, 0x01, 0x99, 0xab, 0xf6, + 0x4a, 0xbf, 0xe0, 0x64, 0xba, 0xa6, 0x33, 0x3b, 0x0c, 0x1f, 0x07, 0xcb, 0x02, 0xd8, 0x61, 0xd2, + 0x64, 0xc2, 0xde, 0xd0, 0x11, 0x9b, 0xba, 0x77, 0x00, 0x92, 0x3d, 0x31, 0xd1, 0x77, 0x71, 0x9a, + 0xf5, 0x45, 0xc6, 0x8b, 0x77, 0x20, 0xd8, 0x14, 0x20, 0xfa, 0xbb, 0x02, 0xe7, 0xe5, 0x3b, 0x25, + 0x84, 0x1e, 0x48, 0xb6, 0xa1, 0xe6, 0xf8, 0x98, 0x1d, 0xd8, 0x33, 0x97, 0xd7, 0xe4, 0x2f, 0x9d, + 0x6d, 0xb4, 0x31, 0x91, 0x3a, 0xcb, 0xd8, 0xcc, 0x2d, 0xc7, 0x4f, 0x9d, 0x6d, 0xca, 0x19, 0xaa, + 0x5f, 0x24, 0xd3, 0xa6, 0x3a, 0x5b, 0x8e, 0x8f, 0x81, 0xed, 0x09, 0xfa, 0x16, 0xc2, 0xf2, 0xbd, + 0x3e, 0x7e, 0xfc, 0x5e, 0x87, 0xf2, 0x60, 0x44, 0x7f, 0x56, 0xe0, 0x83, 0x92, 0x9f, 0xad, 0x73, + 0x64, 0xba, 0x78, 0xb4, 0xd8, 0x27, 0x50, 0xbb, 0x1b, 0x59, 0x25, 0x2f, 0xb5, 0xb6, 0xb1, 0x16, + 0xfa, 0x1c, 0x4e, 0x5f, 0xb2, 0x35, 0x55, 0x3b, 0xa5, 0x69, 0x72, 0xbb, 0x4b, 0xa9, 0x5b, 0xf1, + 0xe9, 0xfc, 0x1e, 0x35, 0x37, 0xac, 0x78, 0x9a, 0xf3, 0x0d, 0xd7, 0x45, 0x6e, 0x46, 0x6f, 0xe0, + 0xc9, 0xbb, 0xcb, 0x46, 0x5f, 0x43, 0xd0, 0x23, 0x7c, 0xb6, 0x65, 0x33, 0x57, 0xd0, 0x83, 0x16, + 0x6f, 0xc5, 0x8a, 0xf2, 0x5e, 0x16, 0xaf, 0xa8, 0xc6, 0xc1, 0xb4, 0xf0, 0x44, 0x5f, 0x80, 0x77, + 0x93, 0xaa, 0x77, 0xbd, 0x95, 0xe5, 0x00, 0x4f, 0xa4, 0x2a, 0x7a, 0x7d, 0x2f, 0x77, 0x49, 0x46, + 0x11, 0x34, 0x6e, 0x38, 0xbd, 0x65, 0x09, 0xed, 0x65, 0x52, 0x69, 0x9b, 0xde, 0xc3, 0x0d, 0x51, + 0x62, 0x66, 0x49, 0x98, 0xce, 0xd9, 0x7a, 0x6d, 0x90, 0xcd, 0xe7, 0x61, 0x90, 0x7b, 0x92, 0xbf, + 0x1d, 0x29, 0x89, 0x99, 0xde, 0xd9, 0xa9, 0x78, 0xe6, 0xed, 0xc8, 0xed, 0x5e, 0xfd, 0xd7, 0x6a, + 0xfe, 0x9f, 0x54, 0xb3, 0x3f, 0x5f, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x35, 0x4e, 0x86, 0xff, + 0x12, 0x07, 0x00, 0x00, } diff --git a/proto/types.proto b/proto/types.proto index ca600544a..dc5b1d3f2 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -11,7 +11,6 @@ message ExtraData { uint32 StdinPort = 3; uint32 StdoutPort = 4; uint32 StderrPort = 5; - string DriveID = 6; } // Message to specify network config for a Firecracker VM @@ -107,12 +106,45 @@ message FirecrackerMachineConfiguration { } // Message to specify the block device config for a Firecracker VM -message FirecrackerDrive { - bool IsWritable = 1; // Specifies if the drive is writable from a guest - bool IsRootDevice = 2; // Specifies if the drive is the root device - string Partuuid = 3; // Specifies the unique id of the boot partition on this device - string PathOnHost = 4; // Specifies the host level path for the guest drive - FirecrackerRateLimiter RateLimiter = 5; // Specifies a rate limiter for block IO +message FirecrackerRootDrive { + // (Required) HostPath is the path on the host to the filesystem image or device + // that will supply the rootfs of the VM. + string HostPath = 1; + + // (Optional) If the HostPath points to a drive or image with multiple + // partitions, Partuuid specifies which partition will be used to boot + // the VM + string Partuuid = 2; + + // (Optional) If set to true, IsWritable results in the VM Guest's rootfs + // being mounted as read-write. Defaults to false, in which case the + // rootfs is mounted as read-only. + bool IsWritable = 3; + + // (Optional) RateLimiter configuration that will be applied to the + // backing-drive for the VM's rootfs + FirecrackerRateLimiter RateLimiter = 4; +} + +message FirecrackerDriveMount { + // (Required) HostPath is the path on the host to the filesystem image or device + // that will be mounted inside the VM. + string HostPath = 1; + + // (Required) VMPath is the path inside the VM guest at which the filesystem + // image or device will be mounted. + string VMPath = 2; + + // (Required) FilesystemType is the filesystem type (i.e. ext4, xfs, etc.), as + // used when mounting the filesystem image inside the VM. The VM guest kernel + // is expected to have support for this filesystem. + string FilesystemType = 3; + + // (Optional) Options are fstab-style options that the mount will be performed + // within the VM (i.e. ["rw", "noatime"]). Defaults to none if not specified. + repeated string Options = 4; + + // TODO support for ratelimiters and read-only drives (issue #296) } // Message to specify an IO rate limiter with bytes/s and ops/s limits diff --git a/runtime/cni_integ_test.go b/runtime/cni_integ_test.go index 67f053f3a..e7e62cb60 100644 --- a/runtime/cni_integ_test.go +++ b/runtime/cni_integ_test.go @@ -92,10 +92,6 @@ func TestCNISupport_Isolated(t *testing.T) { MachineCfg: &proto.FirecrackerMachineConfiguration{ MemSizeMib: 512, }, - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: defaultVMRootfsPath, - IsRootDevice: true, - }, NetworkInterfaces: []*proto.FirecrackerNetworkInterface{{ CNIConfig: &proto.CNIConfiguration{ NetworkName: cniNetworkName, @@ -265,10 +261,6 @@ func TestCNIPlugin_Performance(t *testing.T) { MachineCfg: &proto.FirecrackerMachineConfiguration{ MemSizeMib: uint32(vmMemSizeMB), }, - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: defaultVMRootfsPath, - IsRootDevice: true, - }, NetworkInterfaces: []*proto.FirecrackerNetworkInterface{{ CNIConfig: &proto.CNIConfiguration{ NetworkName: cniNetworkName, diff --git a/runtime/drive_handler.go b/runtime/drive_handler.go index b82bb0d48..f5f958e47 100644 --- a/runtime/drive_handler.go +++ b/runtime/drive_handler.go @@ -178,13 +178,13 @@ func (h *stubDriveHandler) InDriveSet(path string) bool { } // PatchStubDrive will replace the next available stub drive with the provided drive -func (h *stubDriveHandler) PatchStubDrive(ctx context.Context, client firecracker.MachineIface, pathOnHost string) (*string, error) { +func (h *stubDriveHandler) PatchStubDrive(ctx context.Context, client firecracker.MachineIface, pathOnHost string) (string, error) { h.mutex.Lock() defer h.mutex.Unlock() // Check to see if stubDriveIndex has increased more than the drive amount. if h.stubDriveIndex >= int64(len(h.drives)) { - return nil, ErrDrivesExhausted + return "", ErrDrivesExhausted } d := h.drives[h.stubDriveIndex] @@ -193,16 +193,16 @@ func (h *stubDriveHandler) PatchStubDrive(ctx context.Context, client firecracke if d.DriveID == nil { // this should never happen, but we want to ensure that we never nil // dereference - return nil, ErrDriveIDNil + return "", ErrDriveIDNil } h.drives[h.stubDriveIndex] = d err := client.UpdateGuestDrive(ctx, firecracker.StringValue(d.DriveID), pathOnHost) if err != nil { - return nil, err + return "", err } h.stubDriveIndex++ - return d.DriveID, nil + return firecracker.StringValue(d.DriveID), nil } diff --git a/runtime/drive_handler_test.go b/runtime/drive_handler_test.go index 835b93242..a8d2f851a 100644 --- a/runtime/drive_handler_test.go +++ b/runtime/drive_handler_test.go @@ -97,7 +97,7 @@ func TestPatchStubDrive(t *testing.T) { for i, path := range expectedReplacements { driveID, err := handler.PatchStubDrive(ctx, client, path) assert.NoError(t, err, "failed to patch stub drive") - assert.Equal(t, expectedDriveIDs[i], firecracker.StringValue(driveID), "drive ids are not equal") + assert.Equal(t, expectedDriveIDs[i], driveID, "drive ids are not equal") } } diff --git a/runtime/helpers.go b/runtime/helpers.go index d09cdb36a..99be1b6d9 100644 --- a/runtime/helpers.go +++ b/runtime/helpers.go @@ -112,19 +112,6 @@ func networkConfigFromProto(nwIface *proto.FirecrackerNetworkInterface, vmID str return result, nil } -func addDriveFromProto(builder firecracker.DrivesBuilder, drive *proto.FirecrackerDrive) firecracker.DrivesBuilder { - opt := func(d *models.Drive) { - d.IsRootDevice = firecracker.Bool(drive.IsRootDevice) - d.Partuuid = drive.Partuuid - - if limiter := drive.RateLimiter; limiter != nil { - d.RateLimiter = rateLimiterFromProto(limiter) - } - } - - return builder.AddDrive(drive.PathOnHost, !drive.IsWritable, opt) -} - // rateLimiterFromProto creates a firecracker RateLimiter object from the // protobuf message. func rateLimiterFromProto(rl *proto.FirecrackerRateLimiter) *models.RateLimiter { diff --git a/runtime/helpers_test.go b/runtime/helpers_test.go index 11d31536e..32dc8907b 100644 --- a/runtime/helpers_test.go +++ b/runtime/helpers_test.go @@ -207,15 +207,3 @@ func TestTokenBucketFromProto(t *testing.T) { assert.EqualValues(t, refillTime, *bucket.RefillTime) assert.EqualValues(t, capacity, *bucket.Size) } - -func TestAddDriveFromProto(t *testing.T) { - list := addDriveFromProto(firecracker.DrivesBuilder{}, &proto.FirecrackerDrive{ - IsWritable: false, - PathOnHost: "/a", - Partuuid: "xy", - }).Build() - - assert.Equal(t, "/a", *list[0].PathOnHost) - assert.Equal(t, "xy", list[0].Partuuid) - assert.True(t, *list[0].IsReadOnly) -} diff --git a/runtime/service.go b/runtime/service.go index 47beb75a9..1a7014ba6 100644 --- a/runtime/service.go +++ b/runtime/service.go @@ -57,6 +57,7 @@ import ( fcShim "github.com/firecracker-microvm/firecracker-containerd/internal/shim" "github.com/firecracker-microvm/firecracker-containerd/internal/vm" "github.com/firecracker-microvm/firecracker-containerd/proto" + drivemount "github.com/firecracker-microvm/firecracker-containerd/proto/service/drivemount/ttrpc" fccontrolTtrpc "github.com/firecracker-microvm/firecracker-containerd/proto/service/fccontrol/ttrpc" ) @@ -120,6 +121,7 @@ type service struct { vmStartOnce sync.Once agentClient taskAPI.TaskService eventBridgeClient eventbridge.Getter + driveMountClient drivemount.DriveMounterService stubDriveHandler *stubDriveHandler exitAfterAllTasksDeleted bool // exit the VM and shim when all tasks are deleted @@ -357,7 +359,7 @@ func logPanicAndDie(logger *logrus.Entry) { } } -func (s *service) generateExtraData(jsonBytes []byte, driveID *string, options *ptypes.Any) (*proto.ExtraData, error) { +func (s *service) generateExtraData(jsonBytes []byte, options *ptypes.Any) (*proto.ExtraData, error) { var opts *ptypes.Any if options != nil { // Copy values of existing options over @@ -375,7 +377,6 @@ func (s *service) generateExtraData(jsonBytes []byte, driveID *string, options * StdinPort: s.nextVSockPort(), StdoutPort: s.nextVSockPort(), StderrPort: s.nextVSockPort(), - DriveID: firecracker.StringValue(driveID), }, nil } @@ -509,10 +510,35 @@ func (s *service) createVM(requestCtx context.Context, request *proto.CreateVMRe rpcClient := ttrpc.NewClient(conn, ttrpc.WithOnClose(func() { _ = conn.Close() })) s.agentClient = taskAPI.NewTaskClient(rpcClient) s.eventBridgeClient = eventbridge.NewGetterClient(rpcClient) + s.driveMountClient = drivemount.NewDriveMounterClient(rpcClient) s.exitAfterAllTasksDeleted = request.ExitAfterAllTasksDeleted + err = s.mountDrives(requestCtx, request.DriveMounts) + if err != nil { + return err + } + s.logger.Info("successfully started the VM") + return nil +} +func (s *service) mountDrives(requestCtx context.Context, driveMounts []*proto.FirecrackerDriveMount) error { + for _, driveMount := range driveMounts { + driveID, err := s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, driveMount.HostPath) + if err != nil { + return errors.Wrapf(err, "failed to patch drive mount stub") + } + + _, err = s.driveMountClient.MountDrive(requestCtx, &drivemount.MountDriveRequest{ + DriveID: driveID, + DestinationPath: driveMount.VMPath, + FilesytemType: driveMount.FilesystemType, + Options: driveMount.Options, + }) + if err != nil { + return errors.Wrapf(err, "failed to mount drive inside vm") + } + } return nil } @@ -599,6 +625,14 @@ func (s *service) SetVMMetadata(requestCtx context.Context, request *proto.SetVM } func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker.Config, error) { + for _, driveMount := range req.DriveMounts { + // Verify the request specified an absolute path for the source/dest of drives. + // Otherwise, users can implicitly rely on the CWD of this shim or agent. + if !strings.HasPrefix(driveMount.HostPath, "/") || !strings.HasPrefix(driveMount.VMPath, "/") { + return nil, errors.Errorf("driveMount %s contains relative path", driveMount.String()) + } + } + relSockPath, err := s.shimDir.FirecrackerSockRelPath() if err != nil { return nil, errors.Wrapf(err, "failed to get relative path to firecracker api socket") @@ -651,7 +685,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker // Create stub drives first and let stub driver handler manage the drives stubDriveHandler, err := newStubDriveHandler( string(s.jailer.JailPath()), - s.logger, containerCount, + s.logger, containerCount+len(req.DriveMounts), s.jailer.StubDrivesOptions()..., ) if err != nil { @@ -659,7 +693,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker } s.stubDriveHandler = stubDriveHandler - cfg.Drives = append(stubDriveHandler.GetDrives(), s.buildNonStubDrives(req)...) + cfg.Drives = append(stubDriveHandler.GetDrives(), s.buildRootDrive(req)...) // If no value for NetworkInterfaces was specified (not even an empty but non-nil list) and // the runtime config specifies a default list, use those defaults @@ -682,11 +716,11 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker return &cfg, nil } -func (s *service) buildNonStubDrives(req *proto.CreateVMRequest) []models.Drive { +func (s *service) buildRootDrive(req *proto.CreateVMRequest) []models.Drive { var builder firecracker.DrivesBuilder if input := req.RootDrive; input != nil { - builder = builder.WithRootDrive(input.PathOnHost, + builder = builder.WithRootDrive(input.HostPath, firecracker.WithReadOnly(!input.IsWritable), firecracker.WithPartuuid(input.Partuuid), withRateLimiterFromProto(input.RateLimiter)) @@ -694,10 +728,6 @@ func (s *service) buildNonStubDrives(req *proto.CreateVMRequest) []models.Drive builder = builder.WithRootDrive(s.config.RootDrive, firecracker.WithReadOnly(true)) } - for _, drive := range req.AdditionalDrives { - builder = addDriveFromProto(builder, drive) - } - return builder.Build() } @@ -720,8 +750,10 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask "checkpoint": request.Checkpoint, }).Debug("creating task") - bundleDir := bundle.Dir(request.Bundle) - err = s.shimDir.CreateBundleLink(request.ID, bundleDir) + hostBundleDir := bundle.Dir(request.Bundle) + vmBundleDir := bundle.VMBundleDir(request.ID) + + err = s.shimDir.CreateBundleLink(request.ID, hostBundleDir) if err != nil { err = errors.Wrap(err, "failed to create VM dir bundle link") logger.WithError(err).Error() @@ -739,25 +771,32 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask if err := s.jailer.ExposeDeviceToJail(mnt.Source); err != nil { return nil, errors.Wrapf(err, "failed to expose mount to jail %v", mnt.Source) } - } - var driveID *string - for _, mnt := range request.Rootfs { - driveID, err = s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, mnt.Source) + driveID, err := s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, mnt.Source) if err != nil { if err == ErrDrivesExhausted { return nil, errors.Wrapf(errdefs.ErrUnavailable, "no remaining stub drives to be used") } return nil, errors.Wrapf(err, "failed to patch stub drive") } + + _, err = s.driveMountClient.MountDrive(requestCtx, &drivemount.MountDriveRequest{ + DriveID: driveID, + DestinationPath: vmBundleDir.RootfsPath(), + FilesytemType: "ext4", + Options: nil, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to mount drive inside vm") + } } - ociConfigBytes, err := bundleDir.OCIConfig().Bytes() + ociConfigBytes, err := hostBundleDir.OCIConfig().Bytes() if err != nil { return nil, err } - extraData, err := s.generateExtraData(ociConfigBytes, driveID, request.Options) + extraData, err := s.generateExtraData(ociConfigBytes, request.Options) if err != nil { err = errors.Wrap(err, "failed to generate extra data") logger.WithError(err).Error() @@ -808,6 +847,15 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask ioConnectorSet = vm.NewIOConnectorProxy(stdinConnectorPair, stdoutConnectorPair, stderrConnectorPair) } + // override the request with the bundle dir that should be used inside the VM + request.Bundle = vmBundleDir.RootPath() + + // The rootfs is mounted via a MountDrive call, so unset Rootfs in the request. + // We unfortunately can't rely on just having the runc shim inside the VM do + // the mount for us because we sometimes need to do mount retries due to our + // requirement of patching stub drives + request.Rootfs = nil + resp, err := s.taskManager.CreateTask(requestCtx, request, s.agentClient, ioConnectorSet) if err != nil { err = errors.Wrap(err, "failed to create task") @@ -871,8 +919,8 @@ func (s *service) Exec(requestCtx context.Context, req *taskAPI.ExecProcessReque logger := s.logger.WithField("task_id", req.ID).WithField("exec_id", req.ExecID) logger.Debug("exec") - // no OCI config bytes or DriveID to provide for Exec, just leave those fields empty - extraData, err := s.generateExtraData(nil, nil, req.Spec) + // no OCI config bytes to provide for Exec, just leave those fields empty + extraData, err := s.generateExtraData(nil, req.Spec) if err != nil { err = errors.Wrap(err, "failed to generate extra data") logger.WithError(err).Error() diff --git a/runtime/service_integ_test.go b/runtime/service_integ_test.go index a5e0e1763..63af1dd17 100644 --- a/runtime/service_integ_test.go +++ b/runtime/service_integ_test.go @@ -44,6 +44,7 @@ import ( "github.com/stretchr/testify/require" _ "github.com/firecracker-microvm/firecracker-containerd/firecracker-control" + fcClient "github.com/firecracker-microvm/firecracker-containerd/firecracker-control/client" "github.com/firecracker-microvm/firecracker-containerd/internal" "github.com/firecracker-microvm/firecracker-containerd/internal/vm" "github.com/firecracker-microvm/firecracker-containerd/proto" @@ -282,9 +283,8 @@ func TestMultipleVMs_Isolated(t *testing.T) { MachineCfg: &proto.FirecrackerMachineConfiguration{ MemSizeMib: 512, }, - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: rootfsPath, - IsRootDevice: true, + RootDrive: &proto.FirecrackerRootDrive{ + HostPath: rootfsPath, }, NetworkInterfaces: []*proto.FirecrackerNetworkInterface{ { @@ -525,11 +525,7 @@ func TestLongUnixSocketPath_Isolated(t *testing.T) { fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client()) _, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{ - VMID: vmID, - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: defaultVMRootfsPath, - IsRootDevice: true, - }, + VMID: vmID, NetworkInterfaces: []*proto.FirecrackerNetworkInterface{}, }) require.NoError(t, err, "failed to create VM") @@ -566,8 +562,6 @@ func TestStubBlockDevices_Isolated(t *testing.T) { image, err := alpineImage(ctx, client, defaultSnapshotterName()) require.NoError(t, err, "failed to get alpine image") - rootfsPath := defaultVMRootfsPath - tapName := fmt.Sprintf("tap%d", vmID) err = createTapDevice(ctx, tapName) require.NoError(t, err, "failed to create tap device for vm %d", vmID) @@ -581,10 +575,6 @@ func TestStubBlockDevices_Isolated(t *testing.T) { fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client()) _, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{ VMID: strconv.Itoa(vmID), - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: rootfsPath, - IsRootDevice: true, - }, NetworkInterfaces: []*proto.FirecrackerNetworkInterface{ { AllowMMDS: true, @@ -716,11 +706,7 @@ func testCreateContainerWithSameName(t *testing.T, vmID string) { if len(vmID) != 0 { fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client()) _, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{ - VMID: vmID, - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: defaultRootfsPath, - IsRootDevice: true, - }, + VMID: vmID, ContainerCount: 2, }) require.NoError(t, err) @@ -845,3 +831,172 @@ func TestCreateTooManyContainers_Isolated(t *testing.T) { assert.Equal("no remaining stub drives to be used: unavailable: unknown", err.Error()) require.Error(t, err) } + +func TestDriveMount_Isolated(t *testing.T) { + prepareIntegTest(t) + + testTimeout := 120 * time.Second + ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), defaultNamespace), testTimeout) + defer cancel() + + ctrdClient, err := containerd.New(containerdSockPath, containerd.WithDefaultRuntime(firecrackerRuntime)) + require.NoError(t, err, "unable to create client to containerd service at %s, is containerd running?", containerdSockPath) + + fcClient, err := fcClient.New(containerdSockPath + ".ttrpc") + require.NoError(t, err, "failed to create fccontrol client") + + image, err := alpineImage(ctx, ctrdClient, naiveSnapshotterName) + require.NoError(t, err, "failed to get alpine image") + + vmID := "test-drive-mount" + + vmMounts := []struct { + VMPath string + FilesystemType string + VMMountOptions []string + ContainerPath string + FSImgFile internal.FSImgFile + }{ + { + // /systemmount meant to make sure logic doesn't ban this just because it begins with /sys + VMPath: "/systemmount", + FilesystemType: "ext4", + VMMountOptions: []string{"rw", "noatime"}, + ContainerPath: "/foo", + FSImgFile: internal.FSImgFile{ + Subpath: "dir/foo", + Contents: "foo\n", + }, + }, + { + VMPath: "/mnt", + FilesystemType: "ext3", + VMMountOptions: []string{"ro", "relatime"}, + ContainerPath: "/bar", + FSImgFile: internal.FSImgFile{ + Subpath: "dir/bar", + Contents: "bar\n", + }, + }, + } + + vmDriveMounts := []*proto.FirecrackerDriveMount{} + ctrBindMounts := []specs.Mount{} + ctrCatCommands := []string{} + for _, vmMount := range vmMounts { + vmDriveMounts = append(vmDriveMounts, &proto.FirecrackerDriveMount{ + HostPath: internal.CreateFSImg(ctx, t, vmMount.FilesystemType, vmMount.FSImgFile), + VMPath: vmMount.VMPath, + FilesystemType: vmMount.FilesystemType, + Options: vmMount.VMMountOptions, + }) + + ctrBindMounts = append(ctrBindMounts, specs.Mount{ + Source: vmMount.VMPath, + Destination: vmMount.ContainerPath, + Options: []string{"bind"}, + }) + + ctrCatCommands = append(ctrCatCommands, fmt.Sprintf("/bin/cat %s", + filepath.Join(vmMount.ContainerPath, vmMount.FSImgFile.Subpath), + )) + } + + _, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{ + VMID: vmID, + DriveMounts: vmDriveMounts, + }) + require.NoError(t, err, "failed to create vm") + + containerName := fmt.Sprintf("%s-container", vmID) + snapshotName := fmt.Sprintf("%s-snapshot", vmID) + + newContainer, err := ctrdClient.NewContainer(ctx, + containerName, + containerd.WithSnapshotter(naiveSnapshotterName), + containerd.WithNewSnapshot(snapshotName, image), + containerd.WithNewSpec( + oci.WithProcessArgs("/bin/sh", "-c", strings.Join(append(ctrCatCommands, + "/bin/cat /proc/mounts", + ), " && ")), + oci.WithMounts(ctrBindMounts), + firecrackeroci.WithVMID(vmID), + ), + ) + require.NoError(t, err, "failed to create container %s", containerName) + + outputLines := strings.Split(startAndWaitTask(ctx, t, newContainer), "\n") + if len(outputLines) < len(vmMounts) { + require.Fail(t, "unexpected ctr output, expected at least %d lines: %+v", len(vmMounts), outputLines) + } + + mountInfos, err := internal.ParseProcMountLines(outputLines[len(vmMounts):]...) + require.NoError(t, err, "failed to parse /proc/mount") + // this is n^2, but it's doubtful the number of mounts will reach a point where that matters... + for _, vmMount := range vmMounts { + // Make sure that this vmMount's test file was cat'd by a container previously and output the expected + // file contents. This ensure the filesystem was successfully mounted in the VM and the container. + assert.Containsf(t, outputLines[:len(vmMounts)], strings.TrimSpace(vmMount.FSImgFile.Contents), + "did not find expected test file output for vm mount at %q", vmMount.ContainerPath) + + // iterate over /proc/mounts entries, find this vmMount's entry in there and verify it was mounted + // with the correct options. + var foundExpectedMount bool + for _, actualMountInfo := range mountInfos { + if actualMountInfo.DestPath == vmMount.ContainerPath { + foundExpectedMount = true + assert.Equalf(t, vmMount.FilesystemType, actualMountInfo.Type, + "vm mount at %q did have expected filesystem type", vmMount.ContainerPath) + for _, vmMountOption := range vmMount.VMMountOptions { + assert.Containsf(t, actualMountInfo.Options, vmMountOption, + "vm mount at %q did not have expected option", vmMount.ContainerPath) + } + break + } + } + assert.Truef(t, foundExpectedMount, "did not find expected mount at container path %q", vmMount.ContainerPath) + } +} + +func TestDriveMountFails_Isolated(t *testing.T) { + prepareIntegTest(t) + + testTimeout := 120 * time.Second + ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), defaultNamespace), testTimeout) + defer cancel() + + fcClient, err := fcClient.New(containerdSockPath + ".ttrpc") + require.NoError(t, err, "failed to create fccontrol client") + + testImgHostPath := internal.CreateFSImg(ctx, t, "ext4", internal.FSImgFile{ + Subpath: "idc", + Contents: "doesn't matter", + }) + + for _, driveMount := range []*proto.FirecrackerDriveMount{ + { + HostPath: testImgHostPath, + VMPath: "/proc/foo", // invalid due to being under /proc + FilesystemType: "ext4", + }, + { + HostPath: testImgHostPath, + VMPath: "/dev/foo", // invalid due to being under /dev + FilesystemType: "ext4", + }, + { + HostPath: testImgHostPath, + VMPath: "/sys/foo", // invalid due to being under /sys + FilesystemType: "ext4", + }, + } { + _, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{ + VMID: "test-drive-mount-fails", + DriveMounts: []*proto.FirecrackerDriveMount{driveMount}, + }) + + // TODO it would be good to check for more specific error types, see #294 for possible improvements: + // https://github.com/firecracker-microvm/firecracker-containerd/issues/294 + assert.Error(t, err, "unexpectedly succeeded in creating a VM with a drive mount under banned path") + } +} diff --git a/runtime/service_test.go b/runtime/service_test.go index ebebbe332..30fa29ae1 100644 --- a/runtime/service_test.go +++ b/runtime/service_test.go @@ -79,8 +79,8 @@ func TestBuildVMConfiguration(t *testing.T) { request: &proto.CreateVMRequest{ KernelArgs: "REQUEST KERNEL ARGS", KernelImagePath: "REQUEST KERNEL IMAGE", - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: "REQUEST ROOT DRIVE", + RootDrive: &proto.FirecrackerRootDrive{ + HostPath: "REQUEST ROOT DRIVE", IsWritable: true, }, MachineCfg: &proto.FirecrackerMachineConfiguration{ @@ -114,8 +114,8 @@ func TestBuildVMConfiguration(t *testing.T) { request: &proto.CreateVMRequest{ KernelArgs: "REQUEST KERNEL ARGS", KernelImagePath: "REQUEST KERNEL IMAGE", - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: "REQUEST ROOT DRIVE", + RootDrive: &proto.FirecrackerRootDrive{ + HostPath: "REQUEST ROOT DRIVE", IsWritable: true, }, MachineCfg: &proto.FirecrackerMachineConfiguration{ @@ -154,8 +154,8 @@ func TestBuildVMConfiguration(t *testing.T) { request: &proto.CreateVMRequest{ KernelArgs: "REQUEST KERNEL ARGS", KernelImagePath: "REQUEST KERNEL IMAGE", - RootDrive: &proto.FirecrackerDrive{ - PathOnHost: "REQUEST ROOT DRIVE", + RootDrive: &proto.FirecrackerRootDrive{ + HostPath: "REQUEST ROOT DRIVE", }, MachineCfg: &proto.FirecrackerMachineConfiguration{}, }, From 9f0eb50bdc6419e41e6d72b80a6ae279cce7d3d3 Mon Sep 17 00:00:00 2001 From: Erik Sipsma Date: Fri, 25 Oct 2019 21:32:04 +0000 Subject: [PATCH 2/2] Integrate drive mounts with jailer. The primary change required is for the Jailer to support exposing regular files to the jail in addition to the pre-existing support for block devices. Signed-off-by: Erik Sipsma --- runtime/jailer.go | 7 ++++--- runtime/noop_jailer.go | 4 ++-- runtime/runc_jailer.go | 38 +++++++++++++++++++++++++---------- runtime/service.go | 7 ++++++- runtime/service_integ_test.go | 13 +++++++----- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/runtime/jailer.go b/runtime/jailer.go index bfa093294..33bea1e83 100644 --- a/runtime/jailer.go +++ b/runtime/jailer.go @@ -47,9 +47,10 @@ type jailer interface { // firecracker.Opt to be passed into firecracker.NewMachine which will allow // for the VM to be jailed. BuildJailedMachine(cfg *Config, machineCfg *firecracker.Config, vmID string) ([]firecracker.Opt, error) - // ExposeDeviceToJail will expose the given device provided by the snapshotter - // to the jailed filesystem - ExposeDeviceToJail(path string) error + // ExposeFileToJail will expose the given file to the jailed filesystem, including + // regular files and block devices. An error is returned if provided a path to a file + // with type that is not supported. + ExposeFileToJail(path string) error // JailPath is used to return the directory we are supposed to be working in. JailPath() vm.Dir // StubDrivesOptions will return a set of options used to create a new stub diff --git a/runtime/noop_jailer.go b/runtime/noop_jailer.go index 420bd8957..ae15872e6 100644 --- a/runtime/noop_jailer.go +++ b/runtime/noop_jailer.go @@ -63,8 +63,8 @@ func (j noopJailer) JailPath() vm.Dir { return j.shimDir } -func (j noopJailer) ExposeDeviceToJail(path string) error { - j.logger.Debug("noop operation for ExposeDeviceToJail") +func (j noopJailer) ExposeFileToJail(path string) error { + j.logger.Debug("noop operation for ExposeFileToJail") return nil } diff --git a/runtime/runc_jailer.go b/runtime/runc_jailer.go index 096d9a7e3..79fe25cb3 100644 --- a/runtime/runc_jailer.go +++ b/runtime/runc_jailer.go @@ -253,33 +253,49 @@ func (j runcJailer) StubDrivesOptions() []stubDrivesOpt { } } -// ExposeDeviceToJail will inspect the given file, srcDevicePath, and based on the +// ExposeFileToJail will inspect the given file, srcPath, and based on the // file type, proper handling will occur to ensure that the file is visible in // the jail. For block devices we will use mknod to create the device and then -// set the correct permissions to ensure visibility in the jail. -func (j runcJailer) ExposeDeviceToJail(srcDevicePath string) error { +// set the correct permissions to ensure visibility in the jail. Regular files +// will be copied into the jail. +func (j runcJailer) ExposeFileToJail(srcPath string) error { uid := j.uid gid := j.gid stat := syscall.Stat_t{} - if err := syscall.Stat(srcDevicePath, &stat); err != nil { + if err := syscall.Stat(srcPath, &stat); err != nil { return err } // Checks file type using S_IFMT which is the bit mask for the file type. - // Here we only care about block devices, ie S_IFBLK. If it is a block type - // we will manually call mknod and create that device. - if (stat.Mode & syscall.S_IFMT) == syscall.S_IFBLK { - path := filepath.Join(j.RootPath(), filepath.Dir(srcDevicePath)) - if err := mkdirAllWithPermissions(path, 0700, uid, gid); err != nil { + switch stat.Mode & syscall.S_IFMT { + case syscall.S_IFBLK: + parentDir := filepath.Join(j.RootPath(), filepath.Dir(srcPath)) + if err := mkdirAllWithPermissions(parentDir, 0700, uid, gid); err != nil { return err } - dst := filepath.Join(path, filepath.Base(srcDevicePath)) + dst := filepath.Join(parentDir, filepath.Base(srcPath)) if err := exposeBlockDeviceToJail(dst, int(stat.Rdev), int(uid), int(gid)); err != nil { return err } - } else { + + case syscall.S_IFREG: + parentDir := filepath.Join(j.RootPath(), filepath.Dir(srcPath)) + if err := mkdirAllWithPermissions(parentDir, 0700, uid, gid); err != nil { + return err + } + + dst := filepath.Join(parentDir, filepath.Base(srcPath)) + if err := copyFile(srcPath, dst, os.FileMode(stat.Mode)); err != nil { + return err + } + + if err := os.Chown(dst, int(uid), int(gid)); err != nil { + return err + } + + default: return fmt.Errorf("unsupported mode: %v", stat.Mode) } diff --git a/runtime/service.go b/runtime/service.go index 1a7014ba6..d7073c32e 100644 --- a/runtime/service.go +++ b/runtime/service.go @@ -524,6 +524,11 @@ func (s *service) createVM(requestCtx context.Context, request *proto.CreateVMRe func (s *service) mountDrives(requestCtx context.Context, driveMounts []*proto.FirecrackerDriveMount) error { for _, driveMount := range driveMounts { + err := s.jailer.ExposeFileToJail(driveMount.HostPath) + if err != nil { + return errors.Wrapf(err, "failed to expose drive mount host path to jail %s", driveMount.HostPath) + } + driveID, err := s.stubDriveHandler.PatchStubDrive(requestCtx, s.machine, driveMount.HostPath) if err != nil { return errors.Wrapf(err, "failed to patch drive mount stub") @@ -768,7 +773,7 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask } for _, mnt := range request.Rootfs { - if err := s.jailer.ExposeDeviceToJail(mnt.Source); err != nil { + if err := s.jailer.ExposeFileToJail(mnt.Source); err != nil { return nil, errors.Wrapf(err, "failed to expose mount to jail %v", mnt.Source) } diff --git a/runtime/service_integ_test.go b/runtime/service_integ_test.go index 63af1dd17..bde965ffa 100644 --- a/runtime/service_integ_test.go +++ b/runtime/service_integ_test.go @@ -833,7 +833,9 @@ func TestCreateTooManyContainers_Isolated(t *testing.T) { } func TestDriveMount_Isolated(t *testing.T) { - prepareIntegTest(t) + prepareIntegTest(t, func(cfg *Config) { + cfg.JailerConfig.RuncBinaryPath = "/usr/local/bin/runc" + }) testTimeout := 120 * time.Second ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), defaultNamespace), testTimeout) @@ -845,7 +847,7 @@ func TestDriveMount_Isolated(t *testing.T) { fcClient, err := fcClient.New(containerdSockPath + ".ttrpc") require.NoError(t, err, "failed to create fccontrol client") - image, err := alpineImage(ctx, ctrdClient, naiveSnapshotterName) + image, err := alpineImage(ctx, ctrdClient, defaultSnapshotterName()) require.NoError(t, err, "failed to get alpine image") vmID := "test-drive-mount" @@ -903,8 +905,9 @@ func TestDriveMount_Isolated(t *testing.T) { } _, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{ - VMID: vmID, - DriveMounts: vmDriveMounts, + VMID: vmID, + DriveMounts: vmDriveMounts, + JailerConfig: &proto.JailerConfig{}, }) require.NoError(t, err, "failed to create vm") @@ -913,7 +916,7 @@ func TestDriveMount_Isolated(t *testing.T) { newContainer, err := ctrdClient.NewContainer(ctx, containerName, - containerd.WithSnapshotter(naiveSnapshotterName), + containerd.WithSnapshotter(defaultSnapshotterName()), containerd.WithNewSnapshot(snapshotName, image), containerd.WithNewSpec( oci.WithProcessArgs("/bin/sh", "-c", strings.Join(append(ctrCatCommands,