-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
merge.go
203 lines (182 loc) · 6.9 KB
/
merge.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
package snapshot
import (
"context"
"strconv"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/pkg/userns"
"github.com/containerd/containerd/snapshots"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/buildkit/util/leaseutil"
"github.com/pkg/errors"
)
// hardlinkMergeSnapshotters are the names of snapshotters that support merges implemented by
// creating "hardlink farms" where non-directory objects are hard-linked into the merged tree
// from their parent snapshots.
var hardlinkMergeSnapshotters = map[string]struct{}{
"native": {},
"overlayfs": {},
}
// overlayBasedSnapshotters are the names of snapshotter that use overlay mounts, which
// enables optimizations such as skipping the base layer when doing a hardlink merge.
var overlayBasedSnapshotters = map[string]struct{}{
"overlayfs": {},
"stargz": {},
}
type Diff struct {
Lower string
Upper string
}
type MergeSnapshotter interface {
Snapshotter
// Merge creates a snapshot whose contents are the provided diffs applied onto one
// another in the provided order, starting from scratch. The diffs are calculated
// the same way that diffs are calculated during exports, which ensures that the
// result of merging these diffs looks the same as exporting the diffs as layer
// blobs and unpacking them as an image.
//
// Each key in the provided diffs is expected to be a committed snapshot. The
// snapshot created by Merge is also committed.
//
// The size of a merged snapshot (as returned by the Usage method) depends on the merge
// implementation. Implementations using hardlinks to create merged views will take up
// less space than those that use copies, for example.
Merge(ctx context.Context, key string, diffs []Diff, opts ...snapshots.Opt) error
}
type mergeSnapshotter struct {
Snapshotter
lm leases.Manager
// Whether we should try to implement merges by hardlinking between underlying directories
tryCrossSnapshotLink bool
// Whether the optimization of preparing on top of base layers is supported (see Merge method).
skipBaseLayers bool
// Whether we should use the "user.*" namespace when writing overlay xattrs. If false,
// "trusted.*" is used instead.
userxattr bool
}
func NewMergeSnapshotter(ctx context.Context, sn Snapshotter, lm leases.Manager) MergeSnapshotter {
name := sn.Name()
_, tryCrossSnapshotLink := hardlinkMergeSnapshotters[name]
_, overlayBased := overlayBasedSnapshotters[name]
skipBaseLayers := overlayBased // default to skipping base layer for overlay-based snapshotters
var userxattr bool
if overlayBased && userns.RunningInUserNS() {
// When using an overlay-based snapshotter, if we are running rootless on a pre-5.11
// kernel, we will not have userxattr. This results in opaque xattrs not being visible
// to us and thus breaking the overlay-optimized differ.
var err error
userxattr, err = needsUserXAttr(ctx, sn, lm)
if err != nil {
bklog.G(ctx).Debugf("failed to check user xattr: %v", err)
tryCrossSnapshotLink = false
skipBaseLayers = false
} else {
tryCrossSnapshotLink = tryCrossSnapshotLink && userxattr
// Disable skipping base layers when in pre-5.11 rootless mode. Skipping the base layers
// necessitates the ability to set opaque xattrs sometimes, which only works in 5.11+
// kernels that support userxattr.
skipBaseLayers = userxattr
}
}
return &mergeSnapshotter{
Snapshotter: sn,
lm: lm,
tryCrossSnapshotLink: tryCrossSnapshotLink,
skipBaseLayers: skipBaseLayers,
userxattr: userxattr,
}
}
func (sn *mergeSnapshotter) Merge(ctx context.Context, key string, diffs []Diff, opts ...snapshots.Opt) error {
var baseKey string
if sn.skipBaseLayers {
// Overlay-based snapshotters can skip the base snapshot of the merge (if one exists) and just use it as the
// parent of the merge snapshot. Other snapshotters will start empty (with baseKey set to "").
// Find the baseKey by following the chain of diffs for as long as it follows the pattern of the current lower
// being the parent of the current upper and equal to the previous upper, i.e.:
// Diff("", A) -> Diff(A, B) -> Diff(B, C), etc.
var baseIndex int
for i, diff := range diffs {
var parentKey string
if diff.Upper != "" {
info, err := sn.Stat(ctx, diff.Upper)
if err != nil {
return err
}
parentKey = info.Parent
}
if parentKey != diff.Lower {
break
}
if diff.Lower != baseKey {
break
}
baseKey = diff.Upper
baseIndex = i + 1
}
diffs = diffs[baseIndex:]
}
ctx, done, err := leaseutil.WithLease(ctx, sn.lm, leaseutil.MakeTemporary)
if err != nil {
return errors.Wrap(err, "failed to create temporary lease for view mounts during merge")
}
defer done(context.TODO())
// Make the snapshot that will be merged into
prepareKey := identity.NewID()
if err := sn.Prepare(ctx, prepareKey, baseKey); err != nil {
return errors.Wrapf(err, "failed to prepare %q", key)
}
applyMounts, err := sn.Mounts(ctx, prepareKey)
if err != nil {
return errors.Wrapf(err, "failed to get mounts of %q", key)
}
usage, err := sn.diffApply(ctx, applyMounts, diffs...)
if err != nil {
return errors.Wrap(err, "failed to apply diffs")
}
if err := sn.Commit(ctx, key, prepareKey, withMergeUsage(usage)); err != nil {
return errors.Wrapf(err, "failed to commit %q", key)
}
return nil
}
func (sn *mergeSnapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
// If key was created by Merge, we may need to use the annotated mergeUsage key as
// the snapshotter's usage method is wrong when hardlinks are used to create the merge.
if info, err := sn.Stat(ctx, key); err != nil {
return snapshots.Usage{}, err
} else if usage, ok, err := mergeUsageOf(info); err != nil {
return snapshots.Usage{}, err
} else if ok {
return usage, nil
}
return sn.Snapshotter.Usage(ctx, key)
}
// mergeUsage{Size,Inodes}Label hold the correct usage calculations for diffApplyMerges, for which the builtin usage
// is wrong because it can't account for hardlinks made across immutable snapshots
const mergeUsageSizeLabel = "buildkit.mergeUsageSize"
const mergeUsageInodesLabel = "buildkit.mergeUsageInodes"
func withMergeUsage(usage snapshots.Usage) snapshots.Opt {
return snapshots.WithLabels(map[string]string{
mergeUsageSizeLabel: strconv.Itoa(int(usage.Size)),
mergeUsageInodesLabel: strconv.Itoa(int(usage.Inodes)),
})
}
func mergeUsageOf(info snapshots.Info) (usage snapshots.Usage, ok bool, rerr error) {
if info.Labels == nil {
return snapshots.Usage{}, false, nil
}
if str, ok := info.Labels[mergeUsageSizeLabel]; ok {
i, err := strconv.Atoi(str)
if err != nil {
return snapshots.Usage{}, false, err
}
usage.Size = int64(i)
}
if str, ok := info.Labels[mergeUsageInodesLabel]; ok {
i, err := strconv.Atoi(str)
if err != nil {
return snapshots.Usage{}, false, err
}
usage.Inodes = int64(i)
}
return usage, true, nil
}