github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/tests/rkt_fetch_test.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "fmt" 19 "io" 20 "net/http" 21 "net/http/httptest" 22 "os" 23 "path/filepath" 24 "strconv" 25 "strings" 26 "sync" 27 "testing" 28 29 "github.com/appc/spec/schema/types" 30 "github.com/coreos/rkt/tests/testutils" 31 taas "github.com/coreos/rkt/tests/testutils/aci-server" 32 ) 33 34 // TestFetchFromFile tests that 'rkt fetch/run/prepare' for a file will always 35 // fetch the file regardless of the specified behavior (default, store only, 36 // remote only). 37 func TestFetchFromFile(t *testing.T) { 38 image := "rkt-inspect-implicit-fetch.aci" 39 imagePath := patchTestACI(image, "--exec=/inspect") 40 41 defer os.Remove(imagePath) 42 43 tests := []struct { 44 args string 45 image string 46 }{ 47 {"--insecure-options=image fetch", imagePath}, 48 {"--insecure-options=image fetch --store-only", imagePath}, 49 {"--insecure-options=image fetch --no-store", imagePath}, 50 {"--insecure-options=image run --mds-register=false", imagePath}, 51 {"--insecure-options=image run --mds-register=false --store-only", imagePath}, 52 {"--insecure-options=image run --mds-register=false --no-store", imagePath}, 53 {"--insecure-options=image prepare", imagePath}, 54 {"--insecure-options=image prepare --store-only", imagePath}, 55 {"--insecure-options=image prepare --no-store", imagePath}, 56 } 57 58 for _, tt := range tests { 59 testFetchFromFile(t, tt.args, tt.image) 60 } 61 } 62 63 func testFetchFromFile(t *testing.T, arg string, image string) { 64 fetchFromFileMsg := fmt.Sprintf("using image from file %s", image) 65 66 ctx := testutils.NewRktRunCtx() 67 defer ctx.Cleanup() 68 69 cmd := fmt.Sprintf("%s %s %s", ctx.Cmd(), arg, image) 70 71 // 1. Run cmd, should get $fetchFromFileMsg. 72 child := spawnOrFail(t, cmd) 73 if err := expectWithOutput(child, fetchFromFileMsg); err != nil { 74 t.Fatalf("%q should be found: %v", fetchFromFileMsg, err) 75 } 76 child.Wait() 77 78 // 1. Run cmd again, should get $fetchFromFileMsg. 79 runRktAndCheckOutput(t, cmd, fetchFromFileMsg, false) 80 } 81 82 // TestFetch tests that 'rkt fetch/run/prepare' for any type (image name string 83 // or URL) except file:// URL will work with the default, store only 84 // (--store-only) and remote only (--no-store) behaviors. 85 func TestFetch(t *testing.T) { 86 image := "rkt-inspect-implicit-fetch.aci" 87 imagePath := patchTestACI(image, "--exec=/inspect") 88 89 defer os.Remove(imagePath) 90 91 tests := []struct { 92 args string 93 image string 94 imageArgs string 95 finalURL string 96 }{ 97 {"--insecure-options=image fetch", "coreos.com/etcd:v2.1.2", "", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci"}, 98 {"--insecure-options=image fetch", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci", "", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci"}, 99 {"--insecure-options=image fetch", "docker://busybox", "", "docker://busybox"}, 100 {"--insecure-options=image fetch", "docker://busybox:latest", "", "docker://busybox:latest"}, 101 {"--insecure-options=image run --mds-register=false", "coreos.com/etcd:v2.1.2", "--exec /dev/null", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci"}, 102 {"--insecure-options=image run --mds-register=false", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci", "--exec /dev/null", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci"}, 103 {"--insecure-options=image run --mds-register=false", "docker://busybox", "", "docker://busybox"}, 104 {"--insecure-options=image run --mds-register=false", "docker://busybox:latest", "", "docker://busybox:latest"}, 105 {"--insecure-options=image prepare", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci", "", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci"}, 106 {"--insecure-options=image prepare", "coreos.com/etcd:v2.1.2", "", "https://github.com/coreos/etcd/releases/download/v2.1.2/etcd-v2.1.2-linux-amd64.aci"}, 107 // test --insecure-options=tls to make sure 108 // https://github.com/coreos/rkt/issues/1829 is not an issue anymore 109 {"--insecure-options=image,tls prepare", "docker://busybox", "", "docker://busybox"}, 110 {"--insecure-options=image prepare", "docker://busybox:latest", "", "docker://busybox:latest"}, 111 } 112 113 for _, tt := range tests { 114 testFetchDefault(t, tt.args, tt.image, tt.imageArgs, tt.finalURL) 115 testFetchStoreOnly(t, tt.args, tt.image, tt.imageArgs, tt.finalURL) 116 testFetchNoStore(t, tt.args, tt.image, tt.imageArgs, tt.finalURL) 117 } 118 } 119 120 func TestFetchFullHash(t *testing.T) { 121 imagePath := getInspectImagePath() 122 123 ctx := testutils.NewRktRunCtx() 124 defer ctx.Cleanup() 125 126 tests := []struct { 127 fetchArgs string 128 expectedHashLength int 129 }{ 130 {"", len("sha512-") + 32}, 131 {"--full", len("sha512-") + 64}, 132 } 133 134 for _, tt := range tests { 135 hash := importImageAndFetchHash(t, ctx, tt.fetchArgs, imagePath) 136 if len(hash) != tt.expectedHashLength { 137 t.Fatalf("expected hash length of %d, got %d", tt.expectedHashLength, len(hash)) 138 } 139 } 140 } 141 142 func testFetchDefault(t *testing.T, arg string, image string, imageArgs string, finalURL string) { 143 remoteFetchMsgTpl := `remote fetching from URL %q` 144 storeMsgTpl := `using image from local store for .* %s` 145 if finalURL == "" { 146 finalURL = image 147 } 148 remoteFetchMsg := fmt.Sprintf(remoteFetchMsgTpl, finalURL) 149 storeMsg := fmt.Sprintf(storeMsgTpl, image) 150 151 ctx := testutils.NewRktRunCtx() 152 defer ctx.Cleanup() 153 154 cmd := fmt.Sprintf("%s %s %s %s", ctx.Cmd(), arg, image, imageArgs) 155 156 // 1. Run cmd with the image not available in the store, should get $remoteFetchMsg. 157 child := spawnOrFail(t, cmd) 158 if err := expectWithOutput(child, remoteFetchMsg); err != nil { 159 t.Fatalf("%q should be found: %v", remoteFetchMsg, err) 160 } 161 child.Wait() 162 163 // 2. Run cmd with the image available in the store, should get $storeMsg. 164 runRktAndCheckRegexOutput(t, cmd, storeMsg) 165 } 166 167 func testFetchStoreOnly(t *testing.T, args string, image string, imageArgs string, finalURL string) { 168 cannotFetchMsgTpl := `unable to fetch.* image from .* %q` 169 storeMsgTpl := `using image from local store for .* %s` 170 cannotFetchMsg := fmt.Sprintf(cannotFetchMsgTpl, image) 171 storeMsg := fmt.Sprintf(storeMsgTpl, image) 172 173 ctx := testutils.NewRktRunCtx() 174 defer ctx.Cleanup() 175 176 cmd := fmt.Sprintf("%s --store-only %s %s %s", ctx.Cmd(), args, image, imageArgs) 177 178 // 1. Run cmd with the image not available in the store should get $cannotFetchMsg. 179 runRktAndCheckRegexOutput(t, cmd, cannotFetchMsg) 180 181 importImageAndFetchHash(t, ctx, "", image) 182 183 // 2. Run cmd with the image available in the store, should get $storeMsg. 184 runRktAndCheckRegexOutput(t, cmd, storeMsg) 185 } 186 187 func testFetchNoStore(t *testing.T, args string, image string, imageArgs string, finalURL string) { 188 remoteFetchMsgTpl := `remote fetching from URL %q` 189 remoteFetchMsg := fmt.Sprintf(remoteFetchMsgTpl, finalURL) 190 191 ctx := testutils.NewRktRunCtx() 192 defer ctx.Cleanup() 193 194 importImageAndFetchHash(t, ctx, "", image) 195 196 cmd := fmt.Sprintf("%s --no-store %s %s %s", ctx.Cmd(), args, image, imageArgs) 197 198 // 1. Run cmd with the image available in the store, should get $remoteFetchMsg. 199 child := spawnOrFail(t, cmd) 200 if err := expectWithOutput(child, remoteFetchMsg); err != nil { 201 t.Fatalf("%q should be found: %v", remoteFetchMsg, err) 202 } 203 child.Wait() 204 } 205 206 type synchronizedBool struct { 207 value bool 208 lock sync.Mutex 209 } 210 211 func (b *synchronizedBool) Read() bool { 212 b.lock.Lock() 213 value := b.value 214 b.lock.Unlock() 215 return value 216 } 217 218 func (b *synchronizedBool) Write(value bool) { 219 b.lock.Lock() 220 b.value = value 221 b.lock.Unlock() 222 } 223 224 func TestResumedFetch(t *testing.T) { 225 image := "rkt-inspect-implicit-fetch.aci" 226 imagePath := patchTestACI(image, "--exec=/inspect") 227 defer os.Remove(imagePath) 228 229 hash := types.ShortHash("sha512-" + getHashOrPanic(imagePath)) 230 231 kill := make(chan struct{}) 232 reportkill := make(chan struct{}) 233 234 shouldInterrupt := &synchronizedBool{} 235 shouldInterrupt.Write(true) 236 237 server := httptest.NewServer(testServerHandler(t, shouldInterrupt, imagePath, kill, reportkill)) 238 defer server.Close() 239 240 ctx := testutils.NewRktRunCtx() 241 defer ctx.Cleanup() 242 243 cmd := fmt.Sprintf("%s --no-store --insecure-options=image fetch %s", ctx.Cmd(), server.URL) 244 child := spawnOrFail(t, cmd) 245 <-kill 246 err := child.Close() 247 if err != nil { 248 panic(err) 249 } 250 reportkill <- struct{}{} 251 252 // rkt has fetched the first half of the image 253 // If it fetches the first half again these channels will be written to. 254 // Closing them to make the test panic if they're written to. 255 close(kill) 256 close(reportkill) 257 258 child = spawnOrFail(t, cmd) 259 if _, _, err := expectRegexWithOutput(child, ".*"+hash); err != nil { 260 t.Fatalf("hash didn't match: %v", err) 261 } 262 waitOrFail(t, child, true) 263 } 264 265 func TestResumedFetchInvalidCache(t *testing.T) { 266 image := "rkt-inspect-implicit-fetch.aci" 267 imagePath := patchTestACI(image, "--exec=/inspect") 268 defer os.Remove(imagePath) 269 270 hash := types.ShortHash("sha512-" + getHashOrPanic(imagePath)) 271 272 kill := make(chan struct{}) 273 reportkill := make(chan struct{}) 274 275 ctx := testutils.NewRktRunCtx() 276 defer ctx.Cleanup() 277 278 shouldInterrupt := &synchronizedBool{} 279 shouldInterrupt.Write(true) 280 281 // Fetch the first half of the image, and kill rkt once it reaches halfway. 282 server := httptest.NewServer(testServerHandler(t, shouldInterrupt, imagePath, kill, reportkill)) 283 defer server.Close() 284 cmd := fmt.Sprintf("%s --no-store --insecure-options=image fetch %s", ctx.Cmd(), server.URL) 285 child := spawnOrFail(t, cmd) 286 <-kill 287 err := child.Close() 288 if err != nil { 289 panic(err) 290 } 291 reportkill <- struct{}{} 292 293 // Fetch the image again. The server doesn't support Etags or the 294 // Last-Modified header, so the cached version should be invalidated. If 295 // rkt tries to use the cache, the hash won't check out. 296 shouldInterrupt.Write(false) 297 child = spawnOrFail(t, cmd) 298 if _, s, err := expectRegexWithOutput(child, ".*"+hash); err != nil { 299 t.Fatalf("hash didn't match: %v\nin: %s", err, s) 300 } 301 waitOrFail(t, child, true) 302 } 303 304 func testServerHandler(t *testing.T, shouldInterrupt *synchronizedBool, imagePath string, kill, waitforkill chan struct{}) http.HandlerFunc { 305 interruptingHandler := testInterruptingServerHandler(t, imagePath, kill, waitforkill) 306 simpleHandler := testSimpleServerHandler(t, imagePath) 307 308 return func(w http.ResponseWriter, r *http.Request) { 309 if shouldInterrupt.Read() { 310 interruptingHandler(w, r) 311 } else { 312 simpleHandler(w, r) 313 } 314 } 315 } 316 317 func testInterruptingServerHandler(t *testing.T, imagePath string, kill, waitforkill chan struct{}) http.HandlerFunc { 318 finfo, err := os.Stat(imagePath) 319 if err != nil { 320 panic(err) 321 } 322 cutoff := finfo.Size() / 2 323 return func(w http.ResponseWriter, r *http.Request) { 324 if r.Method == "HEAD" { 325 headers := w.Header() 326 headers["Accept-Ranges"] = []string{"bytes"} 327 headers["Last-Modified"] = []string{"Mon, 02 Jan 2006 15:04:05 MST"} 328 w.WriteHeader(http.StatusOK) 329 return 330 } 331 if r.Method != "GET" { 332 w.WriteHeader(http.StatusNotFound) 333 return 334 } 335 336 file, err := os.Open(imagePath) 337 if err != nil { 338 panic(err) 339 } 340 defer file.Close() 341 342 rangeHeaders, ok := r.Header["Range"] 343 if ok && len(rangeHeaders) == 1 && strings.HasPrefix(rangeHeaders[0], "bytes=") { 344 rangeHeader := rangeHeaders[0][6:] // The first (and only) range header, with len("bytes=") characters chopped off the front 345 tokens := strings.Split(rangeHeader, "-") 346 if len(tokens) != 2 { 347 t.Fatalf("couldn't parse range header: %q", rangeHeader) 348 } 349 350 start, err := strconv.Atoi(tokens[0]) 351 if err != nil { 352 if tokens[0] == "" { 353 start = 0 // If start wasn't specified, start at the beginning 354 } else { 355 t.Fatalf("requested non-int starting location: %s", tokens[0]) 356 } 357 } 358 end, err := strconv.Atoi(tokens[1]) 359 if err != nil { 360 if tokens[1] == "" { 361 end = int(finfo.Size()) - 1 // If end wasn't specified, end at the end 362 } else { 363 t.Fatalf("requested non-int ending location: %s", tokens[0]) 364 } 365 } 366 367 _, err = file.Seek(int64(start), os.SEEK_SET) 368 if err != nil { 369 panic(err) 370 } 371 372 _, err = io.CopyN(w, file, int64(end-start+1)) 373 if err != nil { 374 panic(err) 375 } 376 377 return 378 } 379 380 _, err = io.CopyN(w, file, cutoff) 381 if err != nil { 382 panic(err) 383 } 384 385 kill <- struct{}{} 386 <-waitforkill 387 } 388 } 389 390 func testSimpleServerHandler(t *testing.T, imagePath string) http.HandlerFunc { 391 return func(w http.ResponseWriter, r *http.Request) { 392 if r.Method == "HEAD" { 393 w.WriteHeader(http.StatusOK) 394 return 395 } 396 if r.Method != "GET" { 397 w.WriteHeader(http.StatusNotFound) 398 return 399 } 400 401 file, err := os.Open(imagePath) 402 if err != nil { 403 panic(err) 404 } 405 defer file.Close() 406 407 _, err = io.Copy(w, file) 408 if err != nil { 409 panic(err) 410 } 411 } 412 } 413 414 func TestDeferredSignatureDownload(t *testing.T) { 415 imageName := "localhost/rkt-inspect-deferred-signature-download" 416 imageFileName := fmt.Sprintf("%s.aci", filepath.Base(imageName)) 417 // no spaces between words, because of an actool limitation 418 successMsg := "deferredSignatureDownloadWasSuccessful" 419 420 args := []string{ 421 fmt.Sprintf("--exec=/inspect --print-msg='%s'", successMsg), 422 fmt.Sprintf("--name=%s", imageName), 423 } 424 image := patchTestACI(imageFileName, args...) 425 defer os.Remove(image) 426 427 asc := runSignImage(t, image, 1) 428 defer os.Remove(asc) 429 ascBase := filepath.Base(asc) 430 431 setup := taas.GetDefaultServerSetup() 432 setup.Server = taas.ServerQuay 433 server := runServer(t, setup) 434 defer server.Close() 435 fileSet := make(map[string]string, 2) 436 fileSet[imageFileName] = image 437 fileSet[ascBase] = asc 438 if err := server.UpdateFileSet(fileSet); err != nil { 439 t.Fatalf("Failed to populate a file list in test aci server: %v", err) 440 } 441 442 ctx := testutils.NewRktRunCtx() 443 defer ctx.Cleanup() 444 445 runRktTrust(t, ctx, "", 1) 446 447 runCmd := fmt.Sprintf("%s --debug --insecure-options=tls run %s", ctx.Cmd(), imageName) 448 child := spawnOrFail(t, runCmd) 449 defer waitOrFail(t, child, true) 450 451 expectedMessages := []string{ 452 "server requested deferring the signature download", 453 successMsg, 454 } 455 for _, msg := range expectedMessages { 456 if err := expectWithOutput(child, msg); err != nil { 457 t.Fatalf("Could not find expected msg %q, output follows:\n%v", msg, err) 458 } 459 } 460 }