Skip to content

Commit cda4bee

Browse files
committed
Use Block CIM layers for container RootFS
This commit adds the ability to parse block CIM layer mounts and to mount the merged block CIMs to be used as a rootfs for a container. Signed-off-by: Amit Barve <[email protected]>
1 parent 20c7227 commit cda4bee

File tree

5 files changed

+323
-54
lines changed

5 files changed

+323
-54
lines changed

internal/layers/helpers.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,11 @@ const (
7575
// parent layer CIMs
7676
parentLayerCimPathsFlag = "parentCimPaths="
7777

78-
LegacyMountType string = "windows-layer"
79-
CimFSMountType string = "CimFS"
78+
LegacyMountType string = "windows-layer"
79+
ForkedCIMMountType string = "CimFS"
80+
BlockCIMMountType string = "BlockCIM"
81+
BlockCIMTypeFlag string = "blockCIMType="
82+
mergedCIMPathFlag string = "mergedCIMPath="
8083
)
8184

8285
// getOptionAsArray finds if there is an option which has the given prefix and if such an

internal/layers/wcow_mount.go

Lines changed: 119 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import (
1212

1313
"github.com/pkg/errors"
1414
"github.com/sirupsen/logrus"
15+
"go.opencensus.io/trace"
1516
"golang.org/x/sys/windows"
1617

1718
"github.com/Microsoft/hcsshim/computestorage"
1819
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
1920
"github.com/Microsoft/hcsshim/internal/hcserror"
2021
"github.com/Microsoft/hcsshim/internal/log"
22+
"github.com/Microsoft/hcsshim/internal/oc"
2123
"github.com/Microsoft/hcsshim/internal/resources"
2224
"github.com/Microsoft/hcsshim/internal/uvm"
2325
"github.com/Microsoft/hcsshim/internal/uvm/scsi"
@@ -37,6 +39,11 @@ func MountWCOWLayers(ctx context.Context, containerID string, vm *uvm.UtilityVM,
3739
return mountProcessIsolatedForkedCimLayers(ctx, containerID, l)
3840
}
3941
return nil, nil, fmt.Errorf("hyperv isolated containers aren't supported with forked cim layers")
42+
case *wcowBlockCIMLayers:
43+
if vm == nil {
44+
return mountProcessIsolatedBlockCIMLayers(ctx, containerID, l)
45+
}
46+
return nil, nil, fmt.Errorf("hyperv isolated containers aren't supported with block cim layers")
4047
default:
4148
return nil, nil, fmt.Errorf("invalid layer type %T", wl)
4249
}
@@ -171,53 +178,43 @@ func mountProcessIsolatedWCIFSLayers(ctx context.Context, l *wcowWCIFSLayers) (_
171178
}, nil
172179
}
173180

