-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
mount.go
369 lines (310 loc) · 10.8 KB
/
mount.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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
package container_disk
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"sync"
"time"
"kubevirt.io/client-go/log"
containerdisk "kubevirt.io/kubevirt/pkg/container-disk"
diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
"kubevirt.io/kubevirt/pkg/virt-handler/isolation"
"k8s.io/apimachinery/pkg/types"
v1 "kubevirt.io/client-go/api/v1"
)
//go:generate mockgen -source $GOFILE -package=$GOPACKAGE -destination=generated_mock_$GOFILE
type mounter struct {
podIsolationDetector isolation.PodIsolationDetector
mountStateDir string
mountRecords map[types.UID]*vmiMountTargetRecord
mountRecordsLock sync.Mutex
suppressWarningTimeout time.Duration
pathGetter containerdisk.SocketPathGetter
}
type Mounter interface {
ContainerDisksReady(vmi *v1.VirtualMachineInstance, notInitializedSince time.Time) (bool, error)
Mount(vmi *v1.VirtualMachineInstance, verify bool) error
Unmount(vmi *v1.VirtualMachineInstance) error
}
type vmiMountTargetEntry struct {
TargetFile string `json:"targetFile"`
SocketFile string `json:"socketFile"`
}
type vmiMountTargetRecord struct {
MountTargetEntries []vmiMountTargetEntry `json:"mountTargetEntries"`
}
func NewMounter(isoDetector isolation.PodIsolationDetector, mountStateDir string) Mounter {
return &mounter{
mountRecords: make(map[types.UID]*vmiMountTargetRecord),
podIsolationDetector: isoDetector,
mountStateDir: mountStateDir,
suppressWarningTimeout: 1 * time.Minute,
pathGetter: containerdisk.NewSocketPathGetter(""),
}
}
func (m *mounter) deleteMountTargetRecord(vmi *v1.VirtualMachineInstance) error {
if string(vmi.UID) == "" {
return fmt.Errorf("unable to find container disk mounted directories for vmi without uid")
}
recordFile := filepath.Join(m.mountStateDir, string(vmi.UID))
exists, err := diskutils.FileExists(recordFile)
if err != nil {
return err
}
if exists {
record, err := m.getMountTargetRecord(vmi)
if err != nil {
return err
}
for _, target := range record.MountTargetEntries {
os.Remove(target.TargetFile)
os.Remove(target.SocketFile)
}
os.Remove(recordFile)
}
m.mountRecordsLock.Lock()
defer m.mountRecordsLock.Unlock()
delete(m.mountRecords, vmi.UID)
return nil
}
func (m *mounter) getMountTargetRecord(vmi *v1.VirtualMachineInstance) (*vmiMountTargetRecord, error) {
var ok bool
var existingRecord *vmiMountTargetRecord
if string(vmi.UID) == "" {
return nil, fmt.Errorf("unable to find container disk mounted directories for vmi without uid")
}
m.mountRecordsLock.Lock()
defer m.mountRecordsLock.Unlock()
existingRecord, ok = m.mountRecords[vmi.UID]
// first check memory cache
if ok {
return existingRecord, nil
}
// if not there, see if record is on disk, this can happen if virt-handler restarts
recordFile := filepath.Join(m.mountStateDir, string(vmi.UID))
exists, err := diskutils.FileExists(recordFile)
if err != nil {
return nil, err
}
if exists {
record := vmiMountTargetRecord{}
bytes, err := ioutil.ReadFile(recordFile)
if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, &record)
if err != nil {
return nil, err
}
m.mountRecords[vmi.UID] = &record
return &record, nil
}
// not found
return nil, nil
}
func (m *mounter) setMountTargetRecord(vmi *v1.VirtualMachineInstance, record *vmiMountTargetRecord) error {
if string(vmi.UID) == "" {
return fmt.Errorf("unable to set container disk mounted directories for vmi without uid")
}
recordFile := filepath.Join(m.mountStateDir, string(vmi.UID))
fileExists, err := diskutils.FileExists(recordFile)
if err != nil {
return err
}
m.mountRecordsLock.Lock()
defer m.mountRecordsLock.Unlock()
existingRecord, ok := m.mountRecords[vmi.UID]
if ok && fileExists && reflect.DeepEqual(existingRecord, record) {
// already done
return nil
}
bytes, err := json.Marshal(record)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(recordFile), 0755)
if err != nil {
return err
}
err = ioutil.WriteFile(recordFile, bytes, 0644)
if err != nil {
return err
}
m.mountRecords[vmi.UID] = record
return nil
}
// Mount takes a vmi and mounts all container disks of the VMI, so that they are visible for the qemu process.
// Additionally qcow2 images are validated if "verify" is true. The validation happens with rlimits set, to avoid DOS.
func (m *mounter) Mount(vmi *v1.VirtualMachineInstance, verify bool) error {
record := vmiMountTargetRecord{}
for i, volume := range vmi.Spec.Volumes {
if volume.ContainerDisk != nil {
targetFile, err := containerdisk.GetDiskTargetPathFromHostView(vmi, i)
if err != nil {
return err
}
sock, err := m.pathGetter(vmi, i)
if err != nil {
return err
}
record.MountTargetEntries = append(record.MountTargetEntries, vmiMountTargetEntry{
TargetFile: targetFile,
SocketFile: sock,
})
}
}
if len(record.MountTargetEntries) > 0 {
err := m.setMountTargetRecord(vmi, &record)
if err != nil {
return err
}
}
for i, volume := range vmi.Spec.Volumes {
if volume.ContainerDisk != nil {
targetFile, err := containerdisk.GetDiskTargetPathFromHostView(vmi, i)
if err != nil {
return err
}
nodeRes := isolation.NodeIsolationResult()
if isMounted, err := nodeRes.IsMounted(targetFile); err != nil {
return fmt.Errorf("failed to determine if %s is already mounted: %v", targetFile, err)
} else if !isMounted {
sock, err := m.pathGetter(vmi, i)
if err != nil {
return err
}
res, err := m.podIsolationDetector.DetectForSocket(vmi, sock)
if err != nil {
return fmt.Errorf("failed to detect socket for containerDisk %v: %v", volume.Name, err)
}
mountInfo, err := res.MountInfoRoot()
if err != nil {
return fmt.Errorf("failed to detect root mount info of containerDisk %v: %v", volume.Name, err)
}
nodeMountInfo, err := nodeRes.ParentMountInfoFor(mountInfo)
if err != nil {
return fmt.Errorf("failed to detect root mount point of containerDisk %v on the node: %v", volume.Name, err)
}
sourceFile, err := containerdisk.GetImage(filepath.Join(nodeRes.MountRoot(), nodeMountInfo.Root, nodeMountInfo.MountPoint), volume.ContainerDisk.Path)
if err != nil {
return fmt.Errorf("failed to find a sourceFile in containerDisk %v: %v", volume.Name, err)
}
f, err := os.Create(targetFile)
if err != nil {
return fmt.Errorf("failed to create mount point target %v: %v", targetFile, err)
}
f.Close()
if err = os.Chmod(sourceFile, 0444); err != nil {
return fmt.Errorf("failed to change permisions on %s", sourceFile)
}
log.DefaultLogger().Object(vmi).Infof("Bind mounting container disk at %s to %s", strings.TrimPrefix(sourceFile, nodeRes.MountRoot()), targetFile)
out, err := exec.Command("/usr/bin/virt-chroot", "--mount", "/proc/1/ns/mnt", "mount", "-o", "ro,bind", strings.TrimPrefix(sourceFile, nodeRes.MountRoot()), targetFile).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to bindmount containerDisk %v: %v : %v", volume.Name, string(out), err)
}
}
if verify {
res, err := m.podIsolationDetector.Detect(vmi)
if err != nil {
return fmt.Errorf("failed to detect VMI pod: %v", err)
}
imageInfo, err := isolation.GetImageInfo(containerdisk.GetDiskTargetPathFromLauncherView(i), res)
if err != nil {
return fmt.Errorf("failed to get image info: %v", err)
}
if err := containerdisk.VerifyImage(imageInfo); err != nil {
return fmt.Errorf("invalid image in containerDisk %v: %v", volume.Name, err)
}
}
}
}
return nil
}
// Legacy Unmount unmounts all container disks of a given VMI when the hold HostPath method was in use.
// This exists for backwards compatibility for VMIs running before a KubeVirt update occurs.
func (m *mounter) legacyUnmount(vmi *v1.VirtualMachineInstance) error {
mountDir := containerdisk.GetLegacyVolumeMountDirOnHost(vmi)
files, err := ioutil.ReadDir(mountDir)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to list container disk mounts: %v", err)
}
if vmi.UID != "" {
for _, file := range files {
path := filepath.Join(mountDir, file.Name())
if strings.HasSuffix(path, ".sock") {
continue
}
if mounted, err := isolation.NodeIsolationResult().IsMounted(path); err != nil {
return fmt.Errorf("failed to check mount point for containerDisk %v: %v", path, err)
} else if mounted {
out, err := exec.Command("/usr/bin/virt-chroot", "--mount", "/proc/1/ns/mnt", "umount", path).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to unmount containerDisk %v: %v : %v", path, string(out), err)
}
}
}
if err := os.RemoveAll(mountDir); err != nil {
return fmt.Errorf("failed to remove containerDisk files: %v", err)
}
}
return nil
}
// Unmount unmounts all container disks of a given VMI.
func (m *mounter) Unmount(vmi *v1.VirtualMachineInstance) error {
if vmi.UID != "" {
// this will catch unmounting a vmi's container disk when
// an old VMI is left over after a KubeVirt update
err := m.legacyUnmount(vmi)
if err != nil {
return err
}
record, err := m.getMountTargetRecord(vmi)
if err != nil {
return err
} else if record == nil {
// no entries to unmount
log.DefaultLogger().Object(vmi).Infof("No container disk mount entries found to unmount")
return nil
}
log.DefaultLogger().Object(vmi).Infof("Found container disk mount entries")
for _, entry := range record.MountTargetEntries {
path := entry.TargetFile
log.DefaultLogger().Object(vmi).Infof("Looking to see if containerdisk is mounted at path %s", path)
if mounted, err := isolation.NodeIsolationResult().IsMounted(path); err != nil {
return fmt.Errorf("failed to check mount point for containerDisk %v: %v", path, err)
} else if mounted {
log.DefaultLogger().Object(vmi).Infof("unmounting container disk at path %s", path)
out, err := exec.Command("/usr/bin/virt-chroot", "--mount", "/proc/1/ns/mnt", "umount", path).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to unmount containerDisk %v: %v : %v", path, string(out), err)
}
}
}
err = m.deleteMountTargetRecord(vmi)
if err != nil {
return err
}
}
return nil
}
func (m *mounter) ContainerDisksReady(vmi *v1.VirtualMachineInstance, notInitializedSince time.Time) (bool, error) {
for i, volume := range vmi.Spec.Volumes {
if volume.ContainerDisk != nil {
_, err := m.pathGetter(vmi, i)
if err != nil {
log.DefaultLogger().Object(vmi).Infof("containerdisk %s not yet ready", volume.Name)
if time.Now().After(notInitializedSince.Add(m.suppressWarningTimeout)) {
return false, fmt.Errorf("containerdisk %s still not ready after one minute", volume.Name)
}
return false, nil
}
}
}
log.DefaultLogger().Object(vmi).V(4).Info("all containerdisks are ready")
return true, nil
}