Skip to content

Commit 1a1184d

Browse files
authored
Merge pull request #669 from kzys/volume-stargz-2
volume: support in-VM snapshotters
2 parents 19aa030 + 6f4f8a0 commit 1a1184d

File tree

13 files changed

+660
-56
lines changed

13 files changed

+660
-56
lines changed

.buildkite/pipeline.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ steps:
101101
DOCKER_IMAGE_TAG: "$BUILDKITE_BUILD_NUMBER"
102102
NUMBER_OF_VMS: 10
103103
EXTRAGOARGS: "-v -count=1 -race"
104+
FICD_DM_VOLUME_GROUP: fcci-vg
104105
artifact_paths:
105106
- "snapshotter/logs/*"
106107
command:
107-
- make -C snapshotter integ-test
108+
- make -C snapshotter integ-test FICD_DM_POOL=build_${BUILDKITE_BUILD_NUMBER}_snapshotter
108109

109110
- wait
110111

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# express or implied. See the License for the specific language governing
1212
# permissions and limitations under the License.
1313

14-
SUBDIRS:=agent runtime examples firecracker-control/cmd/containerd snapshotter docker-credential-mmds
14+
SUBDIRS:=agent runtime examples firecracker-control/cmd/containerd snapshotter docker-credential-mmds volume
1515
TEST_SUBDIRS:=$(addprefix test-,$(SUBDIRS))
1616
INTEG_TEST_SUBDIRS:=$(addprefix integ-test-,$(SUBDIRS))
1717

@@ -147,9 +147,10 @@ files_ephemeral: $(RUNC_BIN) agent-in-docker
147147
cp agent/agent tools/image-builder/files_ephemeral/usr/local/bin
148148
touch tools/image-builder/files_ephemeral
149149

150-
files_ephemeral_stargz: $(STARGZ_BIN) docker-credential-mmds-in-docker
150+
files_ephemeral_stargz: $(STARGZ_BIN) docker-credential-mmds-in-docker volume-in-docker
151151
mkdir -p tools/image-builder/files_ephemeral_stargz/usr/local/bin
152152
cp docker-credential-mmds/docker-credential-mmds tools/image-builder/files_ephemeral_stargz/usr/local/bin
153+
cp volume/volume-init tools/image-builder/files_ephemeral_stargz/usr/local/bin
153154
cp $(STARGZ_BIN) tools/image-builder/files_ephemeral_stargz/usr/local/bin
154155

155156
image: files_ephemeral

snapshotter/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ GOSUM := $(GOMOD:.mod=.sum)
2020
DOCKER_IMAGE_TAG?=latest
2121
GO_CACHE_VOLUME_NAME?=gocache
2222
FIRECRACKER_CONTAINERD_TEST_IMAGE?=localhost/firecracker-containerd-test
23+
FICD_DM_POOL?=fc-test-thinpool
2324

2425
REVISION := $(shell git rev-parse HEAD)
2526

@@ -51,6 +52,7 @@ integ-test:
5152
$(MAKE) $(addprefix integ-test-,$(INTEG_TESTNAMES))
5253

5354
integ-test-%: logs
55+
$(CURDIR)/../tools/thinpool.sh reset "$(FICD_DM_POOL)"
5456
docker run --rm -it \
5557
--privileged \
5658
--ipc=host \
@@ -65,6 +67,8 @@ integ-test-%: logs
6567
--env GOSUMDB=off \
6668
--env GO111MODULE=on \
6769
--env NUMBER_OF_VMS=$(NUMBER_OF_VMS) \
70+
--env FICD_DM_VOLUME_GROUP=$(FICD_DM_VOLUME_GROUP) \
71+
--env FICD_DM_POOL=$(FICD_DM_POOL) \
6872
--workdir="/src/snapshotter" \
6973
--init \
7074
$(FIRECRACKER_CONTAINERD_TEST_IMAGE):$(DOCKER_IMAGE_TAG) \

