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 }