
CimFS layers don't need to create a new scratch VHD per image. The scratch VHDs used with CimFS are empty so we can just create one base VHD and one differencing VHD and copy it for every scratch snapshot. (Note that UVM VHDs are still unique per image because the VHD information is embedded in the UVM BCD during import) Signed-off-by: Amit Barve <ambarve@microsoft.com>
275 lines
8.7 KiB
Go
275 lines
8.7 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
/*
|
|
Copyright The containerd 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 windows
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
"github.com/Microsoft/hcsshim"
|
|
"github.com/containerd/containerd/v2/core/snapshots"
|
|
"github.com/containerd/containerd/v2/core/snapshots/storage"
|
|
"github.com/containerd/continuity/fs"
|
|
"github.com/containerd/log"
|
|
)
|
|
|
|
// windowsBaseSnapshotter is a type that implements common functionality required by both windows & cimfs
|
|
// snapshotters (sort of a base type that windows & cimfs snapshotter types derive from - however, windowsBaseSnapshotter does NOT impelement the full Snapshotter interface). Some functions
|
|
// (like Stat, Update) that are identical for both snapshotters are directly implemented in this base
|
|
// snapshotter and such functions handle database transaction creation etc. However, the functions that are
|
|
// not common don't create a transaction to allow the caller the flexibility of deciding whether to commit or
|
|
// abort the transaction.
|
|
type windowsBaseSnapshotter struct {
|
|
root string
|
|
ms *storage.MetaStore
|
|
info hcsshim.DriverInfo
|
|
}
|
|
|
|
func newBaseSnapshotter(root string) (*windowsBaseSnapshotter, error) {
|
|
if err := os.MkdirAll(root, 0700); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
|
|
return nil, err
|
|
}
|
|
|
|
return &windowsBaseSnapshotter{
|
|
root: root,
|
|
ms: ms,
|
|
info: hcsshim.DriverInfo{HomeDir: filepath.Join(root, "snapshots")},
|
|
}, nil
|
|
}
|
|
|
|
func (w *windowsBaseSnapshotter) getSnapshotDir(id string) string {
|
|
return filepath.Join(w.root, "snapshots", id)
|
|
}
|
|
|
|
func (w *windowsBaseSnapshotter) parentIDsToParentPaths(parentIDs []string) []string {
|
|
parentLayerPaths := make([]string, 0, len(parentIDs))
|
|
for _, ID := range parentIDs {
|
|
parentLayerPaths = append(parentLayerPaths, w.getSnapshotDir(ID))
|
|
}
|
|
return parentLayerPaths
|
|
}
|
|
|
|
func (w *windowsBaseSnapshotter) Stat(ctx context.Context, key string) (info snapshots.Info, err error) {
|
|
err = w.ms.WithTransaction(ctx, false, func(ctx context.Context) error {
|
|
_, info, _, err = storage.GetInfo(ctx, key)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return snapshots.Info{}, err
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func (w *windowsBaseSnapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (_ snapshots.Info, err error) {
|
|
err = w.ms.WithTransaction(ctx, true, func(ctx context.Context) error {
|
|
info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return snapshots.Info{}, err
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func (w *windowsBaseSnapshotter) Usage(ctx context.Context, key string) (usage snapshots.Usage, err error) {
|
|
var (
|
|
id string
|
|
info snapshots.Info
|
|
)
|
|
|
|
err = w.ms.WithTransaction(ctx, false, func(ctx context.Context) error {
|
|
id, info, usage, err = storage.GetInfo(ctx, key)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return snapshots.Usage{}, err
|
|
}
|
|
|
|
if info.Kind == snapshots.KindActive {
|
|
path := w.getSnapshotDir(id)
|
|
du, err := fs.DiskUsage(ctx, path)
|
|
if err != nil {
|
|
return snapshots.Usage{}, err
|
|
}
|
|
|
|
usage = snapshots.Usage(du)
|
|
}
|
|
|
|
return usage, nil
|
|
}
|
|
|
|
// Walk the committed snapshots.
|
|
func (w *windowsBaseSnapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error {
|
|
return w.ms.WithTransaction(ctx, false, func(ctx context.Context) error {
|
|
return storage.WalkInfo(ctx, fn, fs...)
|
|
})
|
|
}
|
|
|
|
// preRemove prepares for removal of a snapshot by first renaming the snapshot directory and if that succeeds
|
|
// removing the snapshot info from the database. Then the caller can decide how to remove the actual renamed
|
|
// snapshot directory. Returns the new 'ID' (i.e the directory name after rename).
|
|
func (w *windowsBaseSnapshotter) preRemove(ctx context.Context, key string) (string, error) {
|
|
var (
|
|
renamed, path, renamedID string
|
|
restore bool
|
|
)
|
|
|
|
err := w.ms.WithTransaction(ctx, true, func(ctx context.Context) error {
|
|
id, _, err := storage.Remove(ctx, key)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to remove: %w", err)
|
|
}
|
|
|
|
path = w.getSnapshotDir(id)
|
|
renamedID = "rm-" + id
|
|
renamed = w.getSnapshotDir(renamedID)
|
|
if err = os.Rename(path, renamed); err != nil && !os.IsNotExist(err) {
|
|
if !os.IsPermission(err) {
|
|
return err
|
|
}
|
|
// If permission denied, it's possible that the scratch is still mounted, an
|
|
// artifact after a hard daemon crash for example. Worth a shot to try deactivating it
|
|
// before retrying the rename.
|
|
var (
|
|
home, layerID = filepath.Split(path)
|
|
di = hcsshim.DriverInfo{
|
|
HomeDir: home,
|
|
}
|
|
)
|
|
|
|
if deactivateErr := hcsshim.DeactivateLayer(di, layerID); deactivateErr != nil {
|
|
return fmt.Errorf("failed to deactivate layer following failed rename: %s: %w", deactivateErr, err)
|
|
}
|
|
|
|
if renameErr := os.Rename(path, renamed); renameErr != nil && !os.IsNotExist(renameErr) {
|
|
return fmt.Errorf("second rename attempt following detach failed: %s: %w", renameErr, err)
|
|
}
|
|
}
|
|
|
|
restore = true
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
if restore { // failed to commit
|
|
if err1 := os.Rename(renamed, path); err1 != nil {
|
|
// May cause inconsistent data on disk
|
|
log.G(ctx).WithError(err1).WithField("path", renamed).Error("Failed to rename after failed commit")
|
|
}
|
|
}
|
|
return "", err
|
|
}
|
|
return renamedID, nil
|
|
}
|
|
|
|
// Close closes the snapshotter
|
|
func (w *windowsBaseSnapshotter) Close() error {
|
|
return w.ms.Close()
|
|
}
|
|
|
|
// This handles creating the UVMs scratch layer.
|
|
func (w *windowsBaseSnapshotter) createUVMScratchLayer(ctx context.Context, snDir string, parentLayers []string) error {
|
|
parentLen := len(parentLayers)
|
|
if parentLen == 0 {
|
|
return errors.New("no parent layers present")
|
|
}
|
|
baseLayer := parentLayers[parentLen-1]
|
|
|
|
// Make sure base layer has a UtilityVM folder.
|
|
uvmPath := filepath.Join(baseLayer, "UtilityVM")
|
|
if _, err := os.Stat(uvmPath); os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to find UtilityVM directory in base layer %q: %w", baseLayer, err)
|
|
}
|
|
|
|
templateDiffDisk := filepath.Join(uvmPath, "SystemTemplate.vhdx")
|
|
|
|
// Check if SystemTemplate disk doesn't exist for some reason (this should be made during the unpacking
|
|
// of the base layer).
|
|
if _, err := os.Stat(templateDiffDisk); os.IsNotExist(err) {
|
|
return fmt.Errorf("%q does not exist in Utility VM image", templateDiffDisk)
|
|
}
|
|
|
|
// Move the sandbox.vhdx into a nested vm folder to avoid clashing with a containers sandbox.vhdx.
|
|
vmScratchDir := filepath.Join(snDir, "vm")
|
|
if err := os.MkdirAll(vmScratchDir, 0777); err != nil {
|
|
return fmt.Errorf("failed to make `vm` directory for vm's scratch space: %w", err)
|
|
}
|
|
|
|
return copyScratchDisk(templateDiffDisk, filepath.Join(vmScratchDir, "sandbox.vhdx"))
|
|
}
|
|
|
|
func copyScratchDisk(source, dest string) error {
|
|
scratchSource, err := os.OpenFile(source, os.O_RDWR, 0700)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %s: %w", source, err)
|
|
}
|
|
defer scratchSource.Close()
|
|
|
|
f, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0700)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create sandbox.vhdx in snapshot: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if _, err := io.Copy(f, scratchSource); err != nil {
|
|
os.Remove(dest)
|
|
return fmt.Errorf("failed to copy cached %q to %q in snapshot: %w", source, dest, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getRequestedScratchSize(ctx context.Context, snapshotInfo snapshots.Info) (uint64, error) {
|
|
var sizeInBytes uint64
|
|
var err error
|
|
if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeInGBLabel]; ok {
|
|
log.G(ctx).Warnf("%q label is deprecated, please use %q instead.", rootfsSizeInGBLabel, rootfsSizeInBytesLabel)
|
|
|
|
sizeInGB, err := strconv.ParseUint(sizeGBstr, 10, 32)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInGBLabel, sizeGBstr, err)
|
|
}
|
|
sizeInBytes = sizeInGB * 1024 * 1024 * 1024
|
|
}
|
|
|
|
// Prefer the newer label in bytes over the deprecated Windows specific GB variant.
|
|
if sizeBytesStr, ok := snapshotInfo.Labels[rootfsSizeInBytesLabel]; ok {
|
|
sizeInBytes, err = strconv.ParseUint(sizeBytesStr, 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInBytesLabel, sizeBytesStr, err)
|
|
}
|
|
}
|
|
return sizeInBytes, nil
|
|
}
|