Skip to content

Commit b0a91e8

Browse files
authored
Merge pull request #573 from Kern--/vm-mounts
Add ability to launch containers with vm local rootfs.
2 parents fdc542c + 24e7e95 commit b0a91e8

File tree

8 files changed

+630
-14
lines changed

8 files changed

+630
-14
lines changed

agent/service.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas
189189
return nil
190190
})
191191

192+
isVmLocalRootFs := len(req.Rootfs) > 0 && vm.IsLocalMount(req.Rootfs[0])
193+
194+
// If the rootfs is inside the VM, then the DriveMount call didn't happen and therefore
195+
// the bundledir was not created. Create it here.
196+
if isVmLocalRootFs {
197+
if err := os.MkdirAll(bundleDir.RootfsPath(), 0700); err != nil {
198+
return nil, errors.Wrapf(err, "Failed to create bundle's rootfs path from inside the vm %q", bundleDir.RootfsPath())
199+
}
200+
}
201+
192202
// check the rootfs dir has been created (presumed to be by a previous MountDrive call)
193203
rootfsStat, err := os.Stat(bundleDir.RootfsPath())
194204
if err != nil {
@@ -204,8 +214,24 @@ func (ts *TaskService) Create(requestCtx context.Context, req *taskAPI.CreateTas
204214
}
205215
return nil
206216
})
207-
208-
err = bundleDir.OCIConfig().Write(extraData.JsonSpec)
217+
specData := extraData.JsonSpec
218+
219+
// If the rootfs is inside the VM then:
220+
// a) the rootfs mount type has a prefix that we used to identify this which needs to be stripped before passing to runc
221+
// b) we were not able to inspect the container's rootfs from the client when setting up the spec. Do that here.
222+
if isVmLocalRootFs {
223+
req.Rootfs[0] = vm.StripLocalMountIdentifier(req.Rootfs[0])
224+
rootfsMount := mount.Mount{
225+
Type: req.Rootfs[0].Type,
226+
Source: req.Rootfs[0].Source,
227+
Options: req.Rootfs[0].Options,
228+
}
229+
specData, err = vm.UpdateUserInSpec(requestCtx, specData, rootfsMount)
230+
if err != nil {
231+
return nil, errors.Wrap(err, "failed to update spec")
232+
}
233+
}
234+
err = bundleDir.OCIConfig().Write(specData)
209235
if err != nil {
210236
return nil, errors.Wrap(err, "failed to write oci config file")
211237
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/awslabs/tc-redirect-tap v0.0.0-20211025175357-e30dfca224c2
77
github.com/bits-and-blooms/bitset v1.2.1 // indirect
88
github.com/containerd/containerd v1.5.9
9-
github.com/containerd/continuity v0.2.0 // indirect
9+
github.com/containerd/continuity v0.2.0
1010
github.com/containerd/fifo v1.0.0
1111
github.com/containerd/go-cni v1.1.1 // indirect
1212
github.com/containerd/go-runc v1.0.0
@@ -25,6 +25,7 @@ require (
2525
github.com/klauspost/compress v1.13.6 // indirect
2626
github.com/mdlayher/vsock v1.1.0
2727
github.com/miekg/dns v1.1.25
28+
github.com/opencontainers/image-spec v1.0.2
2829
github.com/opencontainers/runc v1.0.3
2930
github.com/opencontainers/runtime-spec v1.0.3-0.20210910115017-0d6cc581aeea
3031
github.com/opencontainers/selinux v1.8.5 // indirect

internal/vm/mount.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 vm
15+
16+
import (
17+
"strings"
18+
19+
"github.com/containerd/containerd/api/types"
20+
"github.com/containerd/containerd/mount"
21+
)
22+
23+
const vmLocalMountTypePrefix = "vm:"
24+
25+
// IsLocalMount returns true if the mount source is inside the VM as opposed to the
26+
// default assumption that the mount source is a block device on the host.
27+
func IsLocalMount(mount *types.Mount) bool {
28+
return mount != nil && strings.HasPrefix(mount.Type, vmLocalMountTypePrefix)
29+
}
30+
31+
// AddLocalMountIdentifier adds an identifier to a mount so that it can be detected by IsLocalMount.
32+
// This is intended to be used by cooperating snapshotters to mark mounts as inside the VM so they
33+
// can be plumbed at the proper points inside/outside the VM.
34+
func AddLocalMountIdentifier(mnt mount.Mount) mount.Mount {
35+
return mount.Mount{
36+
Type: vmLocalMountTypePrefix + mnt.Type,
37+
Source: mnt.Source,
38+
Options: mnt.Options,
39+
}
40+
}
41+
42+
// StripLocalMountIdentifier removes the identifier that signals that a mount
43+
// is inside the VM. This is used before passing the mount information to runc
44+
func StripLocalMountIdentifier(mnt *types.Mount) *types.Mount {
45+
options := make([]string, len(mnt.Options))
46+
copy(options, mnt.Options)
47+
return &types.Mount{
48+
Type: strings.Replace(mnt.Type, vmLocalMountTypePrefix, "", 1),
49+
Source: mnt.Source,
50+
Target: mnt.Target,
51+
Options: options,
52+
}
53+
}

internal/vm/mount_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 vm
15+
16+
import (
17+
"testing"
18+
19+
"github.com/containerd/containerd/api/types"
20+
"github.com/containerd/containerd/mount"
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func TestIsLocalMount(t *testing.T) {
25+
mnt := types.Mount{
26+
Type: "bind",
27+
Source: "/tmp/snapshots/1/fs",
28+
Options: []string{"rbind"},
29+
}
30+
assert.False(t, IsLocalMount(&mnt), "Standard mount was considered vm local")
31+
}
32+
33+
func TestAddLocalMountIdentifier(t *testing.T) {
34+
mnt := mount.Mount{
35+
Type: "bind",
36+
Source: "/tmp/snapshots/1/fs",
37+
Options: []string{"rbind"},
38+
}
39+
localMnt := AddLocalMountIdentifier(mnt)
40+
41+
mntProto := types.Mount{
42+
Type: mnt.Type,
43+
Source: mnt.Source,
44+
Options: mnt.Options,
45+
}
46+
localMntProto := types.Mount{
47+
Type: localMnt.Type,
48+
Source: localMnt.Source,
49+
Options: localMnt.Options,
50+
}
51+
52+
assert.False(t, IsLocalMount(&mntProto), "Standard mount was considered vm local")
53+
assert.True(t, IsLocalMount(&localMntProto), "Mount was not vm local after adding the local mount identifier")
54+
}
55+
56+
func TestStripLocalMountIdentifier(t *testing.T) {
57+
mnt := mount.Mount{
58+
Type: "bind",
59+
Source: "/tmp/snapshots/1/fs",
60+
Options: []string{"rbind"},
61+
}
62+
localMnt := AddLocalMountIdentifier(mnt)
63+
localMntProto := &types.Mount{
64+
Type: localMnt.Type,
65+
Source: localMnt.Source,
66+
Options: localMnt.Options,
67+
}
68+
assert.True(t, IsLocalMount(localMntProto), "Mount was not vm local after adding the local mount identifier")
69+
localMntProto = StripLocalMountIdentifier(localMntProto)
70+
assert.False(t, IsLocalMount(localMntProto), "Mount was vm local after stripping the local mount identifier")
71+
}

0 commit comments

Comments
 (0)