github.com/lalkh/containerd@v1.4.3/snapshots/lcow/lcow.go (about) 1 // +build windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package lcow 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "sync" 31 "time" 32 33 winfs "github.com/Microsoft/go-winio/pkg/fs" 34 "github.com/Microsoft/hcsshim/pkg/go-runhcs" 35 "github.com/containerd/containerd/errdefs" 36 "github.com/containerd/containerd/log" 37 "github.com/containerd/containerd/mount" 38 "github.com/containerd/containerd/plugin" 39 "github.com/containerd/containerd/snapshots" 40 "github.com/containerd/containerd/snapshots/storage" 41 "github.com/containerd/continuity/fs" 42 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 43 "github.com/pkg/errors" 44 ) 45 46 func init() { 47 plugin.Register(&plugin.Registration{ 48 Type: plugin.SnapshotPlugin, 49 ID: "windows-lcow", 50 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 51 ic.Meta.Platforms = append(ic.Meta.Platforms, ocispec.Platform{ 52 OS: "linux", 53 Architecture: "amd64", 54 }) 55 return NewSnapshotter(ic.Root) 56 }, 57 }) 58 } 59 60 const ( 61 rootfsSizeLabel = "containerd.io/snapshot/io.microsoft.container.storage.rootfs.size-gb" 62 ) 63 64 type snapshotter struct { 65 root string 66 ms *storage.MetaStore 67 68 scratchLock sync.Mutex 69 } 70 71 // NewSnapshotter returns a new windows snapshotter 72 func NewSnapshotter(root string) (snapshots.Snapshotter, error) { 73 fsType, err := winfs.GetFileSystemType(root) 74 if err != nil { 75 return nil, err 76 } 77 if strings.ToLower(fsType) != "ntfs" { 78 return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root) 79 } 80 81 if err := os.MkdirAll(root, 0700); err != nil { 82 return nil, err 83 } 84 ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) 85 if err != nil { 86 return nil, err 87 } 88 89 if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { 90 return nil, err 91 } 92 93 return &snapshotter{ 94 root: root, 95 ms: ms, 96 }, nil 97 } 98 99 // Stat returns the info for an active or committed snapshot by name or 100 // key. 101 // 102 // Should be used for parent resolution, existence checks and to discern 103 // the kind of snapshot. 104 func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { 105 ctx, t, err := s.ms.TransactionContext(ctx, false) 106 if err != nil { 107 return snapshots.Info{}, err 108 } 109 defer t.Rollback() 110 111 _, info, _, err := storage.GetInfo(ctx, key) 112 return info, err 113 } 114 115 func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { 116 ctx, t, err := s.ms.TransactionContext(ctx, true) 117 if err != nil { 118 return snapshots.Info{}, err 119 } 120 defer t.Rollback() 121 122 info, err = storage.UpdateInfo(ctx, info, fieldpaths...) 123 if err != nil { 124 return snapshots.Info{}, err 125 } 126 127 if err := t.Commit(); err != nil { 128 return snapshots.Info{}, err 129 } 130 131 return info, nil 132 } 133 134 func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 135 ctx, t, err := s.ms.TransactionContext(ctx, false) 136 if err != nil { 137 return snapshots.Usage{}, err 138 } 139 id, info, usage, err := storage.GetInfo(ctx, key) 140 t.Rollback() // transaction no longer needed at this point. 141 142 if err != nil { 143 return snapshots.Usage{}, err 144 } 145 146 if info.Kind == snapshots.KindActive { 147 du, err := fs.DiskUsage(ctx, s.getSnapshotDir(id)) 148 if err != nil { 149 return snapshots.Usage{}, err 150 } 151 usage = snapshots.Usage(du) 152 } 153 154 return usage, nil 155 } 156 157 func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 158 return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) 159 } 160 161 func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 162 return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts) 163 } 164 165 // Mounts returns the mounts for the transaction identified by key. Can be 166 // called on an read-write or readonly transaction. 167 // 168 // This can be used to recover mounts after calling View or Prepare. 169 func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { 170 ctx, t, err := s.ms.TransactionContext(ctx, false) 171 if err != nil { 172 return nil, err 173 } 174 defer t.Rollback() 175 176 snapshot, err := storage.GetSnapshot(ctx, key) 177 if err != nil { 178 return nil, errors.Wrap(err, "failed to get snapshot mount") 179 } 180 return s.mounts(snapshot), nil 181 } 182 183 func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { 184 ctx, t, err := s.ms.TransactionContext(ctx, true) 185 if err != nil { 186 return err 187 } 188 defer func() { 189 if err != nil { 190 if rerr := t.Rollback(); rerr != nil { 191 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 192 } 193 } 194 }() 195 196 // grab the existing id 197 id, _, _, err := storage.GetInfo(ctx, key) 198 if err != nil { 199 return err 200 } 201 202 usage, err := fs.DiskUsage(ctx, s.getSnapshotDir(id)) 203 if err != nil { 204 return err 205 } 206 207 if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { 208 return errors.Wrap(err, "failed to commit snapshot") 209 } 210 211 return t.Commit() 212 } 213 214 // Remove abandons the transaction identified by key. All resources 215 // associated with the key will be removed. 216 func (s *snapshotter) Remove(ctx context.Context, key string) error { 217 ctx, t, err := s.ms.TransactionContext(ctx, true) 218 if err != nil { 219 return err 220 } 221 defer t.Rollback() 222 223 id, _, err := storage.Remove(ctx, key) 224 if err != nil { 225 return errors.Wrap(err, "failed to remove") 226 } 227 228 path := s.getSnapshotDir(id) 229 renamed := s.getSnapshotDir("rm-" + id) 230 if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) { 231 return err 232 } 233 234 if err := t.Commit(); err != nil { 235 if err1 := os.Rename(renamed, path); err1 != nil { 236 // May cause inconsistent data on disk 237 log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit") 238 } 239 return errors.Wrap(err, "failed to commit") 240 } 241 242 if err := os.RemoveAll(renamed); err != nil { 243 // Must be cleaned up, any "rm-*" could be removed if no active transactions 244 log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem") 245 } 246 247 return nil 248 } 249 250 // Walk the committed snapshots. 251 func (s *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { 252 ctx, t, err := s.ms.TransactionContext(ctx, false) 253 if err != nil { 254 return err 255 } 256 defer t.Rollback() 257 258 return storage.WalkInfo(ctx, fn, fs...) 259 } 260 261 // Close closes the snapshotter 262 func (s *snapshotter) Close() error { 263 return s.ms.Close() 264 } 265 266 func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount { 267 var ( 268 roFlag string 269 source string 270 parentLayerPaths []string 271 ) 272 273 if sn.Kind == snapshots.KindView { 274 roFlag = "ro" 275 } else { 276 roFlag = "rw" 277 } 278 279 if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive { 280 source = s.getSnapshotDir(sn.ID) 281 parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs) 282 } else { 283 source = s.getSnapshotDir(sn.ParentIDs[0]) 284 parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:]) 285 } 286 287 // error is not checked here, as a string array will never fail to Marshal 288 parentLayersJSON, _ := json.Marshal(parentLayerPaths) 289 parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON) 290 291 var mounts []mount.Mount 292 mounts = append(mounts, mount.Mount{ 293 Source: source, 294 Type: "lcow-layer", 295 Options: []string{ 296 roFlag, 297 parentLayersOption, 298 }, 299 }) 300 301 return mounts 302 } 303 304 func (s *snapshotter) getSnapshotDir(id string) string { 305 return filepath.Join(s.root, "snapshots", id) 306 } 307 308 func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) { 309 ctx, t, err := s.ms.TransactionContext(ctx, true) 310 if err != nil { 311 return nil, err 312 } 313 defer t.Rollback() 314 315 newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) 316 if err != nil { 317 return nil, errors.Wrap(err, "failed to create snapshot") 318 } 319 320 if kind == snapshots.KindActive { 321 log.G(ctx).Debug("createSnapshot active") 322 // Create the new snapshot dir 323 snDir := s.getSnapshotDir(newSnapshot.ID) 324 if err := os.MkdirAll(snDir, 0700); err != nil { 325 return nil, err 326 } 327 328 var snapshotInfo snapshots.Info 329 for _, o := range opts { 330 o(&snapshotInfo) 331 } 332 333 var sizeGB int 334 if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeLabel]; ok { 335 i32, err := strconv.ParseInt(sizeGBstr, 10, 32) 336 if err != nil { 337 return nil, errors.Wrapf(err, "failed to parse annotation %q=%q", rootfsSizeLabel, sizeGBstr) 338 } 339 sizeGB = int(i32) 340 } 341 342 scratchSource, err := s.openOrCreateScratch(ctx, sizeGB) 343 if err != nil { 344 return nil, err 345 } 346 defer scratchSource.Close() 347 348 // TODO: JTERRY75 - This has to be called sandbox.vhdx for the time 349 // being but it really is the scratch.vhdx Using this naming convention 350 // for now but this is not the kubernetes sandbox. 351 // 352 // Create the sandbox.vhdx for this snapshot from the cache. 353 destPath := filepath.Join(snDir, "sandbox.vhdx") 354 dest, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE, 0700) 355 if err != nil { 356 return nil, errors.Wrap(err, "failed to create sandbox.vhdx in snapshot") 357 } 358 defer dest.Close() 359 if _, err := io.Copy(dest, scratchSource); err != nil { 360 dest.Close() 361 os.Remove(destPath) 362 return nil, errors.Wrap(err, "failed to copy cached scratch.vhdx to sandbox.vhdx in snapshot") 363 } 364 } 365 366 if err := t.Commit(); err != nil { 367 return nil, errors.Wrap(err, "commit failed") 368 } 369 370 return s.mounts(newSnapshot), nil 371 } 372 373 func (s *snapshotter) openOrCreateScratch(ctx context.Context, sizeGB int) (_ *os.File, err error) { 374 // Create the scratch.vhdx cache file if it doesn't already exit. 375 s.scratchLock.Lock() 376 defer s.scratchLock.Unlock() 377 378 vhdFileName := "scratch.vhdx" 379 if sizeGB > 0 { 380 vhdFileName = fmt.Sprintf("scratch_%d.vhdx", sizeGB) 381 } 382 383 scratchFinalPath := filepath.Join(s.root, vhdFileName) 384 scratchSource, err := os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700) 385 if err != nil { 386 if !os.IsNotExist(err) { 387 return nil, errors.Wrapf(err, "failed to open vhd %s for read", vhdFileName) 388 } 389 390 log.G(ctx).Debugf("vhd %s not found, creating a new one", vhdFileName) 391 392 // Golang logic for ioutil.TempFile without the file creation 393 r := uint32(time.Now().UnixNano() + int64(os.Getpid())) 394 r = r*1664525 + 1013904223 // constants from Numerical Recipes 395 396 scratchTempName := fmt.Sprintf("scratch-%s-tmp.vhdx", strconv.Itoa(int(1e9 + r%1e9))[1:]) 397 scratchTempPath := filepath.Join(s.root, scratchTempName) 398 399 // Create the scratch 400 rhcs := runhcs.Runhcs{ 401 Debug: true, 402 Log: filepath.Join(s.root, "runhcs-scratch.log"), 403 LogFormat: runhcs.JSON, 404 Owner: "containerd", 405 } 406 407 opt := runhcs.CreateScratchOpts{ 408 SizeGB: sizeGB, 409 } 410 411 if err := rhcs.CreateScratchWithOpts(ctx, scratchTempPath, &opt); err != nil { 412 _ = os.Remove(scratchTempPath) 413 return nil, errors.Wrapf(err, "failed to create '%s' temp file", scratchTempName) 414 } 415 if err := os.Rename(scratchTempPath, scratchFinalPath); err != nil { 416 _ = os.Remove(scratchTempPath) 417 return nil, errors.Wrapf(err, "failed to rename '%s' temp file to 'scratch.vhdx'", scratchTempName) 418 } 419 scratchSource, err = os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700) 420 if err != nil { 421 _ = os.Remove(scratchFinalPath) 422 return nil, errors.Wrap(err, "failed to open scratch.vhdx for read after creation") 423 } 424 } 425 return scratchSource, nil 426 } 427 428 func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string { 429 var parentLayerPaths []string 430 for _, ID := range parentIDs { 431 parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID)) 432 } 433 return parentLayerPaths 434 }