174-
// wcowHostForkedCIMLayerCloser is used to cleanup forked CIM layers mounted on the host for process isolated
175-
// containers
176-
type wcowHostForkedCIMLayerCloser struct {
177-
scratchLayerData
178-
containerID string
179-
}
180-
181-
func (l *wcowHostForkedCIMLayerCloser) Release(ctx context.Context) error {
182-
mountPath, err := wclayer.GetLayerMountPath(ctx, l.scratchLayerPath)
183-
if err != nil {
184-
return err
185-
}
186-
187-
if err = computestorage.DetachOverlayFilter(ctx, mountPath, hcsschema.UnionFS); err != nil {
188-
return err
189-
}
190-
191-
if err = cimlayer.CleanupContainerMounts(l.containerID); err != nil {
192-
return err
193-
}
194-
return wclayer.DeactivateLayer(ctx, l.scratchLayerPath)
195-
}
181+
// Handles the common processing for mounting all 3 types of cimfs layers. This involves
182+
// mounting the scratch, attaching the filter and preparing the return values.
183+
// `volume` is the path to the volume at which read only layer CIMs are mounted.
184+
func mountProcessIsolatedCimLayersCommon(ctx context.Context, containerID string, volume string, s *scratchLayerData) (_ *MountedWCOWLayers, _ resources.ResourceCloser, err error) {
185+
ctx, span := oc.StartSpan(ctx, "mountProcessIsolatedCimLayersCommon")
186+
defer func() {
187+
oc.SetSpanStatus(span, err)
188+
span.End()
189+
}()
190+
span.AddAttributes(
191+
trace.StringAttribute("scratch path", s.scratchLayerPath),
192+
trace.StringAttribute("mounted CIM volume", volume))
196193

197-
func mountProcessIsolatedForkedCimLayers(ctx context.Context, containerID string, l *wcowForkedCIMLayers) (_ *MountedWCOWLayers, _ resources.ResourceCloser, err error) {
198-
if err = wclayer.ActivateLayer(ctx, l.scratchLayerPath); err != nil {
199-
return nil, nil, err
200-
}
194+
rcl := &resources.ResourceCloserList{}
201195
defer func() {
202196
if err != nil {
203-
_ = wclayer.DeactivateLayer(ctx, l.scratchLayerPath)
197+
if rErr := rcl.Release(ctx); rErr != nil {
198+
log.G(ctx).WithError(err).Warnf("mount process isolated cim layers common, undo failed with: %s", rErr)
199+
}
204200
}
205201
}()
206202

207-
mountPath, err := wclayer.GetLayerMountPath(ctx, l.scratchLayerPath)
208-
if err != nil {
203+
if err = wclayer.ActivateLayer(ctx, s.scratchLayerPath); err != nil {
209204
return nil, nil, err
210205
}
206+
rcl.AddFunc(func(uCtx context.Context) error {
207+
return wclayer.DeactivateLayer(uCtx, s.scratchLayerPath)
208+
})
211209

212-
volume, err := cimlayer.MountForkedCimLayer(ctx, l.layers[0].cimPath, containerID)
210+
mountPath, err := wclayer.GetLayerMountPath(ctx, s.scratchLayerPath)
213211
if err != nil {
214-
return nil, nil, fmt.Errorf("mount layer cim: %w", err)
212+
return nil, nil, err
215213
}
216-
defer func() {
217-
if err != nil {
218-
_ = cimlayer.UnmountCimLayer(ctx, volume)
219-
}
220-
}()
214+
log.G(ctx).WithFields(logrus.Fields{
215+
"scratch": s.scratchLayerPath,
216+
"mounted path": mountPath,
217+
}).Debug("scratch activated")
221218

222219
layerID, err := cimlayer.LayerID(volume)
223220
if err != nil {
@@ -239,22 +236,97 @@ func mountProcessIsolatedForkedCimLayers(ctx context.Context, containerID string
239236
if err = computestorage.AttachOverlayFilter(ctx, mountPath, layerData); err != nil {
240237
return nil, nil, err
241238
}
239+
rcl.AddFunc(func(uCtx context.Context) error {
240+
return computestorage.DetachOverlayFilter(uCtx, mountPath, hcsschema.UnionFS)
241+
})
242+
243+
log.G(ctx).WithField("layer data", layerData).Debug("unionFS filter attached")
244+
245+
return &MountedWCOWLayers{
246+
RootFS: mountPath,
247+
MountedLayerPaths: []MountedWCOWLayer{{
248+
LayerID: layerID,
249+
MountedPath: volume,
250+
}},
251+
}, rcl, nil
252+
}
253+
254+
func mountProcessIsolatedForkedCimLayers(ctx context.Context, containerID string, l *wcowForkedCIMLayers) (_ *MountedWCOWLayers, _ resources.ResourceCloser, err error) {
255+
ctx, span := oc.StartSpan(ctx, "mountProcessIsolatedForkedCimLayers")
256+
defer func() {
257+
oc.SetSpanStatus(span, err)
258+
span.End()
259+
}()
260+
261+
rcl := &resources.ResourceCloserList{}
262+
defer func() {
263+
if err != nil {
264+
if rErr := rcl.Release(ctx); rErr != nil {
265+
log.G(ctx).WithError(err).Warnf("mount process isolated forked CIM layers, undo failed with: %s", rErr)
266+
}
267+
}
268+
}()
269+
270+
volume, err := cimlayer.MountForkedCimLayer(ctx, l.layers[0].cimPath, containerID)
271+
if err != nil {
272+
return nil, nil, fmt.Errorf("mount forked layer cim: %w", err)
273+
}
274+
rcl.AddFunc(func(uCtx context.Context) error {
275+
return cimlayer.UnmountCimLayer(uCtx, volume)
276+
})
277+
278+
mountedLayers, closer, err := mountProcessIsolatedCimLayersCommon(ctx, containerID, volume, &l.scratchLayerData)
279+
if err != nil {
280+
return nil, nil, err
281+
}
282+
return mountedLayers, rcl.Add(closer), nil
283+
}
284+
285+
func mountProcessIsolatedBlockCIMLayers(ctx context.Context, containerID string, l *wcowBlockCIMLayers) (_ *MountedWCOWLayers, _ resources.ResourceCloser, err error) {
286+
ctx, span := oc.StartSpan(ctx, "mountProcessIsolatedBlockCIMLayers")
287+
defer func() {
288+
oc.SetSpanStatus(span, err)
289+
span.End()
290+
}()
291+
292+
var volume string
293+
294+
rcl := &resources.ResourceCloserList{}
242295
defer func() {
243296
if err != nil {
244-
_ = computestorage.DetachOverlayFilter(ctx, mountPath, hcsschema.UnionFS)
297+
if rErr := rcl.Release(ctx); rErr != nil {
298+
log.G(ctx).WithError(err).Warnf("mount process isolated forked CIM layers, undo failed with: %s", rErr)
299+
}
245300
}
246301
}()
247302

248-
return &MountedWCOWLayers{
249-
RootFS: mountPath,
250-
MountedLayerPaths: []MountedWCOWLayer{{
251-
LayerID: layerID,
252-
MountedPath: volume,
253-
}},
254-
}, &wcowHostForkedCIMLayerCloser{
255-
containerID: containerID,
256-
scratchLayerData: l.scratchLayerData,
257-
}, nil
303+
log.G(ctx).WithFields(logrus.Fields{
304+
"scratch": l.scratchLayerPath,
305+
"merged layer": l.mergedLayer,
306+
"parent layers": l.parentLayers,
307+
}).Debug("mounting process isolated block CIM layers")
308+
309+
if len(l.parentLayers) > 1 {
310+
volume, err = cimlayer.MergeMountBlockCIMLayer(ctx, l.mergedLayer, l.parentLayers, containerID)
311+
} else {
312+
volume, err = cimlayer.MountBlockCIMLayer(ctx, l.parentLayers[0], containerID)
313+
}
314+
if err != nil {
315+
return nil, nil, fmt.Errorf("mount block CIM layers: %w", err)
316+
}
317+
rcl.AddFunc(func(uCtx context.Context) error {
318+
return cimlayer.UnmountCimLayer(uCtx, volume)
319+
})
320+
321+
log.G(ctx).WithField("volume", volume).Debug("mounted blockCIM layers for process isolated container")
322+
323+
mountedLayers, layerCloser, err := mountProcessIsolatedCimLayersCommon(ctx, containerID, volume, &l.scratchLayerData)
324+
if err != nil {
325+
return nil, nil, fmt.Errorf("failed mount CIM layers common: %w", err)
326+
}
327+
rcl.Add(layerCloser)
328+
329+
return mountedLayers, rcl, nil
258330
}
259331

260332
type wcowIsolatedWCIFSLayerCloser struct {

internal/layers/wcow_parse.go

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ package layers
55

66
import (
77
"context"
8+
"encoding/json"
89
"fmt"
910
"os"
1011
"path/filepath"
12+
"strings"
1113

1214
"github.com/containerd/containerd/api/types"
1315

1416
"github.com/Microsoft/hcsshim/internal/copyfile"
1517
"github.com/Microsoft/hcsshim/internal/uvm"
1618
"github.com/Microsoft/hcsshim/internal/uvmfolder"
19+
"github.com/Microsoft/hcsshim/pkg/cimfs"
1720
)
1821

1922
// WCOW image layers is a tagging interface that all WCOW layers MUST implement. This is
@@ -67,6 +70,17 @@ type wcowForkedCIMLayers struct {
6770
layers []forkedCIMLayer
6871
}
6972

73+
// Represents CIM layers where each layer is stored in a block device or in a single file
74+
// and multiple such layer CIMs are merged before mounting them. Currently can only be
75+
// used for process isolated containers.
76+
type wcowBlockCIMLayers struct {
77+
scratchLayerData
78+
// parent layers in order [layerN (top-most), layerN-1,..layer0 (base)]
79+
parentLayers []*cimfs.BlockCIM
80+
// a merged layer is prepared by combining all parent layers
81+
mergedLayer *cimfs.BlockCIM
82+
}
83+
7084
func parseForkedCimMount(m *types.Mount) (*wcowForkedCIMLayers, error) {
7185
parentLayerPaths, err := getOptionAsArray(m, parentLayerPathsFlag)
7286
if err != nil {
@@ -94,8 +108,77 @@ func parseForkedCimMount(m *types.Mount) (*wcowForkedCIMLayers, error) {
94108
}, nil
95109
}
96110

97-
// ParseWCOWLayers parses the layers provided by containerd into the format understood by hcsshim and prepares
98-
// them for mounting.
111+
// TODO(ambarve): The code to parse a mount type should be in a separate package/module
112+
// somewhere and then should be consumed by both hcsshim & containerd from there.
113+
func parseBlockCIMMount(m *types.Mount) (*wcowBlockCIMLayers, error) {
114+
var (
115+
parentPaths []string
116+
layerType cimfs.BlockCIMType
117+
mergedCIMPath string
118+
)
119+
120+
for _, option := range m.Options {
121+
if val, ok := strings.CutPrefix(option, parentLayerCimPathsFlag); ok {
122+
err := json.Unmarshal([]byte(val), &parentPaths)
123+
if err != nil {
124+
return nil, err
125+
}
126+
} else if val, ok = strings.CutPrefix(option, BlockCIMTypeFlag); ok {
127+
if val == "device" {
128+
layerType = cimfs.BlockCIMTypeDevice
129+
} else if val == "file" {
130+
layerType = cimfs.BlockCIMTypeSingleFile
131+
} else {
132+
return nil, fmt.Errorf("invalid block CIM type `%s`", val)
133+
}
134+
} else if val, ok = strings.CutPrefix(option, mergedCIMPathFlag); ok {
135+
mergedCIMPath = val
136+
}
137+
}
138+
139+
if len(parentPaths) == 0 {
140+
return nil, fmt.Errorf("need at least 1 parent layer")
141+
}
142+
if layerType == cimfs.BlockCIMTypeNone {
143+
return nil, fmt.Errorf("BlockCIM type not provided")
144+
}
145+
if mergedCIMPath == "" && len(parentPaths) > 1 {
146+
return nil, fmt.Errorf("merged CIM path not provided")
147+
}
148+
149+
var (
150+
parentLayers []*cimfs.BlockCIM
151+
mergedLayer *cimfs.BlockCIM
152+
)
153+
154+
if len(parentPaths) > 1 {
155+
// for single parent layers merge won't be done
156+
mergedLayer = &cimfs.BlockCIM{
157+
Type: layerType,
158+
BlockPath: filepath.Dir(mergedCIMPath),
159+
CimName: filepath.Base(mergedCIMPath),
160+
}
161+
}
162+
163+
for _, p := range parentPaths {
164+
parentLayers = append(parentLayers, &cimfs.BlockCIM{
165+
Type: layerType,
166+
BlockPath: filepath.Dir(p),
167+
CimName: filepath.Base(p),
168+
})
169+
}
170+
171+
return &wcowBlockCIMLayers{
172+
scratchLayerData: scratchLayerData{
173+
scratchLayerPath: m.Source,
174+
},
175+
parentLayers: parentLayers,
176+
mergedLayer: mergedLayer,
177+
}, nil
178+
}
179+
180+
// ParseWCOWLayers parses the layers provided by containerd into the format understood by
181+
// hcsshim and prepares them for mounting.
99182
func ParseWCOWLayers(rootfs []*types.Mount, layerFolders []string) (WCOWLayers, error) {
100183
if err := validateRootfsAndLayers(rootfs, layerFolders); err != nil {
101184
return nil, err
@@ -123,8 +206,10 @@ func ParseWCOWLayers(rootfs []*types.Mount, layerFolders []string) (WCOWLayers,
123206
},
124207
layerPaths: parentLayers,
125208
}, nil
126-
case CimFSMountType:
209+
case ForkedCIMMountType:
127210
return parseForkedCimMount(m)
211+
case BlockCIMMountType:
212+
return parseBlockCIMMount(m)
128213
default:
129214
return nil, fmt.Errorf("invalid windows mount type: '%s'", m.Type)
130215
}

internal/resources/resources.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,27 @@ func ReleaseResources(ctx context.Context, r *Resources, vm *uvm.UtilityVM, all
168168
}
169169
return nil
170170
}
171+
172+
type ResourceCloserList struct {
173+
closers []ResourceCloser
174+
}
175+
176+
func (l *ResourceCloserList) Add(rOp ResourceCloser) *ResourceCloserList {
177+
l.closers = append(l.closers, rOp)
178+
return l
179+
}
180+
181+
func (l *ResourceCloserList) AddFunc(rOp ResourceCloserFunc) *ResourceCloserList {
182+
l.closers = append(l.closers, rOp)
183+
return l
184+
}
185+
186+
func (l *ResourceCloserList) Release(ctx context.Context) error {
187+
// MUST release in the reverse order
188+
for i := len(l.closers) - 1; i >= 0; i-- {
189+
if oErr := l.closers[i].Release(ctx); oErr != nil {
190+
return oErr
191+
}
192+
}
193+
return nil
194+
}

0 commit comments

Comments
 (0)