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  }