forked from google/gvisor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mount_hints.go
293 lines (263 loc) · 8.52 KB
/
mount_hints.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 boot
import (
"fmt"
"path/filepath"
"strings"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/specutils"
)
const (
// MountPrefix is the annotation prefix for mount hints applied at the pod level.
MountPrefix = "dev.gvisor.spec.mount."
// RootfsPrefix is the annotation prefix for rootfs hint applied at the container level.
RootfsPrefix = "dev.gvisor.spec.rootfs."
)
// ShareType indicates who can access/mutate the volume contents.
type ShareType int
const (
invalid ShareType = iota
// container shareType indicates that the mount is used by a single
// container. There are no external observers.
container
// pod shareType indicates that the mount is used by more than one container
// inside the pod. There are no external observers.
pod
// shared shareType indicates that the mount can also be shared with a process
// outside the pod, e.g. NFS.
shared
)
func (s ShareType) String() string {
switch s {
case invalid:
return "invalid"
case container:
return "container"
case pod:
return "pod"
case shared:
return "shared"
default:
return fmt.Sprintf("invalid share value %d", s)
}
}
// PodMountHints contains a collection of mountHints for the pod.
type PodMountHints struct {
Mounts map[string]*MountHint `json:"mounts"`
}
// NewPodMountHints instantiates PodMountHints using spec.
func NewPodMountHints(spec *specs.Spec) (*PodMountHints, error) {
mnts := make(map[string]*MountHint)
for k, v := range spec.Annotations {
// Look for 'dev.gvisor.spec.mount' annotations and parse them.
if strings.HasPrefix(k, MountPrefix) {
// Remove the prefix and split the rest.
parts := strings.Split(k[len(MountPrefix):], ".")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid mount annotation: %s=%s", k, v)
}
name := parts[0]
if len(name) == 0 {
return nil, fmt.Errorf("invalid mount name: %s", name)
}
mnt := mnts[name]
if mnt == nil {
mnt = &MountHint{Name: name}
mnts[name] = mnt
}
if err := mnt.setField(parts[1], v); err != nil {
log.Warningf("ignoring invalid mount annotation (name = %q, key = %q, value = %q): %v", name, parts[1], v, err)
}
}
}
// Validate all the parsed hints.
for name, m := range mnts {
log.Infof("Mount annotation found, name: %s, source: %q, type: %s, share: %v", name, m.Mount.Source, m.Mount.Type, m.Share)
if m.Share == invalid || len(m.Mount.Source) == 0 || len(m.Mount.Type) == 0 {
log.Warningf("ignoring mount annotations for %q because of missing required field(s)", name)
delete(mnts, name)
continue
}
// Check for duplicate mount sources.
for name2, m2 := range mnts {
if name != name2 && m.Mount.Source == m2.Mount.Source {
return nil, fmt.Errorf("mounts %q and %q have the same mount source %q", m.Name, m2.Name, m.Mount.Source)
}
}
}
return &PodMountHints{Mounts: mnts}, nil
}
// MountHint represents extra information about mounts that are provided via
// annotations. They can override mount type, and provide sharing information
// so that mounts can be correctly shared inside the pod.
// It is part of the sandbox.Sandbox struct, so it must be serializable.
type MountHint struct {
Name string `json:"name"`
Share ShareType `json:"share"`
Mount specs.Mount `json:"mount"`
}
func (m *MountHint) setField(key, val string) error {
switch key {
case "source":
if len(val) == 0 {
return fmt.Errorf("source cannot be empty")
}
m.Mount.Source = val
case "type":
return m.setType(val)
case "share":
return m.setShare(val)
case "options":
m.Mount.Options = specutils.FilterMountOptions(strings.Split(val, ","))
default:
return fmt.Errorf("invalid mount annotation: %s=%s", key, val)
}
return nil
}
func (m *MountHint) setType(val string) error {
switch val {
case tmpfs.Name, Bind:
m.Mount.Type = val
default:
return fmt.Errorf("invalid type %q", val)
}
return nil
}
func (m *MountHint) setShare(val string) error {
switch val {
case container.String():
m.Share = container
case pod.String():
m.Share = pod
case shared.String():
m.Share = shared
default:
return fmt.Errorf("invalid share value %q", val)
}
return nil
}
// ShouldShareMount returns true if this mount should be configured as a shared
// mount that is shared among multiple containers in a pod.
func (m *MountHint) ShouldShareMount() bool {
// Only support tmpfs for now. Bind mounts require a common gofer to mount
// all shared volumes.
return m.Mount.Type == tmpfs.Name &&
// A shared mount should be configured for share=container too so:
// 1. Restarting the container does not lose the tmpfs data.
// 2. Repeated mounts in the container reuse the same tmpfs instance.
(m.Share == container || m.Share == pod)
}
// checkCompatible verifies that shared mount is compatible with master.
// Master options must be the same or less restrictive than the container mount,
// e.g. master can be 'rw' while container mounts as 'ro'.
func (m *MountHint) checkCompatible(replica *specs.Mount) error {
masterOpts := ParseMountOptions(m.Mount.Options)
replicaOpts := ParseMountOptions(replica.Options)
if masterOpts.ReadOnly && !replicaOpts.ReadOnly {
return fmt.Errorf("cannot mount read-write shared mount because master is read-only, mount: %+v", replica)
}
if masterOpts.Flags.NoExec && !replicaOpts.Flags.NoExec {
return fmt.Errorf("cannot mount exec enabled shared mount because master is noexec, mount: %+v", replica)
}
if masterOpts.Flags.NoATime && !replicaOpts.Flags.NoATime {
return fmt.Errorf("cannot mount atime enabled shared mount because master is noatime, mount: %+v", replica)
}
return nil
}
func (m *MountHint) fileAccessType() config.FileAccessType {
if m.Share == shared {
return config.FileAccessShared
}
if m.ShouldShareMount() {
return config.FileAccessExclusive
}
if m.Share == container {
return config.FileAccessExclusive
}
return config.FileAccessShared
}
// FindMount finds the MountHint that applies to this mount.
func (p *PodMountHints) FindMount(mountSrc string) *MountHint {
for _, m := range p.Mounts {
if m.Mount.Source == mountSrc {
return m
}
}
return nil
}
// RootfsHint represents extra information about rootfs that are provided via
// annotations. They can provide mount source, mount type and overlay config.
type RootfsHint struct {
Mount specs.Mount
Overlay config.OverlayMedium
}
func (r *RootfsHint) setSource(val string) error {
if !filepath.IsAbs(val) {
return fmt.Errorf("source should be an absolute path, got %q", val)
}
r.Mount.Source = val
return nil
}
func (r *RootfsHint) setType(val string) error {
switch val {
case erofs.Name, Bind:
r.Mount.Type = val
default:
return fmt.Errorf("invalid type %q", val)
}
return nil
}
func (r *RootfsHint) setField(key, val string) error {
switch key {
case "source":
return r.setSource(val)
case "type":
return r.setType(val)
case "overlay":
return r.Overlay.Set(val)
default:
return fmt.Errorf("invalid rootfs annotation: %s=%s", key, val)
}
}
// NewRootfsHint instantiates RootfsHint using spec.
func NewRootfsHint(spec *specs.Spec) (*RootfsHint, error) {
var hint *RootfsHint
for k, v := range spec.Annotations {
// Look for 'dev.gvisor.spec.rootfs' annotations and parse them.
if !strings.HasPrefix(k, RootfsPrefix) {
continue
}
// Remove the prefix.
k = k[len(RootfsPrefix):]
if hint == nil {
hint = &RootfsHint{}
}
if err := hint.setField(k, v); err != nil {
return nil, fmt.Errorf("invalid rootfs annotation (key = %q, value = %q): %v", k, v, err)
}
}
// Validate the parsed hint.
if hint != nil {
log.Infof("Rootfs annotations found, source: %q, type: %q, overlay: %q", hint.Mount.Source, hint.Mount.Type, hint.Overlay)
if len(hint.Mount.Source) == 0 || len(hint.Mount.Type) == 0 {
return nil, fmt.Errorf("rootfs annotations missing required field(s): %+v", hint)
}
}
return hint, nil
}