snapshotter/service_integ_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import (
3434
const (
3535
snapshotterName = "demux"
3636

37-
imageRef = "ghcr.io/firecracker-microvm/firecracker-containerd/amazonlinux:latest-esgz"
37+
al2stargz = "ghcr.io/firecracker-microvm/firecracker-containerd/amazonlinux:latest-esgz"
3838

3939
noAuth = ""
4040

@@ -138,10 +138,10 @@ func launchContainerWithRemoteSnapshotterInVM(ctx context.Context, vmID string)
138138
return fmt.Errorf("Failed to configure VM metadata for registry resolution [%v]", err)
139139
}
140140

141-
image, err := client.Pull(ctx, imageRef,
141+
image, err := client.Pull(ctx, al2stargz,
142142
containerd.WithPullUnpack,
143143
containerd.WithPullSnapshotter(snapshotterName),
144-
containerd.WithImageHandlerWrapper(source.AppendDefaultLabelsHandlerWrapper(imageRef, 10*1024*1024)),
144+
containerd.WithImageHandlerWrapper(source.AppendDefaultLabelsHandlerWrapper(al2stargz, 10*1024*1024)),
145145
)
146146
if err != nil {
147147
return fmt.Errorf("Failed to pull image for VM: %s [%v]", vmID, err)

snapshotter/volume_integ_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package main
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"testing"
20+
21+
"github.com/containerd/containerd"
22+
"github.com/containerd/containerd/namespaces"
23+
"github.com/containerd/containerd/oci"
24+
"github.com/firecracker-microvm/firecracker-containerd/internal/integtest"
25+
"github.com/firecracker-microvm/firecracker-containerd/proto"
26+
"github.com/firecracker-microvm/firecracker-containerd/runtime/firecrackeroci"
27+
"github.com/firecracker-microvm/firecracker-containerd/snapshotter/internal/integtest/stargz/fs/source"
28+
"github.com/firecracker-microvm/firecracker-containerd/volume"
29+
"github.com/stretchr/testify/assert"
30+
"github.com/stretchr/testify/require"
31+
)
32+
33+
const mib = 1024 * 1024
34+
35+
func TestGuestVolumeFrom_Isolated(t *testing.T) {
36+
integtest.Prepare(t, integtest.WithDefaultNetwork())
37+
const (
38+
vmID = "default"
39+
postgres = "docker.io/library/postgres:14.3"
40+
alpine = "docker.io/library/alpine:3.10.1"
41+
runtime = "aws.firecracker"
42+
)
43+
44+
ctx := namespaces.WithNamespace(context.Background(), vmID)
45+
46+
client, err := containerd.New(integtest.ContainerdSockPath, containerd.WithDefaultRuntime(runtime))
47+
require.NoError(t, err, "unable to create client to containerd service at %s, is containerd running?", integtest.ContainerdSockPath)
48+
defer client.Close()
49+
fcClient, err := integtest.NewFCControlClient(integtest.ContainerdSockPath)
50+
require.NoError(t, err, "Failed to create fccontrol client")
51+
52+
vs := volume.NewSet(runtime)
53+
54+
// Add a non-stargz image with volumes.
55+
localImage := volume.FromImage(client, postgres, "postgres-snapshot", volume.WithSnapshotter("devmapper"))
56+
err = vs.AddFrom(ctx, localImage)
57+
require.NoError(t, err)
58+
59+
// Add a stargz image.
60+
// The volume directories must be specified since the host's containerd doesn't know about the image.
61+
remoteImage := volume.FromGuestImage(
62+
client, vmID, al2stargz, "al2-snapshot", []string{"/etc/yum"},
63+
volume.WithSnapshotter("demux"),
64+
volume.WithPullOptions(containerd.WithImageHandlerWrapper(
65+
source.AppendDefaultLabelsHandlerWrapper(al2stargz, 10*mib),
66+
)),
67+
)
68+
err = vs.AddFrom(ctx, remoteImage)
69+
require.NoError(t, err)
70+
71+
// PrepareDriveMount only copies images that are available before starting the VM.
72+
// In this case, only postgres.
73+
mount, err := vs.PrepareDriveMount(ctx, 10*mib)
74+
require.NoError(t, err)
75+
76+
_, err = fcClient.CreateVM(ctx, &proto.CreateVMRequest{
77+
VMID: vmID,
78+
RootDrive: &proto.FirecrackerRootDrive{
79+
HostPath: "/var/lib/firecracker-containerd/runtime/rootfs-stargz.img",
80+
},
81+
NetworkInterfaces: []*proto.FirecrackerNetworkInterface{
82+
{
83+
AllowMMDS: true,
84+
CNIConfig: &proto.CNIConfiguration{
85+
NetworkName: "fcnet",
86+
InterfaceName: "veth0",
87+
},
88+
},
89+
},
90+
MachineCfg: &proto.FirecrackerMachineConfiguration{
91+
VcpuCount: 2,
92+
MemSizeMib: 2048,
93+
},
94+
ContainerCount: 1,
95+
DriveMounts: []*proto.FirecrackerDriveMount{mount},
96+
})
97+
require.NoErrorf(t, err, "Failed to create microVM[%s]", vmID)
98+
defer fcClient.StopVM(ctx, &proto.StopVMRequest{VMID: vmID})
99+
100+
_, err = fcClient.SetVMMetadata(ctx, &proto.SetVMMetadataRequest{
101+
VMID: vmID,
102+
Metadata: fmt.Sprintf(dockerMetadataTemplate, "ghcr.io", noAuth, noAuth),
103+
})
104+
require.NoError(t, err, "Failed to configure VM metadata for registry resolution")
105+
106+
// PrepareGuestVolumes only copies images that are only available after starting the VM.
107+
// In this case, only al2stargz.
108+
err = vs.PrepareInGuest(ctx, "prepare-in-guest")
109+
require.NoError(t, err)
110+
111+
image, err := client.Pull(ctx,
112+
alpine,
113+
containerd.WithPullUnpack,
114+
containerd.WithPullSnapshotter("devmapper"),
115+
)
116+
require.NoError(t, err)
117+
118+
mountsFromAL2, err := vs.WithMountsFromProvider(al2stargz)
119+
require.NoError(t, err)
120+
121+
mountsFromPostgres, err := vs.WithMountsFromProvider(postgres)
122+
require.NoError(t, err)
123+
124+
name := "cat"
125+
snapshotName := fmt.Sprintf("%s-snapshot", name)
126+
container, err := client.NewContainer(ctx,
127+
name,
128+
containerd.WithSnapshotter("devmapper"),
129+
containerd.WithNewSnapshot(snapshotName, image),
130+
containerd.WithNewSpec(
131+
firecrackeroci.WithVMID(vmID),
132+
oci.WithProcessArgs("sh", "-c", "ls -d /var/lib/postgresql/data; ls /etc/yum"),
133+
oci.WithDefaultPathEnv,
134+
mountsFromAL2,
135+
mountsFromPostgres,
136+
),
137+
)
138+
require.NoError(t, err, "failed to create container %s", name)
139+
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
140+
141+
result, err := integtest.RunTask(ctx, container)
142+
require.NoError(t, err)
143+
144+
assert.Equal(t, uint32(0), result.ExitCode)
145+
assert.Equal(t, "/var/lib/postgresql/data\nfssnap.d\npluginconf.d\nprotected.d\nvars\nversion-groups.conf\n", result.Stdout)
146+
assert.Equal(t, "", result.Stderr)
147+
}

tools/image-builder/files_debootstrap/sbin/overlay-init

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ fi
5757

5858
do_overlay
5959

60+
# firecracker-containerd itself doesn't need /volumes but volume package
61+
# uses that to share files between in-VM snapshotters.
62+
mkdir /volumes
63+
6064
# invoke the actual system init program and procede with the boot
6165
# process.
6266
exec /usr/sbin/init $@

volume/Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
# not use this file except in compliance with the License. A copy of the
5+
# License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is distributed
10+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
# express or implied. See the License for the specific language governing
12+
# permissions and limitations under the License.
13+
14+
volume-init: $(shell find ./cmd -name '*.go')
15+
go build -o $@ ./cmd/volume-init
16+
17+
test:
18+
go test ./...
19+
20+
clean:
21+
rm -f volume-init
22+
23+
install:
24+
25+
.PHONY: test clean install

volume/cmd/volume-init/main.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package main
15+
16+
import (
17+
"encoding/json"
18+
"fmt"
19+
"io"
20+
"os"
21+
22+
"github.com/containerd/continuity/fs"
23+
"github.com/firecracker-microvm/firecracker-containerd/volume"
24+
)
25+
26+
func copy(in volume.GuestVolumeImageInput) error {
27+
for _, v := range in.Volumes {
28+
to, err := fs.RootPath(in.To, v)
29+
if err != nil {
30+
return fmt.Errorf("failed to join %q and %q: %w", in.To, v, err)
31+
}
32+
33+
from, err := fs.RootPath(in.From, v)
34+
if err != nil {
35+
return fmt.Errorf("failed to join %q and %q: %w", in.From, v, err)
36+
}
37+
38+
err = os.MkdirAll(to, 0700)
39+
if err != nil {
40+
return err
41+
}
42+
43+
err = fs.CopyDir(to, from)
44+
if err != nil {
45+
return err
46+
}
47+
}
48+
return nil
49+
}
50+
51+
func realMain() error {
52+
b, err := io.ReadAll(os.Stdin)
53+
if err != nil {
54+
return err
55+
}
56+
57+
var in volume.GuestVolumeImageInput
58+
err = json.Unmarshal(b, &in)
59+
if err != nil {
60+
return err
61+
}
62+
63+
return copy(in)
64+
}
65+
66+
func main() {
67+
err := realMain()
68+
if err != nil {
69+
out := volume.GuestVolumeImageOutput{Error: err.Error()}
70+
b, err := json.Marshal(out)
71+
if err != nil {
72+
fmt.Fprintf(os.Stderr, "failed to unmarshal %+v: %s", out, err)
73+
os.Exit(2)
74+
}
75+
fmt.Printf("%s", string(b))
76+
os.Exit(1)
77+
}
78+
}

volume/cmd/volume-init/main_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package main
15+
16+
import (
17+
"os"
18+
"path/filepath"
19+
"testing"
20+
21+
"github.com/firecracker-microvm/firecracker-containerd/volume"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func mkdirAndTouch(path, content string) error {
26+
err := os.MkdirAll(filepath.Dir(path), 0700)
27+
if err != nil {
28+
return err
29+
}
30+
31+
return os.WriteFile(path, []byte(content), 0600)
32+
}
33+
34+
func TestCopy(t *testing.T) {
35+
from := t.TempDir()
36+
to := t.TempDir()
37+
38+
err := mkdirAndTouch(filepath.Join(from, "/etc/foobar/hello"), "helloworld")
39+
require.NoError(t, err)
40+
41+
err = copy(volume.GuestVolumeImageInput{
42+
From: from,
43+
To: to,
44+
Volumes: []string{"/etc/foobar"},
45+
})
46+
require.NoError(t, err)
47+
48+
b, err := os.ReadFile(filepath.Join(to, "/etc/foobar/hello"))
49+
require.NoError(t, err)
50+
require.Equal(t, "helloworld", string(b))
51+
}

0 commit comments

Comments
 (0)