Skip to content

Commit

Permalink
feat: saves containerd user data to a persistent disk
Browse files Browse the repository at this point in the history
this allows users to retain downloaded images, containers,
etc. across new installations of finch

Signed-off-by: Sam Berning <[email protected]>
  • Loading branch information
sam-berning committed Jan 4, 2023
1 parent 26b7b09 commit 11715e1
Show file tree
Hide file tree
Showing 15 changed files with 691 additions and 21 deletions.
3 changes: 3 additions & 0 deletions cmd/finch/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package main
import (
"fmt"

"github.com/runfinch/finch/pkg/disk"

"github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/config"
"github.com/runfinch/finch/pkg/dependency"
Expand Down Expand Up @@ -108,6 +110,7 @@ func virtualMachineCommands(
config.NewNerdctlApplier(fssh.NewDialer(), fs, fp.LimaSSHPrivateKeyPath(), system.NewStdLib()),
fp,
fs,
disk.NewUserDataDiskManager(lcc, &afero.OsFs{}, fp, system.NewStdLib().Env("HOME")),
)
}

Expand Down
6 changes: 5 additions & 1 deletion cmd/finch/virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"strings"

"github.com/runfinch/finch/pkg/disk"

"github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/config"
"github.com/runfinch/finch/pkg/dependency"
Expand All @@ -30,6 +32,7 @@ func newVirtualMachineCommand(
nca config.NerdctlConfigApplier,
fp path.Finch,
fs afero.Fs,
diskManager disk.UserDataDiskManager,
) *cobra.Command {
virtualMachineCommand := &cobra.Command{
Use: virtualMachineRootCmd,
Expand All @@ -40,7 +43,8 @@ func newVirtualMachineCommand(
newStartVMCommand(limaCmdCreator, logger, optionalDepGroups, lca, nca, fs, fp.LimaSSHPrivateKeyPath()),
newStopVMCommand(limaCmdCreator, logger),
newRemoveVMCommand(limaCmdCreator, logger),
newInitVMCommand(limaCmdCreator, logger, optionalDepGroups, lca, nca, fp.BaseYamlFilePath(), fs, fp.LimaSSHPrivateKeyPath()),
newInitVMCommand(limaCmdCreator, logger, optionalDepGroups, lca, nca, fp.BaseYamlFilePath(), fs,
fp.LimaSSHPrivateKeyPath(), diskManager),
)

return virtualMachineCommand
Expand Down
25 changes: 20 additions & 5 deletions cmd/finch/virtual_machine_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package main
import (
"fmt"

"github.com/runfinch/finch/pkg/disk"

"github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/config"
"github.com/runfinch/finch/pkg/dependency"
Expand All @@ -25,11 +27,12 @@ func newInitVMCommand(
baseYamlFilePath string,
fs afero.Fs,
privateKeyPath string,
diskManager disk.UserDataDiskManager,
) *cobra.Command {
initVMCommand := &cobra.Command{
Use: "init",
Short: "Initialize the virtual machine",
RunE: newInitVMAction(lcc, logger, optionalDepGroups, lca, baseYamlFilePath).runAdapter,
RunE: newInitVMAction(lcc, logger, optionalDepGroups, lca, baseYamlFilePath, diskManager).runAdapter,
PostRunE: newPostVMStartInitAction(logger, lcc, fs, privateKeyPath, nca).runAdapter,
}

Expand All @@ -42,6 +45,7 @@ type initVMAction struct {
logger flog.Logger
optionalDepGroups []*dependency.Group
limaConfigApplier config.LimaConfigApplier
diskManager disk.UserDataDiskManager
}

func newInitVMAction(
Expand All @@ -50,9 +54,15 @@ func newInitVMAction(
optionalDepGroups []*dependency.Group,
lca config.LimaConfigApplier,
baseYamlFilePath string,
diskManager disk.UserDataDiskManager,
) *initVMAction {
return &initVMAction{
creator: creator, logger: logger, optionalDepGroups: optionalDepGroups, limaConfigApplier: lca, baseYamlFilePath: baseYamlFilePath,
creator: creator,
logger: logger,
optionalDepGroups: optionalDepGroups,
limaConfigApplier: lca,
baseYamlFilePath: baseYamlFilePath,
diskManager: diskManager,
}
}

Expand All @@ -61,7 +71,7 @@ func (iva *initVMAction) runAdapter(cmd *cobra.Command, args []string) error {
}

func (iva *initVMAction) run() error {
err := iva.assertVMIsNonexistent(iva.creator, iva.logger)
err := iva.assertVMIsNonexistent()
if err != nil {
return err
}
Expand All @@ -76,6 +86,11 @@ func (iva *initVMAction) run() error {
return err
}

err = iva.diskManager.InitializeUserDataDisk()
if err != nil {
return err
}

instanceName := fmt.Sprintf("--name=%v", limaInstanceName)
limaCmd := iva.creator.CreateWithoutStdio("start", instanceName, iva.baseYamlFilePath, "--tty=false")
iva.logger.Info("Initializing and starting Finch virtual machine...")
Expand All @@ -88,8 +103,8 @@ func (iva *initVMAction) run() error {
return nil
}

func (iva *initVMAction) assertVMIsNonexistent(creator command.LimaCmdCreator, logger flog.Logger) error {
status, err := lima.GetVMStatus(creator, logger, limaInstanceName)
func (iva *initVMAction) assertVMIsNonexistent() error {
status, err := lima.GetVMStatus(iva.creator, iva.logger, limaInstanceName)
if err != nil {
return err
}
Expand Down
25 changes: 20 additions & 5 deletions cmd/finch/virtual_machine_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const mockBaseYamlFilePath = "/os/os.yaml"
func TestNewInitVMCommand(t *testing.T) {
t.Parallel()

cmd := newInitVMCommand(nil, nil, nil, nil, nil, "", nil, "")
cmd := newInitVMCommand(nil, nil, nil, nil, nil, "", nil, "", nil)
assert.Equal(t, cmd.Name(), "init")
}

Expand All @@ -37,6 +37,7 @@ func TestInitVMAction_runAdapter(t *testing.T) {
*mocks.LimaCmdCreator,
*mocks.Logger,
*mocks.LimaConfigApplier,
*mocks.MockUserDataDiskManager,
*gomock.Controller,
)
}{
Expand All @@ -61,6 +62,7 @@ func TestInitVMAction_runAdapter(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -70,6 +72,7 @@ func TestInitVMAction_runAdapter(t *testing.T) {

command := mocks.NewCommand(ctrl)
lca.EXPECT().Apply().Return(nil)
dm.EXPECT().InitializeUserDataDisk().Return(nil)
lcc.EXPECT().CreateWithoutStdio("start", fmt.Sprintf("--name=%s", limaInstanceName),
mockBaseYamlFilePath, "--tty=false").Return(command)
command.EXPECT().CombinedOutput()
Expand All @@ -89,11 +92,12 @@ func TestInitVMAction_runAdapter(t *testing.T) {
logger := mocks.NewLogger(ctrl)
lcc := mocks.NewLimaCmdCreator(ctrl)
lca := mocks.NewLimaConfigApplier(ctrl)
dm := mocks.NewMockUserDataDiskManager(ctrl)

groups := tc.groups(ctrl)
tc.mockSvc(lcc, logger, lca, ctrl)
tc.mockSvc(lcc, logger, lca, dm, ctrl)

assert.NoError(t, newInitVMAction(lcc, logger, groups, lca, mockBaseYamlFilePath).runAdapter(tc.command, tc.args))
assert.NoError(t, newInitVMAction(lcc, logger, groups, lca, mockBaseYamlFilePath, dm).runAdapter(tc.command, tc.args))
})
}
}
Expand All @@ -109,6 +113,7 @@ func TestInitVMAction_run(t *testing.T) {
*mocks.LimaCmdCreator,
*mocks.Logger,
*mocks.LimaConfigApplier,
*mocks.MockUserDataDiskManager,
*gomock.Controller,
)
}{
Expand All @@ -122,6 +127,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -130,6 +136,7 @@ func TestInitVMAction_run(t *testing.T) {
logger.EXPECT().Debugf("Status of virtual machine: %s", "")

lca.EXPECT().Apply().Return(nil)
dm.EXPECT().InitializeUserDataDisk().Return(nil)

command := mocks.NewCommand(ctrl)
lcc.EXPECT().CreateWithoutStdio("start", fmt.Sprintf("--name=%s", limaInstanceName),
Expand All @@ -150,6 +157,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -170,6 +178,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -188,6 +197,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -206,6 +216,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand Down Expand Up @@ -234,6 +245,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -257,6 +269,7 @@ func TestInitVMAction_run(t *testing.T) {
lcc *mocks.LimaCmdCreator,
logger *mocks.Logger,
lca *mocks.LimaConfigApplier,
dm *mocks.MockUserDataDiskManager,
ctrl *gomock.Controller,
) {
getVMStatusC := mocks.NewCommand(ctrl)
Expand All @@ -265,6 +278,7 @@ func TestInitVMAction_run(t *testing.T) {
logger.EXPECT().Debugf("Status of virtual machine: %s", "")

lca.EXPECT().Apply().Return(nil)
dm.EXPECT().InitializeUserDataDisk().Return(nil)

logs := []byte("stdout + stderr")
command := mocks.NewCommand(ctrl)
Expand All @@ -287,11 +301,12 @@ func TestInitVMAction_run(t *testing.T) {
logger := mocks.NewLogger(ctrl)
lcc := mocks.NewLimaCmdCreator(ctrl)
lca := mocks.NewLimaConfigApplier(ctrl)
dm := mocks.NewMockUserDataDiskManager(ctrl)

groups := tc.groups(ctrl)
tc.mockSvc(lcc, logger, lca, ctrl)
tc.mockSvc(lcc, logger, lca, dm, ctrl)

err := newInitVMAction(lcc, logger, groups, lca, mockBaseYamlFilePath).run()
err := newInitVMAction(lcc, logger, groups, lca, mockBaseYamlFilePath, dm).run()
assert.Equal(t, err, tc.wantErr)
})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/finch/virtual_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestVirtualMachineCommand(t *testing.T) {
t.Parallel()

cmd := newVirtualMachineCommand(nil, nil, nil, nil, nil, "", nil)
cmd := newVirtualMachineCommand(nil, nil, nil, nil, nil, "", nil, nil)
assert.Equal(t, cmd.Use, virtualMachineRootCmd)

// check the number of subcommand for vm
Expand Down
44 changes: 44 additions & 0 deletions e2e/additional_disk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package e2e

import (
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/runfinch/common-tests/command"
"github.com/runfinch/common-tests/option"
)

const (
savedImage = "public.ecr.aws/docker/library/alpine:latest"
containerName = "userDataTest"
)

var testAdditionalDisk = func(o *option.Option) {
ginkgo.Describe("Additional disk", ginkgo.Serial, func() {
ginkgo.It("Retains container user data after the VM is deleted", func() {
command.Run(o, "pull", savedImage)
oldImagesOutput := command.StdoutStr(o, "images", "--format", "{{.Name}}")
gomega.Expect(oldImagesOutput).Should(gomega.ContainSubstring(savedImage))

command.Run(o, "run", "--name", containerName, savedImage)
oldPsOutput := command.StdoutStr(o, "ps", "--all", "--format", "{{.Names}}")
gomega.Expect(oldPsOutput).Should(gomega.ContainSubstring(containerName))

command.New(o, virtualMachineRootCmd, "stop").WithoutCheckingExitCode().WithTimeoutInSeconds(60).Run()
command.Run(o, virtualMachineRootCmd, "remove")

command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(240).Run()

newImagesOutput := command.StdoutStr(o, "images", "--format", "{{.Name}}")
gomega.Expect(newImagesOutput).Should(gomega.Equal(oldImagesOutput))

newPsOutput := command.StdoutStr(o, "ps", "--all", "--format", "{{.Names}}")
gomega.Expect(newPsOutput).Should(gomega.Equal(oldPsOutput))

command.Run(o, "rm", containerName)
command.Run(o, "rmi", savedImage)
})
})
}
4 changes: 2 additions & 2 deletions e2e/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func writeFile(filePath string, buf []byte) {
func updateAndApplyConfig(o *option.Option, configBytes []byte) *gexec.Session {
writeFile(finchConfigFilePath, configBytes)

command.New(o, virtualMachineRootCmd, "stop").WithoutCheckingExitCode().WithTimeoutInSeconds(20).Run()
return command.New(o, virtualMachineRootCmd, "start").WithoutCheckingExitCode().WithTimeoutInSeconds(60).Run()
command.New(o, virtualMachineRootCmd, "stop").WithoutCheckingExitCode().WithTimeoutInSeconds(60).Run()
return command.New(o, virtualMachineRootCmd, "start").WithoutCheckingExitCode().WithTimeoutInSeconds(120).Run()
}

// testConfig updates the finch config file and ensures that its settings are applied properly.
Expand Down
1 change: 1 addition & 0 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func TestE2e(t *testing.T) {
// When running tests in serial sequence and using the local registry, testVirtualMachine needs to run after generic tests finished
// since it will remove the VM instance thus removing the local registry.
testVirtualMachine(o)
testAdditionalDisk(o)
testConfig(o, *installed)
testVersion(o)
})
Expand Down
22 changes: 15 additions & 7 deletions finch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ mounts:
# 🟢 Builtin default: "reverse-sshfs"
mountType: reverse-sshfs

# Lima disks to attach to the instance. The disks will be accessible from inside the
# instance, labeled by name. (e.g. if the disk is named "data", it will be labeled
# "lima-data" inside the instance). The disk will be mounted inside the instance at
# `/mnt/lima-${VOLUME}`.
# 🟢 Builtin default: null
# For Finch, this value should always be the same as the diskName in pkg/disk/disk.go
additionalDisks:
- "finch"

ssh:
# A localhost port of the host. Forwarded to port 22 of the guest.
# 🟢 Builtin default: 0 (automatically assigned to a free port)
Expand Down Expand Up @@ -136,13 +145,12 @@ provision:
systemctl reset-failed NetworkManager-wait-online.service
systemctl mask NetworkManager-wait-online.service
# # `user` is executed without the root privilege
# - mode: user
# script: |
# #!/bin/bash
# set -eux -o pipefail
# cat <<EOF > ~/.vimrc
# set number
# EOF
- mode: user
script: |
#!/bin/bash
sudo chown $USER /mnt/lima-finch
sudo mount --bind /mnt/lima-finch ~/.local/share/containerd
systemctl --user restart containerd.service
# Probe scripts to check readiness.
# 🟢 Builtin default: null
Expand Down
Loading

0 comments on commit 11715e1

Please sign in to comment.