github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/cas/dir/dir_cas_test.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 SUSE LLC 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package dir 19 20 import ( 21 "bytes" 22 "context" 23 "io" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "testing" 28 29 "github.com/opencontainers/umoci/oci/cas" 30 "github.com/opencontainers/umoci/pkg/testutils" 31 "github.com/pkg/errors" 32 ) 33 34 // NOTE: These tests aren't really testing OCI-style manifests. It's all just 35 // example structures to make sure that the CAS acts properly. 36 37 func TestCreateLayout(t *testing.T) { 38 ctx := context.Background() 39 40 root, err := ioutil.TempDir("", "umoci-TestCreateLayout") 41 if err != nil { 42 t.Fatal(err) 43 } 44 defer os.RemoveAll(root) 45 46 image := filepath.Join(root, "image") 47 if err := Create(image); err != nil { 48 t.Fatalf("unexpected error creating image: %+v", err) 49 } 50 51 engine, err := Open(image) 52 if err != nil { 53 t.Fatalf("unexpected error opening image: %+v", err) 54 } 55 defer engine.Close() 56 57 // We should have an empty index and no blobs. 58 if index, err := engine.GetIndex(ctx); err != nil { 59 t.Errorf("unexpected error getting top-level index: %+v", err) 60 } else if len(index.Manifests) > 0 { 61 t.Errorf("got manifests in top-level index in a newly created image: %v", index.Manifests) 62 } 63 if blobs, err := engine.ListBlobs(ctx); err != nil { 64 t.Errorf("unexpected error getting list of blobs: %+v", err) 65 } else if len(blobs) > 0 { 66 t.Errorf("got blobs in a newly created image: %v", blobs) 67 } 68 69 // We should get an error if we try to create a new image atop an old one. 70 if err := Create(image); err == nil { 71 t.Errorf("expected to get a cowardly no-clobber error!") 72 } 73 } 74 75 func TestEngineBlob(t *testing.T) { 76 ctx := context.Background() 77 78 root, err := ioutil.TempDir("", "umoci-TestEngineBlob") 79 if err != nil { 80 t.Fatal(err) 81 } 82 defer os.RemoveAll(root) 83 84 image := filepath.Join(root, "image") 85 if err := Create(image); err != nil { 86 t.Fatalf("unexpected error creating image: %+v", err) 87 } 88 89 engine, err := Open(image) 90 if err != nil { 91 t.Fatalf("unexpected error opening image: %+v", err) 92 } 93 defer engine.Close() 94 95 for _, test := range []struct { 96 bytes []byte 97 }{ 98 {[]byte("")}, 99 {[]byte("some blob")}, 100 {[]byte("another blob")}, 101 } { 102 digester := cas.BlobAlgorithm.Digester() 103 if _, err := io.Copy(digester.Hash(), bytes.NewReader(test.bytes)); err != nil { 104 t.Fatalf("could not hash bytes: %+v", err) 105 } 106 expectedDigest := digester.Digest() 107 108 digest, size, err := engine.PutBlob(ctx, bytes.NewReader(test.bytes)) 109 if err != nil { 110 t.Errorf("PutBlob: unexpected error: %+v", err) 111 } 112 113 if digest != expectedDigest { 114 t.Errorf("PutBlob: digest doesn't match: expected=%s got=%s", expectedDigest, digest) 115 } 116 if size != int64(len(test.bytes)) { 117 t.Errorf("PutBlob: length doesn't match: expected=%d got=%d", len(test.bytes), size) 118 } 119 120 blobReader, err := engine.GetBlob(ctx, digest) 121 if err != nil { 122 t.Errorf("GetBlob: unexpected error: %+v", err) 123 } 124 defer blobReader.Close() 125 126 gotBytes, err := ioutil.ReadAll(blobReader) 127 if err != nil { 128 t.Errorf("GetBlob: failed to ReadAll: %+v", err) 129 } 130 if !bytes.Equal(test.bytes, gotBytes) { 131 t.Errorf("GetBlob: bytes did not match: expected=%s got=%s", string(test.bytes), string(gotBytes)) 132 } 133 134 if err := engine.DeleteBlob(ctx, digest); err != nil { 135 t.Errorf("DeleteBlob: unexpected error: %+v", err) 136 } 137 138 if br, err := engine.GetBlob(ctx, digest); !os.IsNotExist(errors.Cause(err)) { 139 if err == nil { 140 br.Close() 141 t.Errorf("GetBlob: still got blob contents after DeleteBlob!") 142 } else { 143 t.Errorf("GetBlob: unexpected error: %+v", err) 144 } 145 } 146 147 // DeleteBlob is idempotent. It shouldn't cause an error. 148 if err := engine.DeleteBlob(ctx, digest); err != nil { 149 t.Errorf("DeleteBlob: unexpected error on double-delete: %+v", err) 150 } 151 } 152 153 // Should be no blobs left. 154 if blobs, err := engine.ListBlobs(ctx); err != nil { 155 t.Errorf("unexpected error getting list of blobs: %+v", err) 156 } else if len(blobs) > 0 { 157 t.Errorf("got blobs in a clean image: %v", blobs) 158 } 159 } 160 161 func TestEngineValidate(t *testing.T) { 162 root, err := ioutil.TempDir("", "umoci-TestEngineValidate") 163 if err != nil { 164 t.Fatal(err) 165 } 166 defer os.RemoveAll(root) 167 168 var engine cas.Engine 169 var image string 170 171 // Empty directory. 172 image, err = ioutil.TempDir(root, "image") 173 if err != nil { 174 t.Fatal(err) 175 } 176 engine, err = Open(image) 177 if err == nil { 178 t.Errorf("expected to get an error") 179 engine.Close() 180 } 181 182 // Invalid oci-layout. 183 image, err = ioutil.TempDir(root, "image") 184 if err != nil { 185 t.Fatal(err) 186 } 187 if err := ioutil.WriteFile(filepath.Join(image, layoutFile), []byte("invalid JSON"), 0644); err != nil { 188 t.Fatal(err) 189 } 190 engine, err = Open(image) 191 if err == nil { 192 t.Errorf("expected to get an error") 193 engine.Close() 194 } 195 196 // Invalid oci-layout. 197 image, err = ioutil.TempDir(root, "image") 198 if err != nil { 199 t.Fatal(err) 200 } 201 if err := ioutil.WriteFile(filepath.Join(image, layoutFile), []byte("{}"), 0644); err != nil { 202 t.Fatal(err) 203 } 204 engine, err = Open(image) 205 if err == nil { 206 t.Errorf("expected to get an error") 207 engine.Close() 208 } 209 210 // Missing blobdir. 211 image, err = ioutil.TempDir(root, "image") 212 if err != nil { 213 t.Fatal(err) 214 } 215 if err := os.Remove(image); err != nil { 216 t.Fatal(err) 217 } 218 if err := Create(image); err != nil { 219 t.Fatalf("unexpected error creating image: %+v", err) 220 } 221 if err := os.RemoveAll(filepath.Join(image, blobDirectory)); err != nil { 222 t.Fatalf("unexpected error deleting blobdir: %+v", err) 223 } 224 engine, err = Open(image) 225 if err == nil { 226 t.Errorf("expected to get an error") 227 engine.Close() 228 } 229 230 // blobdir is not a directory. 231 image, err = ioutil.TempDir(root, "image") 232 if err != nil { 233 t.Fatal(err) 234 } 235 if err := os.Remove(image); err != nil { 236 t.Fatal(err) 237 } 238 if err := Create(image); err != nil { 239 t.Fatalf("unexpected error creating image: %+v", err) 240 } 241 if err := os.RemoveAll(filepath.Join(image, blobDirectory)); err != nil { 242 t.Fatalf("unexpected error deleting blobdir: %+v", err) 243 } 244 if err := ioutil.WriteFile(filepath.Join(image, blobDirectory), []byte(""), 0755); err != nil { 245 t.Fatal(err) 246 } 247 engine, err = Open(image) 248 if err == nil { 249 t.Errorf("expected to get an error") 250 engine.Close() 251 } 252 253 // Missing index.json. 254 image, err = ioutil.TempDir(root, "image") 255 if err != nil { 256 t.Fatal(err) 257 } 258 if err := os.Remove(image); err != nil { 259 t.Fatal(err) 260 } 261 if err := Create(image); err != nil { 262 t.Fatalf("unexpected error creating image: %+v", err) 263 } 264 if err := os.RemoveAll(filepath.Join(image, indexFile)); err != nil { 265 t.Fatalf("unexpected error deleting index: %+v", err) 266 } 267 engine, err = Open(image) 268 if err == nil { 269 t.Errorf("expected to get an error") 270 engine.Close() 271 } 272 273 // index is not a valid file. 274 image, err = ioutil.TempDir(root, "image") 275 if err != nil { 276 t.Fatal(err) 277 } 278 if err := os.Remove(image); err != nil { 279 t.Fatal(err) 280 } 281 if err := Create(image); err != nil { 282 t.Fatalf("unexpected error creating image: %+v", err) 283 } 284 if err := os.RemoveAll(filepath.Join(image, indexFile)); err != nil { 285 t.Fatalf("unexpected error deleting index: %+v", err) 286 } 287 if err := os.Mkdir(filepath.Join(image, indexFile), 0755); err != nil { 288 t.Fatal(err) 289 } 290 engine, err = Open(image) 291 if err == nil { 292 t.Errorf("expected to get an error") 293 engine.Close() 294 } 295 296 // No such directory. 297 image = filepath.Join(root, "non-exist") 298 engine, err = Open(image) 299 if err == nil { 300 t.Errorf("expected to get an error") 301 engine.Close() 302 } 303 } 304 305 // Make sure that opencontainers/umoci#63 doesn't have a regression. We 306 // shouldn't GC any blobs which are currently locked. 307 func TestEngineGCLocking(t *testing.T) { 308 ctx := context.Background() 309 310 root, err := ioutil.TempDir("", "umoci-TestEngineGCLocking") 311 if err != nil { 312 t.Fatal(err) 313 } 314 defer os.RemoveAll(root) 315 316 image := filepath.Join(root, "image") 317 if err := Create(image); err != nil { 318 t.Fatalf("unexpected error creating image: %+v", err) 319 } 320 321 content := []byte("here's some sample content") 322 323 // Open a reference to the CAS, and make sure that it has a .temp set up. 324 engine, err := Open(image) 325 if err != nil { 326 t.Fatalf("unexpected error opening image: %+v", err) 327 } 328 329 digester := cas.BlobAlgorithm.Digester() 330 if _, err := io.Copy(digester.Hash(), bytes.NewReader(content)); err != nil { 331 t.Fatalf("could not hash bytes: %+v", err) 332 } 333 expectedDigest := digester.Digest() 334 335 digest, size, err := engine.PutBlob(ctx, bytes.NewReader(content)) 336 if err != nil { 337 t.Errorf("PutBlob: unexpected error: %+v", err) 338 } 339 340 if digest != expectedDigest { 341 t.Errorf("PutBlob: digest doesn't match: expected=%s got=%s", expectedDigest, digest) 342 } 343 if size != int64(len(content)) { 344 t.Errorf("PutBlob: length doesn't match: expected=%d got=%d", len(content), size) 345 } 346 347 if engine.(*dirEngine).temp == "" { 348 t.Errorf("engine doesn't have a tempdir after putting a blob!") 349 } 350 351 // Create umoci and other directories and files to make sure things work. 352 umociTestDir, err := ioutil.TempDir(image, ".umoci-dead-") 353 if err != nil { 354 t.Fatal(err) 355 } 356 357 otherTestDir, err := ioutil.TempDir(image, "other-") 358 if err != nil { 359 t.Fatal(err) 360 } 361 362 // Open a new reference and GC it. 363 gcEngine, err := Open(image) 364 if err != nil { 365 t.Fatalf("unexpected error opening image: %+v", err) 366 } 367 368 // TODO: This should be done with casext.GC... 369 if err := gcEngine.Clean(ctx); err != nil { 370 t.Fatalf("unexpected error while GCing image: %+v", err) 371 } 372 373 for _, path := range []string{ 374 engine.(*dirEngine).temp, 375 otherTestDir, 376 } { 377 if _, err := os.Lstat(path); err != nil { 378 t.Errorf("expected %s to still exist after GC: %+v", path, err) 379 } 380 } 381 382 for _, path := range []string{ 383 umociTestDir, 384 } { 385 if _, err := os.Lstat(path); err == nil { 386 t.Errorf("expected %s to not exist after GC", path) 387 } else if !os.IsNotExist(errors.Cause(err)) { 388 t.Errorf("expected IsNotExist for %s after GC: %+v", path, err) 389 } 390 } 391 } 392 393 func TestCreateLayoutReadonly(t *testing.T) { 394 ctx := context.Background() 395 396 root, err := ioutil.TempDir("", "umoci-TestCreateLayoutReadonly") 397 if err != nil { 398 t.Fatal(err) 399 } 400 defer os.RemoveAll(root) 401 402 image := filepath.Join(root, "image") 403 if err := Create(image); err != nil { 404 t.Fatalf("unexpected error creating image: %+v", err) 405 } 406 407 // make it readonly 408 testutils.MakeReadOnly(t, image) 409 defer testutils.MakeReadWrite(t, image) 410 411 engine, err := Open(image) 412 if err != nil { 413 t.Fatalf("unexpected error opening image: %+v", err) 414 } 415 defer engine.Close() 416 417 // We should have an empty index and no blobs. 418 if index, err := engine.GetIndex(ctx); err != nil { 419 t.Errorf("unexpected error getting top-level index: %+v", err) 420 } else if len(index.Manifests) > 0 { 421 t.Errorf("got manifests in top-level index in a newly created image: %v", index.Manifests) 422 } 423 if blobs, err := engine.ListBlobs(ctx); err != nil { 424 t.Errorf("unexpected error getting list of blobs: %+v", err) 425 } else if len(blobs) > 0 { 426 t.Errorf("got blobs in a newly created image: %v", blobs) 427 } 428 } 429 430 func TestEngineBlobReadonly(t *testing.T) { 431 ctx := context.Background() 432 433 root, err := ioutil.TempDir("", "umoci-TestEngineBlobReadonly") 434 if err != nil { 435 t.Fatal(err) 436 } 437 defer os.RemoveAll(root) 438 439 image := filepath.Join(root, "image") 440 if err := Create(image); err != nil { 441 t.Fatalf("unexpected error creating image: %+v", err) 442 } 443 444 for _, test := range []struct { 445 bytes []byte 446 }{ 447 {[]byte("")}, 448 {[]byte("some blob")}, 449 {[]byte("another blob")}, 450 } { 451 engine, err := Open(image) 452 if err != nil { 453 t.Fatalf("unexpected error opening image: %+v", err) 454 } 455 456 digester := cas.BlobAlgorithm.Digester() 457 if _, err := io.Copy(digester.Hash(), bytes.NewReader(test.bytes)); err != nil { 458 t.Fatalf("could not hash bytes: %+v", err) 459 } 460 expectedDigest := digester.Digest() 461 462 digest, size, err := engine.PutBlob(ctx, bytes.NewReader(test.bytes)) 463 if err != nil { 464 t.Errorf("PutBlob: unexpected error: %+v", err) 465 } 466 467 if digest != expectedDigest { 468 t.Errorf("PutBlob: digest doesn't match: expected=%s got=%s", expectedDigest, digest) 469 } 470 if size != int64(len(test.bytes)) { 471 t.Errorf("PutBlob: length doesn't match: expected=%d got=%d", len(test.bytes), size) 472 } 473 474 if err := engine.Close(); err != nil { 475 t.Errorf("Close: unexpected error encountered: %+v", err) 476 } 477 478 // make it readonly 479 testutils.MakeReadOnly(t, image) 480 481 newEngine, err := Open(image) 482 if err != nil { 483 t.Errorf("unexpected error opening ro image: %+v", err) 484 } 485 486 blobReader, err := newEngine.GetBlob(ctx, digest) 487 if err != nil { 488 t.Errorf("GetBlob: unexpected error: %+v", err) 489 } 490 defer blobReader.Close() 491 492 gotBytes, err := ioutil.ReadAll(blobReader) 493 if err != nil { 494 t.Errorf("GetBlob: failed to ReadAll: %+v", err) 495 } 496 if !bytes.Equal(test.bytes, gotBytes) { 497 t.Errorf("GetBlob: bytes did not match: expected=%s got=%s", string(test.bytes), string(gotBytes)) 498 } 499 500 // Make sure that writing again will FAIL. 501 _, _, err = newEngine.PutBlob(ctx, bytes.NewReader(test.bytes)) 502 if err == nil { 503 t.Logf("PutBlob: e.temp = %s", newEngine.(*dirEngine).temp) 504 t.Errorf("PutBlob: expected error on ro image!") 505 } 506 507 if err := newEngine.Close(); err != nil { 508 t.Errorf("Close: unexpected error encountered on ro: %+v", err) 509 } 510 511 // make it readwrite again. 512 testutils.MakeReadWrite(t, image) 513 } 514 }