github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/cas/dir/dir_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016, 2017, 2018 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  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"testing"
    27  
    28  	"github.com/openSUSE/umoci/oci/cas"
    29  	"github.com/pkg/errors"
    30  	"golang.org/x/net/context"
    31  	"golang.org/x/sys/unix"
    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  // readonly makes the given path read-only (by bind-mounting it as "ro").
    38  // TODO: This should be done through an interface restriction in the test
    39  //       (which is then backed up by the readonly mount if necessary). The fact
    40  //       this test is necessary is a sign that we need a better split up of the
    41  //       CAS interface.
    42  func readonly(t *testing.T, path string) {
    43  	if os.Geteuid() != 0 {
    44  		t.Log("readonly tests only work with root privileges")
    45  		t.Skip()
    46  	}
    47  
    48  	t.Logf("mounting %s as readonly", path)
    49  
    50  	if err := unix.Mount(path, path, "", unix.MS_BIND|unix.MS_RDONLY, ""); err != nil {
    51  		t.Fatalf("mount %s as ro: %s", path, err)
    52  	}
    53  	if err := unix.Mount("none", path, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY, ""); err != nil {
    54  		t.Fatalf("mount %s as ro: %s", path, err)
    55  	}
    56  }
    57  
    58  // readwrite undoes the effect of readonly.
    59  func readwrite(t *testing.T, path string) {
    60  	if os.Geteuid() != 0 {
    61  		t.Log("readonly tests only work with root privileges")
    62  		t.Skip()
    63  	}
    64  
    65  	if err := unix.Unmount(path, unix.MNT_DETACH); err != nil {
    66  		t.Fatalf("unmount %s: %s", path, err)
    67  	}
    68  }
    69  
    70  func TestCreateLayoutReadonly(t *testing.T) {
    71  	ctx := context.Background()
    72  
    73  	root, err := ioutil.TempDir("", "umoci-TestCreateLayoutReadonly")
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	defer os.RemoveAll(root)
    78  
    79  	image := filepath.Join(root, "image")
    80  	if err := Create(image); err != nil {
    81  		t.Fatalf("unexpected error creating image: %+v", err)
    82  	}
    83  
    84  	// make it readonly
    85  	readonly(t, image)
    86  	defer readwrite(t, image)
    87  
    88  	engine, err := Open(image)
    89  	if err != nil {
    90  		t.Fatalf("unexpected error opening image: %+v", err)
    91  	}
    92  	defer engine.Close()
    93  
    94  	// We should have an empty index and no blobs.
    95  	if index, err := engine.GetIndex(ctx); err != nil {
    96  		t.Errorf("unexpected error getting top-level index: %+v", err)
    97  	} else if len(index.Manifests) > 0 {
    98  		t.Errorf("got manifests in top-level index in a newly created image: %v", index.Manifests)
    99  	}
   100  	if blobs, err := engine.ListBlobs(ctx); err != nil {
   101  		t.Errorf("unexpected error getting list of blobs: %+v", err)
   102  	} else if len(blobs) > 0 {
   103  		t.Errorf("got blobs in a newly created image: %v", blobs)
   104  	}
   105  }
   106  
   107  func TestEngineBlobReadonly(t *testing.T) {
   108  	ctx := context.Background()
   109  
   110  	root, err := ioutil.TempDir("", "umoci-TestEngineBlobReadonly")
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	defer os.RemoveAll(root)
   115  
   116  	image := filepath.Join(root, "image")
   117  	if err := Create(image); err != nil {
   118  		t.Fatalf("unexpected error creating image: %+v", err)
   119  	}
   120  
   121  	for _, test := range []struct {
   122  		bytes []byte
   123  	}{
   124  		{[]byte("")},
   125  		{[]byte("some blob")},
   126  		{[]byte("another blob")},
   127  	} {
   128  		engine, err := Open(image)
   129  		if err != nil {
   130  			t.Fatalf("unexpected error opening image: %+v", err)
   131  		}
   132  
   133  		digester := cas.BlobAlgorithm.Digester()
   134  		if _, err := io.Copy(digester.Hash(), bytes.NewReader(test.bytes)); err != nil {
   135  			t.Fatalf("could not hash bytes: %+v", err)
   136  		}
   137  		expectedDigest := digester.Digest()
   138  
   139  		digest, size, err := engine.PutBlob(ctx, bytes.NewReader(test.bytes))
   140  		if err != nil {
   141  			t.Errorf("PutBlob: unexpected error: %+v", err)
   142  		}
   143  
   144  		if digest != expectedDigest {
   145  			t.Errorf("PutBlob: digest doesn't match: expected=%s got=%s", expectedDigest, digest)
   146  		}
   147  		if size != int64(len(test.bytes)) {
   148  			t.Errorf("PutBlob: length doesn't match: expected=%d got=%d", len(test.bytes), size)
   149  		}
   150  
   151  		if err := engine.Close(); err != nil {
   152  			t.Errorf("Close: unexpected error encountered: %+v", err)
   153  		}
   154  
   155  		// make it readonly
   156  		readonly(t, image)
   157  
   158  		newEngine, err := Open(image)
   159  		if err != nil {
   160  			t.Errorf("unexpected error opening ro image: %+v", err)
   161  		}
   162  
   163  		blobReader, err := newEngine.GetBlob(ctx, digest)
   164  		if err != nil {
   165  			t.Errorf("GetBlob: unexpected error: %+v", err)
   166  		}
   167  		defer blobReader.Close()
   168  
   169  		gotBytes, err := ioutil.ReadAll(blobReader)
   170  		if err != nil {
   171  			t.Errorf("GetBlob: failed to ReadAll: %+v", err)
   172  		}
   173  		if !bytes.Equal(test.bytes, gotBytes) {
   174  			t.Errorf("GetBlob: bytes did not match: expected=%s got=%s", string(test.bytes), string(gotBytes))
   175  		}
   176  
   177  		// Make sure that writing again will FAIL.
   178  		_, _, err = newEngine.PutBlob(ctx, bytes.NewReader(test.bytes))
   179  		if err == nil {
   180  			t.Logf("PutBlob: e.temp = %s", newEngine.(*dirEngine).temp)
   181  			t.Errorf("PutBlob: expected error on ro image!")
   182  		}
   183  
   184  		if err := newEngine.Close(); err != nil {
   185  			t.Errorf("Close: unexpected error encountered on ro: %+v", err)
   186  		}
   187  
   188  		// make it readwrite again.
   189  		readwrite(t, image)
   190  	}
   191  }
   192  
   193  // Make sure that openSUSE/umoci#63 doesn't have a regression where we start
   194  // deleting files and directories that other people are using.
   195  func TestEngineGCLocking(t *testing.T) {
   196  	ctx := context.Background()
   197  
   198  	root, err := ioutil.TempDir("", "umoci-TestCreateLayoutReadonly")
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	defer os.RemoveAll(root)
   203  
   204  	image := filepath.Join(root, "image")
   205  	if err := Create(image); err != nil {
   206  		t.Fatalf("unexpected error creating image: %+v", err)
   207  	}
   208  
   209  	content := []byte("here's some sample content")
   210  
   211  	// Open a reference to the CAS, and make sure that it has a .temp set up.
   212  	engine, err := Open(image)
   213  	if err != nil {
   214  		t.Fatalf("unexpected error opening image: %+v", err)
   215  	}
   216  
   217  	digester := cas.BlobAlgorithm.Digester()
   218  	if _, err := io.Copy(digester.Hash(), bytes.NewReader(content)); err != nil {
   219  		t.Fatalf("could not hash bytes: %+v", err)
   220  	}
   221  	expectedDigest := digester.Digest()
   222  
   223  	digest, size, err := engine.PutBlob(ctx, bytes.NewReader(content))
   224  	if err != nil {
   225  		t.Errorf("PutBlob: unexpected error: %+v", err)
   226  	}
   227  
   228  	if digest != expectedDigest {
   229  		t.Errorf("PutBlob: digest doesn't match: expected=%s got=%s", expectedDigest, digest)
   230  	}
   231  	if size != int64(len(content)) {
   232  		t.Errorf("PutBlob: length doesn't match: expected=%d got=%d", len(content), size)
   233  	}
   234  
   235  	if engine.(*dirEngine).temp == "" {
   236  		t.Errorf("engine doesn't have a tempdir after putting a blob!")
   237  	}
   238  
   239  	// Create umoci and other directories and files to make sure things work.
   240  	umociTestDir, err := ioutil.TempDir(image, ".umoci-dead-")
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	otherTestDir, err := ioutil.TempDir(image, "other-")
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  
   250  	// Open a new reference and GC it.
   251  	gcEngine, err := Open(image)
   252  	if err != nil {
   253  		t.Fatalf("unexpected error opening image: %+v", err)
   254  	}
   255  
   256  	// TODO: This should be done with casext.GC...
   257  	if err := gcEngine.Clean(ctx); err != nil {
   258  		t.Fatalf("unexpected error while GCing image: %+v", err)
   259  	}
   260  
   261  	for _, path := range []string{
   262  		engine.(*dirEngine).temp,
   263  		otherTestDir,
   264  	} {
   265  		if _, err := os.Lstat(path); err != nil {
   266  			t.Errorf("expected %s to still exist after GC: %+v", path, err)
   267  		}
   268  	}
   269  
   270  	for _, path := range []string{
   271  		umociTestDir,
   272  	} {
   273  		if _, err := os.Lstat(path); err == nil {
   274  			t.Errorf("expected %s to not exist after GC", path)
   275  		} else if !os.IsNotExist(errors.Cause(err)) {
   276  			t.Errorf("expected IsNotExist for %s after GC: %+v", path, err)
   277  		}
   278  	}
   279  }