containerd/plugins/snapshots/windows/common.go
Amit Barve 994fdd74e5 Don't create new scratch VHD per image for CimFS
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>
2024-03-06 04:18:17 -08:00

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
}