github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/metadata/gc.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package metadata 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "strings" 24 "time" 25 26 "github.com/containerd/containerd/gc" 27 "github.com/containerd/containerd/log" 28 "github.com/pkg/errors" 29 bolt "go.etcd.io/bbolt" 30 ) 31 32 const ( 33 // ResourceUnknown specifies an unknown resource 34 ResourceUnknown gc.ResourceType = iota 35 // ResourceContent specifies a content resource 36 ResourceContent 37 // ResourceSnapshot specifies a snapshot resource 38 ResourceSnapshot 39 // ResourceContainer specifies a container resource 40 ResourceContainer 41 // ResourceTask specifies a task resource 42 ResourceTask 43 // ResourceLease specifies a lease 44 ResourceLease 45 // ResourceIngest specifies a content ingest 46 ResourceIngest 47 ) 48 49 const ( 50 resourceContentFlat = ResourceContent | 0x20 51 resourceSnapshotFlat = ResourceSnapshot | 0x20 52 ) 53 54 var ( 55 labelGCRoot = []byte("containerd.io/gc.root") 56 labelGCSnapRef = []byte("containerd.io/gc.ref.snapshot.") 57 labelGCContentRef = []byte("containerd.io/gc.ref.content") 58 labelGCExpire = []byte("containerd.io/gc.expire") 59 labelGCFlat = []byte("containerd.io/gc.flat") 60 ) 61 62 func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { 63 v1bkt := tx.Bucket(bucketKeyVersion) 64 if v1bkt == nil { 65 return nil 66 } 67 68 expThreshold := time.Now() 69 70 // iterate through each namespace 71 v1c := v1bkt.Cursor() 72 73 // cerr indicates the scan did not successfully send all 74 // the roots. The scan does not need to be cancelled but 75 // must return error at the end. 76 var cerr error 77 fn := func(n gc.Node) { 78 select { 79 case nc <- n: 80 case <-ctx.Done(): 81 cerr = ctx.Err() 82 } 83 } 84 85 for k, v := v1c.First(); k != nil; k, v = v1c.Next() { 86 if v != nil { 87 continue 88 } 89 nbkt := v1bkt.Bucket(k) 90 ns := string(k) 91 92 lbkt := nbkt.Bucket(bucketKeyObjectLeases) 93 if lbkt != nil { 94 if err := lbkt.ForEach(func(k, v []byte) error { 95 if v != nil { 96 return nil 97 } 98 libkt := lbkt.Bucket(k) 99 var flat bool 100 101 if lblbkt := libkt.Bucket(bucketKeyObjectLabels); lblbkt != nil { 102 if expV := lblbkt.Get(labelGCExpire); expV != nil { 103 exp, err := time.Parse(time.RFC3339, string(expV)) 104 if err != nil { 105 // label not used, log and continue to use lease 106 log.G(ctx).WithError(err).WithField("lease", string(k)).Infof("ignoring invalid expiration value %q", string(expV)) 107 } else if expThreshold.After(exp) { 108 // lease has expired, skip 109 return nil 110 } 111 } 112 113 if flatV := lblbkt.Get(labelGCFlat); flatV != nil { 114 flat = true 115 } 116 } 117 118 fn(gcnode(ResourceLease, ns, string(k))) 119 120 // Emit content and snapshots as roots instead of implementing 121 // in references. Since leases cannot be referenced there is 122 // no need to allow the lookup to be recursive, handling here 123 // therefore reduces the number of database seeks. 124 125 ctype := ResourceContent 126 if flat { 127 ctype = resourceContentFlat 128 } 129 130 cbkt := libkt.Bucket(bucketKeyObjectContent) 131 if cbkt != nil { 132 if err := cbkt.ForEach(func(k, v []byte) error { 133 fn(gcnode(ctype, ns, string(k))) 134 return nil 135 }); err != nil { 136 return err 137 } 138 } 139 140 stype := ResourceSnapshot 141 if flat { 142 stype = resourceSnapshotFlat 143 } 144 145 sbkt := libkt.Bucket(bucketKeyObjectSnapshots) 146 if sbkt != nil { 147 if err := sbkt.ForEach(func(sk, sv []byte) error { 148 if sv != nil { 149 return nil 150 } 151 snbkt := sbkt.Bucket(sk) 152 153 return snbkt.ForEach(func(k, v []byte) error { 154 fn(gcnode(stype, ns, fmt.Sprintf("%s/%s", sk, k))) 155 return nil 156 }) 157 }); err != nil { 158 return err 159 } 160 } 161 162 ibkt := libkt.Bucket(bucketKeyObjectIngests) 163 if ibkt != nil { 164 if err := ibkt.ForEach(func(k, v []byte) error { 165 fn(gcnode(ResourceIngest, ns, string(k))) 166 return nil 167 }); err != nil { 168 return err 169 } 170 } 171 172 return nil 173 }); err != nil { 174 return err 175 } 176 } 177 178 ibkt := nbkt.Bucket(bucketKeyObjectImages) 179 if ibkt != nil { 180 if err := ibkt.ForEach(func(k, v []byte) error { 181 if v != nil { 182 return nil 183 } 184 185 target := ibkt.Bucket(k).Bucket(bucketKeyTarget) 186 if target != nil { 187 contentKey := string(target.Get(bucketKeyDigest)) 188 fn(gcnode(ResourceContent, ns, contentKey)) 189 } 190 return sendLabelRefs(ns, ibkt.Bucket(k), fn) 191 }); err != nil { 192 return err 193 } 194 } 195 196 cbkt := nbkt.Bucket(bucketKeyObjectContent) 197 if cbkt != nil { 198 ibkt := cbkt.Bucket(bucketKeyObjectIngests) 199 if ibkt != nil { 200 if err := ibkt.ForEach(func(k, v []byte) error { 201 if v != nil { 202 return nil 203 } 204 ea, err := readExpireAt(ibkt.Bucket(k)) 205 if err != nil { 206 return err 207 } 208 if ea == nil || expThreshold.After(*ea) { 209 return nil 210 } 211 fn(gcnode(ResourceIngest, ns, string(k))) 212 return nil 213 }); err != nil { 214 return err 215 } 216 } 217 cbkt = cbkt.Bucket(bucketKeyObjectBlob) 218 if cbkt != nil { 219 if err := cbkt.ForEach(func(k, v []byte) error { 220 if v != nil { 221 return nil 222 } 223 224 if isRootRef(cbkt.Bucket(k)) { 225 fn(gcnode(ResourceContent, ns, string(k))) 226 } 227 228 return nil 229 }); err != nil { 230 return err 231 } 232 } 233 } 234 235 cbkt = nbkt.Bucket(bucketKeyObjectContainers) 236 if cbkt != nil { 237 if err := cbkt.ForEach(func(k, v []byte) error { 238 if v != nil { 239 return nil 240 } 241 242 cibkt := cbkt.Bucket(k) 243 snapshotter := string(cibkt.Get(bucketKeySnapshotter)) 244 if snapshotter != "" { 245 ss := string(cibkt.Get(bucketKeySnapshotKey)) 246 fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, ss))) 247 } 248 249 return sendLabelRefs(ns, cibkt, fn) 250 }); err != nil { 251 return err 252 } 253 } 254 255 sbkt := nbkt.Bucket(bucketKeyObjectSnapshots) 256 if sbkt != nil { 257 if err := sbkt.ForEach(func(sk, sv []byte) error { 258 if sv != nil { 259 return nil 260 } 261 snbkt := sbkt.Bucket(sk) 262 263 return snbkt.ForEach(func(k, v []byte) error { 264 if v != nil { 265 return nil 266 } 267 if isRootRef(snbkt.Bucket(k)) { 268 fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k))) 269 } 270 return nil 271 }) 272 }); err != nil { 273 return err 274 } 275 } 276 } 277 return cerr 278 } 279 280 func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)) error { 281 switch node.Type { 282 case ResourceContent: 283 bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(node.Key)) 284 if bkt == nil { 285 // Node may be created from dead edge 286 return nil 287 } 288 289 return sendLabelRefs(node.Namespace, bkt, fn) 290 case ResourceSnapshot, resourceSnapshotFlat: 291 parts := strings.SplitN(node.Key, "/", 2) 292 if len(parts) != 2 { 293 return errors.Errorf("invalid snapshot gc key %s", node.Key) 294 } 295 ss := parts[0] 296 name := parts[1] 297 298 bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectSnapshots, []byte(ss), []byte(name)) 299 if bkt == nil { 300 // Node may be created from dead edge 301 return nil 302 } 303 304 if pv := bkt.Get(bucketKeyParent); len(pv) > 0 { 305 fn(gcnode(node.Type, node.Namespace, fmt.Sprintf("%s/%s", ss, pv))) 306 } 307 308 // Do not send labeled references for flat snapshot refs 309 if node.Type == resourceSnapshotFlat { 310 return nil 311 } 312 313 return sendLabelRefs(node.Namespace, bkt, fn) 314 case ResourceIngest: 315 // Send expected value 316 bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(node.Key)) 317 if bkt == nil { 318 // Node may be created from dead edge 319 return nil 320 } 321 // Load expected 322 expected := bkt.Get(bucketKeyExpected) 323 if len(expected) > 0 { 324 fn(gcnode(ResourceContent, node.Namespace, string(expected))) 325 } 326 return nil 327 } 328 329 return nil 330 } 331 332 func scanAll(ctx context.Context, tx *bolt.Tx, fn func(ctx context.Context, n gc.Node) error) error { 333 v1bkt := tx.Bucket(bucketKeyVersion) 334 if v1bkt == nil { 335 return nil 336 } 337 338 // iterate through each namespace 339 v1c := v1bkt.Cursor() 340 341 for k, v := v1c.First(); k != nil; k, v = v1c.Next() { 342 if v != nil { 343 continue 344 } 345 nbkt := v1bkt.Bucket(k) 346 ns := string(k) 347 348 lbkt := nbkt.Bucket(bucketKeyObjectLeases) 349 if lbkt != nil { 350 if err := lbkt.ForEach(func(k, v []byte) error { 351 if v != nil { 352 return nil 353 } 354 return fn(ctx, gcnode(ResourceLease, ns, string(k))) 355 }); err != nil { 356 return err 357 } 358 } 359 360 sbkt := nbkt.Bucket(bucketKeyObjectSnapshots) 361 if sbkt != nil { 362 if err := sbkt.ForEach(func(sk, sv []byte) error { 363 if sv != nil { 364 return nil 365 } 366 snbkt := sbkt.Bucket(sk) 367 return snbkt.ForEach(func(k, v []byte) error { 368 if v != nil { 369 return nil 370 } 371 node := gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)) 372 return fn(ctx, node) 373 }) 374 }); err != nil { 375 return err 376 } 377 } 378 379 cbkt := nbkt.Bucket(bucketKeyObjectContent) 380 if cbkt != nil { 381 ibkt := cbkt.Bucket(bucketKeyObjectIngests) 382 if ibkt != nil { 383 if err := ibkt.ForEach(func(k, v []byte) error { 384 if v != nil { 385 return nil 386 } 387 node := gcnode(ResourceIngest, ns, string(k)) 388 return fn(ctx, node) 389 }); err != nil { 390 return err 391 } 392 } 393 394 cbkt = cbkt.Bucket(bucketKeyObjectBlob) 395 if cbkt != nil { 396 if err := cbkt.ForEach(func(k, v []byte) error { 397 if v != nil { 398 return nil 399 } 400 node := gcnode(ResourceContent, ns, string(k)) 401 return fn(ctx, node) 402 }); err != nil { 403 return err 404 } 405 } 406 } 407 } 408 409 return nil 410 } 411 412 func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error { 413 v1bkt := tx.Bucket(bucketKeyVersion) 414 if v1bkt == nil { 415 return nil 416 } 417 418 nsbkt := v1bkt.Bucket([]byte(node.Namespace)) 419 if nsbkt == nil { 420 return nil 421 } 422 423 switch node.Type { 424 case ResourceContent: 425 cbkt := nsbkt.Bucket(bucketKeyObjectContent) 426 if cbkt != nil { 427 cbkt = cbkt.Bucket(bucketKeyObjectBlob) 428 } 429 if cbkt != nil { 430 log.G(ctx).WithField("key", node.Key).Debug("remove content") 431 return cbkt.DeleteBucket([]byte(node.Key)) 432 } 433 case ResourceSnapshot: 434 sbkt := nsbkt.Bucket(bucketKeyObjectSnapshots) 435 if sbkt != nil { 436 parts := strings.SplitN(node.Key, "/", 2) 437 if len(parts) != 2 { 438 return errors.Errorf("invalid snapshot gc key %s", node.Key) 439 } 440 ssbkt := sbkt.Bucket([]byte(parts[0])) 441 if ssbkt != nil { 442 log.G(ctx).WithField("key", parts[1]).WithField("snapshotter", parts[0]).Debug("remove snapshot") 443 return ssbkt.DeleteBucket([]byte(parts[1])) 444 } 445 } 446 case ResourceLease: 447 lbkt := nsbkt.Bucket(bucketKeyObjectLeases) 448 if lbkt != nil { 449 return lbkt.DeleteBucket([]byte(node.Key)) 450 } 451 case ResourceIngest: 452 ibkt := nsbkt.Bucket(bucketKeyObjectContent) 453 if ibkt != nil { 454 ibkt = ibkt.Bucket(bucketKeyObjectIngests) 455 } 456 if ibkt != nil { 457 log.G(ctx).WithField("ref", node.Key).Debug("remove ingest") 458 return ibkt.DeleteBucket([]byte(node.Key)) 459 } 460 } 461 462 return nil 463 } 464 465 // sendLabelRefs sends all snapshot and content references referred to by the labels in the bkt 466 func sendLabelRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error { 467 lbkt := bkt.Bucket(bucketKeyObjectLabels) 468 if lbkt != nil { 469 lc := lbkt.Cursor() 470 471 labelRef := string(labelGCContentRef) 472 for k, v := lc.Seek(labelGCContentRef); k != nil && strings.HasPrefix(string(k), labelRef); k, v = lc.Next() { 473 if ks := string(k); ks != labelRef { 474 // Allow reference naming separated by . or /, ignore names 475 if ks[len(labelRef)] != '.' && ks[len(labelRef)] != '/' { 476 continue 477 } 478 } 479 480 fn(gcnode(ResourceContent, ns, string(v))) 481 } 482 483 for k, v := lc.Seek(labelGCSnapRef); k != nil && strings.HasPrefix(string(k), string(labelGCSnapRef)); k, v = lc.Next() { 484 snapshotter := k[len(labelGCSnapRef):] 485 if i := bytes.IndexByte(snapshotter, '/'); i >= 0 { 486 snapshotter = snapshotter[:i] 487 } 488 fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v))) 489 } 490 491 } 492 return nil 493 } 494 495 func isRootRef(bkt *bolt.Bucket) bool { 496 lbkt := bkt.Bucket(bucketKeyObjectLabels) 497 if lbkt != nil { 498 rv := lbkt.Get(labelGCRoot) 499 if rv != nil { 500 // TODO: interpret rv as a timestamp and skip if expired 501 return true 502 } 503 } 504 return false 505 } 506 507 func gcnode(t gc.ResourceType, ns, key string) gc.Node { 508 return gc.Node{ 509 Type: t, 510 Namespace: ns, 511 Key: key, 512 } 513 }