github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/casext/gc_test.go (about) 1 package casext 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "testing" 13 14 "github.com/opencontainers/go-digest" 15 imeta "github.com/opencontainers/image-spec/specs-go" 16 ispec "github.com/opencontainers/image-spec/specs-go/v1" 17 "github.com/opencontainers/umoci/oci/cas/dir" 18 ) 19 20 func TestGCWithEmptyIndex(t *testing.T) { 21 ctx := context.Background() 22 23 root, err := ioutil.TempDir("", "umoci-TestEngineReference") 24 if err != nil { 25 t.Fatal(err) 26 } 27 defer os.RemoveAll(root) 28 29 image := filepath.Join(root, "image") 30 if err := dir.Create(image); err != nil { 31 t.Fatalf("unexpected error creating image: %+v", err) 32 } 33 34 engine, err := dir.Open(image) 35 if err != nil { 36 t.Fatalf("unexpected error opening image: %+v", err) 37 } 38 engineExt := NewEngine(engine) 39 defer engine.Close() 40 41 // creates an empty index.json and several orphan blobs which should be pruned 42 descMap, err := fakeSetupEngine(t, engineExt) 43 if err != nil { 44 t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err) 45 } 46 if descMap == nil { 47 t.Fatalf("empty descMap") 48 } 49 50 b, err := engine.ListBlobs(ctx) 51 if err != nil { 52 t.Fatalf("unable to list blobs: %+v", err) 53 } 54 if len(b) == 0 { 55 t.Fatalf("expected non-empty blob list before GC") 56 } 57 58 err = engineExt.GC(ctx) 59 if err != nil { 60 t.Fatalf("GC failed: %+v", err) 61 } 62 63 b, err = engine.ListBlobs(ctx) 64 if err != nil { 65 t.Fatalf("unable to list blobs: %+v", err) 66 } 67 if len(b) != 0 { 68 t.Fatalf("expected empty blob list after GC: %#v", b) 69 } 70 } 71 72 func TestGCWithNonEmptyIndex(t *testing.T) { 73 ctx := context.Background() 74 75 root, err := ioutil.TempDir("", "umoci-TestEngineReference") 76 if err != nil { 77 t.Fatal(err) 78 } 79 defer os.RemoveAll(root) 80 81 image := filepath.Join(root, "image") 82 if err := dir.Create(image); err != nil { 83 t.Fatalf("unexpected error creating image: %+v", err) 84 } 85 86 engine, err := dir.Open(image) 87 if err != nil { 88 t.Fatalf("unexpected error opening image: %+v", err) 89 } 90 engineExt := NewEngine(engine) 91 defer engine.Close() 92 93 // creates an empty index.json and several orphan blobs which should be pruned 94 descMap, err := fakeSetupEngine(t, engineExt) 95 if err != nil { 96 t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err) 97 } 98 if descMap == nil { 99 t.Fatalf("empty descMap") 100 } 101 102 b, err := engine.ListBlobs(ctx) 103 if err != nil { 104 t.Fatalf("unable to list blobs: %+v", err) 105 } 106 if len(b) == 0 { 107 t.Fatalf("expected non-empty blob list before GC") 108 } 109 110 // build a blob, manifest, index that will survive GC 111 content := "this is a test blob" 112 br := strings.NewReader(content) 113 digest, size, err := engine.PutBlob(ctx, br) 114 if err != nil { 115 t.Fatalf("error writing blob: %+v", err) 116 } 117 if size != int64(len(content)) { 118 t.Fatalf("partially written blob") 119 } 120 121 m := ispec.Manifest{ 122 Versioned: imeta.Versioned{ 123 SchemaVersion: 2, 124 }, 125 MediaType: ispec.MediaTypeImageManifest, 126 Config: ispec.Descriptor{ 127 MediaType: ispec.MediaTypeImageLayer, 128 Digest: digest, 129 Size: size, 130 }, 131 Layers: []ispec.Descriptor{ 132 { 133 MediaType: ispec.MediaTypeImageLayer, 134 Digest: digest, 135 Size: size, 136 }, 137 }, 138 } 139 data, err := json.Marshal(&m) 140 if err != nil { 141 t.Fatalf("error marshaling json: %+v", err) 142 } 143 mr := bytes.NewReader(data) 144 digest, size, err = engine.PutBlob(ctx, mr) 145 if err != nil { 146 t.Fatalf("error writing blob: %+v", err) 147 } 148 if size != int64(len(data)) { 149 t.Fatalf("partially written blob") 150 } 151 152 idx := ispec.Index{ 153 Versioned: imeta.Versioned{ 154 SchemaVersion: 2, 155 }, 156 MediaType: ispec.MediaTypeImageIndex, 157 Manifests: []ispec.Descriptor{ 158 { 159 MediaType: ispec.MediaTypeImageManifest, 160 Digest: digest, 161 Size: size, 162 }, 163 }, 164 } 165 if err := engine.PutIndex(ctx, idx); err != nil { 166 t.Fatalf("error writing index: %+v", err) 167 } 168 169 b, err = engine.ListBlobs(ctx) 170 if err != nil { 171 t.Fatalf("unable to list blobs: %+v", err) 172 } 173 if len(b) <= 2 { 174 t.Fatalf("expected >2 blob list before GC: %#v", b) 175 } 176 177 err = engineExt.GC(ctx) 178 if err != nil { 179 t.Fatalf("GC failed: %+v", err) 180 } 181 182 b, err = engine.ListBlobs(ctx) 183 if err != nil { 184 t.Fatalf("unable to list blobs: %+v", err) 185 } 186 if len(b) != 2 { 187 t.Fatalf("expected two-entry blob list after GC: %#v", b) 188 } 189 } 190 191 func gcOkFunc(t *testing.T, expectedDigest digest.Digest, unexpectedDigest digest.Digest) GCPolicy { 192 return func(ctx context.Context, digest digest.Digest) (bool, error) { 193 if digest == "" || digest == unexpectedDigest { 194 t.Errorf("got incorrect digest to gc policy callback: unexpected %v", digest) 195 } 196 if digest != expectedDigest { 197 t.Errorf("got incorrect digest to gc policy callback: expected %v, got %v", expectedDigest, digest) 198 } 199 return true, nil 200 } 201 } 202 203 func gcSkipFunc(t *testing.T, expectedDigest digest.Digest) GCPolicy { 204 return func(ctx context.Context, digest digest.Digest) (bool, error) { 205 if digest != expectedDigest { 206 t.Errorf("got incorrect digest to gc policy callback: expected %v, got %v", expectedDigest, digest) 207 } 208 return false, nil 209 } 210 } 211 212 func errFunc(ctx context.Context, digest digest.Digest) (bool, error) { 213 return false, fmt.Errorf("err policy") 214 } 215 216 func TestGCWithPolicy(t *testing.T) { 217 ctx := context.Background() 218 219 root, err := ioutil.TempDir("", "umoci-TestEngineReference") 220 if err != nil { 221 t.Fatal(err) 222 } 223 defer os.RemoveAll(root) 224 225 image := filepath.Join(root, "image") 226 if err := dir.Create(image); err != nil { 227 t.Fatalf("unexpected error creating image: %+v", err) 228 } 229 230 engine, err := dir.Open(image) 231 if err != nil { 232 t.Fatalf("unexpected error opening image: %+v", err) 233 } 234 engineExt := NewEngine(engine) 235 defer engine.Close() 236 237 // build a orphan blob that should be GC'ed 238 content := "this is a orphan blob" 239 br := strings.NewReader(content) 240 odigest, size, err := engine.PutBlob(ctx, br) 241 if err != nil { 242 t.Fatalf("error writing blob: %+v", err) 243 } 244 if size != int64(len(content)) { 245 t.Fatalf("partially written blob") 246 } 247 248 // build a blob, manifest, index that will survive GC 249 content = "this is a test blob" 250 br = strings.NewReader(content) 251 digest, size, err := engine.PutBlob(ctx, br) 252 if err != nil { 253 t.Fatalf("error writing blob: %+v", err) 254 } 255 if size != int64(len(content)) { 256 t.Fatalf("partially written blob") 257 } 258 259 digest, size, err = engineExt.PutBlobJSON(ctx, 260 ispec.Manifest{ 261 Versioned: imeta.Versioned{ 262 SchemaVersion: 2, 263 }, 264 MediaType: ispec.MediaTypeImageManifest, 265 Config: ispec.Descriptor{ 266 MediaType: ispec.MediaTypeImageLayer, 267 Digest: digest, 268 Size: size, 269 }, 270 Layers: []ispec.Descriptor{ 271 { 272 MediaType: ispec.MediaTypeImageLayer, 273 Digest: digest, 274 Size: size, 275 }, 276 }, 277 }) 278 if err != nil { 279 t.Fatalf("error writing blob: %+v", err) 280 } 281 282 idx := ispec.Index{ 283 Versioned: imeta.Versioned{ 284 SchemaVersion: 2, 285 }, 286 MediaType: ispec.MediaTypeImageIndex, 287 Manifests: []ispec.Descriptor{ 288 { 289 MediaType: ispec.MediaTypeImageManifest, 290 Digest: digest, 291 Size: size, 292 }, 293 }, 294 } 295 if err := engine.PutIndex(ctx, idx); err != nil { 296 t.Fatalf("error writing index: %+v", err) 297 } 298 299 err = engineExt.GC(ctx, errFunc) 300 // expect this to fail 301 if err == nil { 302 t.Fatalf("GC failed: %+v", err) 303 } 304 305 err = engineExt.GC(ctx, gcSkipFunc(t, odigest)) 306 // expect this to succeed but not perform GC 307 if err != nil { 308 t.Fatalf("GC failed: %+v", err) 309 } 310 b, err := engine.ListBlobs(ctx) 311 if err != nil { 312 t.Fatalf("unable to list blobs: %+v", err) 313 } 314 if len(b) != 3 { 315 t.Fatalf("expected all entries in blob list after skip GC policy: %#v", b) 316 } 317 318 err = engineExt.GC(ctx, gcOkFunc(t, odigest, digest)) 319 // expect this to succeed 320 if err != nil { 321 t.Fatalf("GC failed: %+v", err) 322 } 323 324 b, err = engine.ListBlobs(ctx) 325 if err != nil { 326 t.Fatalf("unable to list blobs: %+v", err) 327 } 328 if len(b) != 2 { 329 t.Fatalf("expected blob list with two entries after GC: %#v", b) 330 } 331 }