github.com/demonoid81/containerd@v1.3.4/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 type snapshotter struct { 61 root string 62 ms *storage.MetaStore 63 64 scratchLock sync.Mutex 65 } 66 67 // NewSnapshotter returns a new windows snapshotter 68 func NewSnapshotter(root string) (snapshots.Snapshotter, error) { 69 fsType, err := winfs.GetFileSystemType(root) 70 if err != nil { 71 return nil, err 72 } 73 if strings.ToLower(fsType) != "ntfs" { 74 return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root) 75 } 76 77 if err := os.MkdirAll(root, 0700); err != nil { 78 return nil, err 79 } 80 ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) 81 if err != nil { 82 return nil, err 83 } 84 85 if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { 86 return nil, err 87 } 88 89 return &snapshotter{ 90 root: root, 91 ms: ms, 92 }, nil 93 } 94 95 // Stat returns the info for an active or committed snapshot by name or 96 // key. 97 // 98 // Should be used for parent resolution, existence checks and to discern 99 // the kind of snapshot. 100 func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { 101 log.G(ctx).Debug("Starting Stat") 102 ctx, t, err := s.ms.TransactionContext(ctx, false) 103 if err != nil { 104 return snapshots.Info{}, err 105 } 106 defer t.Rollback() 107 108 _, info, _, err := storage.GetInfo(ctx, key) 109 return info, err 110 } 111 112 func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { 113 log.G(ctx).Debug("Starting Update") 114 ctx, t, err := s.ms.TransactionContext(ctx, true) 115 if err != nil { 116 return snapshots.Info{}, err 117 } 118 defer t.Rollback() 119 120 info, err = storage.UpdateInfo(ctx, info, fieldpaths...) 121 if err != nil { 122 return snapshots.Info{}, err 123 } 124 125 if err := t.Commit(); err != nil { 126 return snapshots.Info{}, err 127 } 128 129 return info, nil 130 } 131 132 func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 133 log.G(ctx).Debug("Starting Usage") 134 ctx, t, err := s.ms.TransactionContext(ctx, false) 135 if err != nil { 136 return snapshots.Usage{}, err 137 } 138 defer t.Rollback() 139 140 _, info, usage, err := storage.GetInfo(ctx, key) 141 if err != nil { 142 return snapshots.Usage{}, err 143 } 144 145 if info.Kind == snapshots.KindActive { 146 du := fs.Usage{ 147 Size: 0, 148 } 149 usage = snapshots.Usage(du) 150 } 151 152 return usage, nil 153 } 154 155 func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 156 log.G(ctx).Debug("Starting Prepare") 157 return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) 158 } 159 160 func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 161 log.G(ctx).Debug("Starting View") 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 log.G(ctx).Debug("Starting Mounts") 171 ctx, t, err := s.ms.TransactionContext(ctx, false) 172 if err != nil { 173 return nil, err 174 } 175 defer t.Rollback() 176 177 snapshot, err := storage.GetSnapshot(ctx, key) 178 if err != nil { 179 return nil, errors.Wrap(err, "failed to get snapshot mount") 180 } 181 return s.mounts(snapshot), nil 182 } 183 184 func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { 185 log.G(ctx).Debug("Starting Commit") 186 ctx, t, err := s.ms.TransactionContext(ctx, true) 187 if err != nil { 188 return err 189 } 190 defer t.Rollback() 191 192 usage := fs.Usage{ 193 Size: 0, 194 } 195 196 if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { 197 return errors.Wrap(err, "failed to commit snapshot") 198 } 199 200 if err := t.Commit(); err != nil { 201 return err 202 } 203 return nil 204 } 205 206 // Remove abandons the transaction identified by key. All resources 207 // associated with the key will be removed. 208 func (s *snapshotter) Remove(ctx context.Context, key string) error { 209 log.G(ctx).Debug("Starting Remove") 210 ctx, t, err := s.ms.TransactionContext(ctx, true) 211 if err != nil { 212 return err 213 } 214 defer t.Rollback() 215 216 id, _, err := storage.Remove(ctx, key) 217 if err != nil { 218 return errors.Wrap(err, "failed to remove") 219 } 220 221 path := s.getSnapshotDir(id) 222 renamed := s.getSnapshotDir("rm-" + id) 223 if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) { 224 return err 225 } 226 227 if err := t.Commit(); err != nil { 228 if err1 := os.Rename(renamed, path); err1 != nil { 229 // May cause inconsistent data on disk 230 log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit") 231 } 232 return errors.Wrap(err, "failed to commit") 233 } 234 235 if err := os.RemoveAll(renamed); err != nil { 236 // Must be cleaned up, any "rm-*" could be removed if no active transactions 237 log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem") 238 } 239 240 return nil 241 } 242 243 // Walk the committed snapshots. 244 func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error { 245 log.G(ctx).Debug("Starting Walk") 246 ctx, t, err := s.ms.TransactionContext(ctx, false) 247 if err != nil { 248 return err 249 } 250 defer t.Rollback() 251 252 return storage.WalkInfo(ctx, fn) 253 } 254 255 // Close closes the snapshotter 256 func (s *snapshotter) Close() error { 257 return s.ms.Close() 258 } 259 260 func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount { 261 var ( 262 roFlag string 263 source string 264 parentLayerPaths []string 265 ) 266 267 if sn.Kind == snapshots.KindView { 268 roFlag = "ro" 269 } else { 270 roFlag = "rw" 271 } 272 273 if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive { 274 source = s.getSnapshotDir(sn.ID) 275 parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs) 276 } else { 277 source = s.getSnapshotDir(sn.ParentIDs[0]) 278 parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:]) 279 } 280 281 // error is not checked here, as a string array will never fail to Marshal 282 parentLayersJSON, _ := json.Marshal(parentLayerPaths) 283 parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON) 284 285 var mounts []mount.Mount 286 mounts = append(mounts, mount.Mount{ 287 Source: source, 288 Type: "lcow-layer", 289 Options: []string{ 290 roFlag, 291 parentLayersOption, 292 }, 293 }) 294 295 return mounts 296 } 297 298 func (s *snapshotter) getSnapshotDir(id string) string { 299 return filepath.Join(s.root, "snapshots", id) 300 } 301 302 func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) { 303 ctx, t, err := s.ms.TransactionContext(ctx, true) 304 if err != nil { 305 return nil, err 306 } 307 defer t.Rollback() 308 309 newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) 310 if err != nil { 311 return nil, errors.Wrap(err, "failed to create snapshot") 312 } 313 314 if kind == snapshots.KindActive { 315 log.G(ctx).Debug("createSnapshot active") 316 // Create the new snapshot dir 317 snDir := s.getSnapshotDir(newSnapshot.ID) 318 if err := os.MkdirAll(snDir, 0700); err != nil { 319 return nil, err 320 } 321 322 scratchSource, err := s.openOrCreateScratch(ctx) 323 if err != nil { 324 return nil, err 325 } 326 defer scratchSource.Close() 327 328 // TODO: JTERRY75 - This has to be called sandbox.vhdx for the time 329 // being but it really is the scratch.vhdx Using this naming convention 330 // for now but this is not the kubernetes sandbox. 331 // 332 // Create the sandbox.vhdx for this snapshot from the cache. 333 destPath := filepath.Join(snDir, "sandbox.vhdx") 334 dest, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE, 0700) 335 if err != nil { 336 return nil, errors.Wrap(err, "failed to create sandbox.vhdx in snapshot") 337 } 338 defer dest.Close() 339 if _, err := io.Copy(dest, scratchSource); err != nil { 340 dest.Close() 341 os.Remove(destPath) 342 return nil, errors.Wrap(err, "failed to copy cached scratch.vhdx to sandbox.vhdx in snapshot") 343 } 344 } 345 346 if err := t.Commit(); err != nil { 347 return nil, errors.Wrap(err, "commit failed") 348 } 349 350 return s.mounts(newSnapshot), nil 351 } 352 353 func (s *snapshotter) openOrCreateScratch(ctx context.Context) (_ *os.File, err error) { 354 // Create the scratch.vhdx cache file if it doesn't already exit. 355 s.scratchLock.Lock() 356 defer s.scratchLock.Unlock() 357 358 scratchFinalPath := filepath.Join(s.root, "scratch.vhdx") 359 scratchSource, err := os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700) 360 if err != nil { 361 if !os.IsNotExist(err) { 362 return nil, errors.Wrap(err, "failed to open scratch.vhdx for read") 363 } 364 365 log.G(ctx).Debug("scratch.vhdx not found, creating a new one") 366 367 // Golang logic for ioutil.TempFile without the file creation 368 r := uint32(time.Now().UnixNano() + int64(os.Getpid())) 369 r = r*1664525 + 1013904223 // constants from Numerical Recipes 370 371 scratchTempName := fmt.Sprintf("scratch-%s-tmp.vhdx", strconv.Itoa(int(1e9 + r%1e9))[1:]) 372 scratchTempPath := filepath.Join(s.root, scratchTempName) 373 374 // Create the scratch 375 rhcs := runhcs.Runhcs{ 376 Debug: true, 377 Log: filepath.Join(s.root, "runhcs-scratch.log"), 378 LogFormat: runhcs.JSON, 379 Owner: "containerd", 380 } 381 if err := rhcs.CreateScratch(ctx, scratchTempPath); err != nil { 382 _ = os.Remove(scratchTempPath) 383 return nil, errors.Wrapf(err, "failed to create '%s' temp file", scratchTempName) 384 } 385 if err := os.Rename(scratchTempPath, scratchFinalPath); err != nil { 386 _ = os.Remove(scratchTempPath) 387 return nil, errors.Wrapf(err, "failed to rename '%s' temp file to 'scratch.vhdx'", scratchTempName) 388 } 389 scratchSource, err = os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700) 390 if err != nil { 391 _ = os.Remove(scratchFinalPath) 392 return nil, errors.Wrap(err, "failed to open scratch.vhdx for read after creation") 393 } 394 } 395 return scratchSource, nil 396 } 397 398 func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string { 399 var parentLayerPaths []string 400 for _, ID := range parentIDs { 401 parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID)) 402 } 403 return parentLayerPaths 404 }