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  }