github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/store/store_download_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package store_test
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"crypto"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"net/http"
    30  	"net/http/httptest"
    31  	"net/url"
    32  	"os"
    33  	"path/filepath"
    34  	"time"
    35  
    36  	"golang.org/x/crypto/sha3"
    37  	. "gopkg.in/check.v1"
    38  	"gopkg.in/retry.v1"
    39  
    40  	"github.com/snapcore/snapd/dirs"
    41  	"github.com/snapcore/snapd/httputil"
    42  	"github.com/snapcore/snapd/osutil"
    43  	"github.com/snapcore/snapd/overlord/auth"
    44  	"github.com/snapcore/snapd/progress"
    45  	"github.com/snapcore/snapd/snap"
    46  	"github.com/snapcore/snapd/store"
    47  	"github.com/snapcore/snapd/testutil"
    48  )
    49  
    50  type storeDownloadSuite struct {
    51  	baseStoreSuite
    52  
    53  	store *store.Store
    54  
    55  	localUser *auth.UserState
    56  
    57  	mockXDelta *testutil.MockCmd
    58  }
    59  
    60  var _ = Suite(&storeDownloadSuite{})
    61  
    62  func (s *storeDownloadSuite) SetUpTest(c *C) {
    63  	s.baseStoreSuite.SetUpTest(c)
    64  
    65  	c.Assert(os.MkdirAll(dirs.SnapMountDir, 0755), IsNil)
    66  
    67  	s.store = store.New(nil, nil)
    68  
    69  	s.localUser = &auth.UserState{
    70  		ID:       11,
    71  		Username: "test-user",
    72  		Macaroon: "snapd-macaroon",
    73  	}
    74  
    75  	s.mockXDelta = testutil.MockCommand(c, "xdelta3", "")
    76  	s.AddCleanup(s.mockXDelta.Restore)
    77  
    78  	store.MockDownloadRetryStrategy(&s.BaseTest, retry.LimitCount(5, retry.LimitTime(1*time.Second,
    79  		retry.Exponential{
    80  			Initial: 1 * time.Millisecond,
    81  			Factor:  1,
    82  		},
    83  	)))
    84  }
    85  
    86  func (s *storeDownloadSuite) TestDownloadOK(c *C) {
    87  	expectedContent := []byte("I was downloaded")
    88  
    89  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
    90  		c.Check(url, Equals, "anon-url")
    91  		w.Write(expectedContent)
    92  		return nil
    93  	})
    94  	defer restore()
    95  
    96  	snap := &snap.Info{}
    97  	snap.RealName = "foo"
    98  	snap.AnonDownloadURL = "anon-url"
    99  	snap.DownloadURL = "AUTH-URL"
   100  	snap.Size = int64(len(expectedContent))
   101  
   102  	path := filepath.Join(c.MkDir(), "downloaded-file")
   103  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, nil)
   104  	c.Assert(err, IsNil)
   105  	defer os.Remove(path)
   106  
   107  	c.Assert(path, testutil.FileEquals, expectedContent)
   108  }
   109  
   110  func (s *storeDownloadSuite) TestDownloadRangeRequest(c *C) {
   111  	partialContentStr := "partial content "
   112  	missingContentStr := "was downloaded"
   113  	expectedContentStr := partialContentStr + missingContentStr
   114  
   115  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   116  		c.Check(resume, Equals, int64(len(partialContentStr)))
   117  		c.Check(url, Equals, "anon-url")
   118  		w.Write([]byte(missingContentStr))
   119  		return nil
   120  	})
   121  	defer restore()
   122  
   123  	snap := &snap.Info{}
   124  	snap.RealName = "foo"
   125  	snap.AnonDownloadURL = "anon-url"
   126  	snap.DownloadURL = "AUTH-URL"
   127  	snap.Sha3_384 = "abcdabcd"
   128  	snap.Size = int64(len(expectedContentStr))
   129  
   130  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   131  	err := ioutil.WriteFile(targetFn+".partial", []byte(partialContentStr), 0644)
   132  	c.Assert(err, IsNil)
   133  
   134  	err = s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   135  	c.Assert(err, IsNil)
   136  
   137  	c.Assert(targetFn, testutil.FileEquals, expectedContentStr)
   138  }
   139  
   140  func (s *storeDownloadSuite) TestResumeOfCompleted(c *C) {
   141  	expectedContentStr := "nothing downloaded"
   142  
   143  	snap := &snap.Info{}
   144  	snap.RealName = "foo"
   145  	snap.AnonDownloadURL = "anon-url"
   146  	snap.DownloadURL = "AUTH-URL"
   147  	snap.Sha3_384 = fmt.Sprintf("%x", sha3.Sum384([]byte(expectedContentStr)))
   148  	snap.Size = int64(len(expectedContentStr))
   149  
   150  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   151  	err := ioutil.WriteFile(targetFn+".partial", []byte(expectedContentStr), 0644)
   152  	c.Assert(err, IsNil)
   153  
   154  	err = s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   155  	c.Assert(err, IsNil)
   156  
   157  	c.Assert(targetFn, testutil.FileEquals, expectedContentStr)
   158  }
   159  
   160  func (s *storeDownloadSuite) TestDownloadEOFHandlesResumeHashCorrectly(c *C) {
   161  	n := 0
   162  	var mockServer *httptest.Server
   163  
   164  	// our mock download content
   165  	buf := make([]byte, 50000)
   166  	for i := range buf {
   167  		buf[i] = 'x'
   168  	}
   169  	h := crypto.SHA3_384.New()
   170  	io.Copy(h, bytes.NewBuffer(buf))
   171  
   172  	// raise an EOF shortly before the end
   173  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   174  		n++
   175  		if n < 2 {
   176  			w.Header().Add("Content-Length", fmt.Sprintf("%d", len(buf)))
   177  			w.Write(buf[0 : len(buf)-5])
   178  			mockServer.CloseClientConnections()
   179  			return
   180  		}
   181  		if len(r.Header["Range"]) > 0 {
   182  			w.WriteHeader(206)
   183  		}
   184  		w.Write(buf[len(buf)-5:])
   185  	}))
   186  
   187  	c.Assert(mockServer, NotNil)
   188  	defer mockServer.Close()
   189  
   190  	snap := &snap.Info{}
   191  	snap.RealName = "foo"
   192  	snap.AnonDownloadURL = mockServer.URL
   193  	snap.DownloadURL = "AUTH-URL"
   194  	snap.Sha3_384 = fmt.Sprintf("%x", h.Sum(nil))
   195  	snap.Size = 50000
   196  
   197  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   198  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   199  	c.Assert(err, IsNil)
   200  	c.Assert(targetFn, testutil.FileEquals, buf)
   201  	c.Assert(s.logbuf.String(), Matches, "(?s).*Retrying .* attempt 2, .*")
   202  }
   203  
   204  func (s *storeDownloadSuite) TestDownloadRetryHashErrorIsFullyRetried(c *C) {
   205  	n := 0
   206  	var mockServer *httptest.Server
   207  
   208  	// our mock download content
   209  	buf := make([]byte, 50000)
   210  	for i := range buf {
   211  		buf[i] = 'x'
   212  	}
   213  	h := crypto.SHA3_384.New()
   214  	io.Copy(h, bytes.NewBuffer(buf))
   215  
   216  	// raise an EOF shortly before the end and send the WRONG content next
   217  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   218  		n++
   219  		switch n {
   220  		case 1:
   221  			w.Header().Add("Content-Length", fmt.Sprintf("%d", len(buf)))
   222  			w.Write(buf[0 : len(buf)-5])
   223  			mockServer.CloseClientConnections()
   224  		case 2:
   225  			io.WriteString(w, "yyyyy")
   226  		case 3:
   227  			w.Write(buf)
   228  		}
   229  	}))
   230  
   231  	c.Assert(mockServer, NotNil)
   232  	defer mockServer.Close()
   233  
   234  	snap := &snap.Info{}
   235  	snap.RealName = "foo"
   236  	snap.AnonDownloadURL = mockServer.URL
   237  	snap.DownloadURL = "AUTH-URL"
   238  	snap.Sha3_384 = fmt.Sprintf("%x", h.Sum(nil))
   239  	snap.Size = 50000
   240  
   241  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   242  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   243  	c.Assert(err, IsNil)
   244  
   245  	c.Assert(targetFn, testutil.FileEquals, buf)
   246  
   247  	c.Assert(s.logbuf.String(), Matches, "(?s).*Retrying .* attempt 2, .*")
   248  }
   249  
   250  func (s *storeDownloadSuite) TestResumeOfCompletedRetriedOnHashFailure(c *C) {
   251  	var mockServer *httptest.Server
   252  
   253  	// our mock download content
   254  	buf := make([]byte, 50000)
   255  	badbuf := make([]byte, 50000)
   256  	for i := range buf {
   257  		buf[i] = 'x'
   258  		badbuf[i] = 'y'
   259  	}
   260  	h := crypto.SHA3_384.New()
   261  	io.Copy(h, bytes.NewBuffer(buf))
   262  
   263  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   264  		w.Write(buf)
   265  	}))
   266  
   267  	c.Assert(mockServer, NotNil)
   268  	defer mockServer.Close()
   269  
   270  	snap := &snap.Info{}
   271  	snap.RealName = "foo"
   272  	snap.AnonDownloadURL = mockServer.URL
   273  	snap.DownloadURL = "AUTH-URL"
   274  	snap.Sha3_384 = fmt.Sprintf("%x", h.Sum(nil))
   275  	snap.Size = 50000
   276  
   277  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   278  	c.Assert(ioutil.WriteFile(targetFn+".partial", badbuf, 0644), IsNil)
   279  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   280  	c.Assert(err, IsNil)
   281  
   282  	c.Assert(targetFn, testutil.FileEquals, buf)
   283  
   284  	c.Assert(s.logbuf.String(), Matches, "(?s).*sha3-384 mismatch.*")
   285  }
   286  
   287  func (s *storeDownloadSuite) TestResumeOfTooMuchDataWorks(c *C) {
   288  	var mockServer *httptest.Server
   289  
   290  	// our mock download content
   291  	snapContent := "snap-content"
   292  	// the partial file has too much data
   293  	tooMuchLocalData := "way-way-way-too-much-snap-content"
   294  
   295  	h := crypto.SHA3_384.New()
   296  	io.Copy(h, bytes.NewBufferString(snapContent))
   297  
   298  	n := 0
   299  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   300  		n++
   301  		w.Write([]byte(snapContent))
   302  	}))
   303  	c.Assert(mockServer, NotNil)
   304  	defer mockServer.Close()
   305  
   306  	snap := &snap.Info{}
   307  	snap.RealName = "foo"
   308  	snap.AnonDownloadURL = mockServer.URL
   309  	snap.DownloadURL = "AUTH-URL"
   310  	snap.Sha3_384 = fmt.Sprintf("%x", h.Sum(nil))
   311  	snap.Size = int64(len(snapContent))
   312  
   313  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   314  	c.Assert(ioutil.WriteFile(targetFn+".partial", []byte(tooMuchLocalData), 0644), IsNil)
   315  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   316  	c.Assert(err, IsNil)
   317  	c.Assert(n, Equals, 1)
   318  
   319  	c.Assert(targetFn, testutil.FileEquals, snapContent)
   320  
   321  	c.Assert(s.logbuf.String(), Matches, "(?s).*sha3-384 mismatch.*")
   322  }
   323  
   324  func (s *storeDownloadSuite) TestDownloadRetryHashErrorIsFullyRetriedOnlyOnce(c *C) {
   325  	n := 0
   326  	var mockServer *httptest.Server
   327  
   328  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   329  		n++
   330  		io.WriteString(w, "something invalid")
   331  	}))
   332  
   333  	c.Assert(mockServer, NotNil)
   334  	defer mockServer.Close()
   335  
   336  	snap := &snap.Info{}
   337  	snap.RealName = "foo"
   338  	snap.AnonDownloadURL = mockServer.URL
   339  	snap.DownloadURL = "AUTH-URL"
   340  	snap.Sha3_384 = "invalid-hash"
   341  	snap.Size = int64(len("something invalid"))
   342  
   343  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   344  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   345  
   346  	_, ok := err.(store.HashError)
   347  	c.Assert(ok, Equals, true)
   348  	// ensure we only retried once (as these downloads might be big)
   349  	c.Assert(n, Equals, 2)
   350  }
   351  
   352  func (s *storeDownloadSuite) TestDownloadRangeRequestRetryOnHashError(c *C) {
   353  	expectedContentStr := "file was downloaded from scratch"
   354  	partialContentStr := "partial content "
   355  
   356  	n := 0
   357  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   358  		n++
   359  		if n == 1 {
   360  			// force sha3 error on first download
   361  			c.Check(resume, Equals, int64(len(partialContentStr)))
   362  			return store.NewHashError("foo", "1234", "5678")
   363  		}
   364  		w.Write([]byte(expectedContentStr))
   365  		return nil
   366  	})
   367  	defer restore()
   368  
   369  	snap := &snap.Info{}
   370  	snap.RealName = "foo"
   371  	snap.AnonDownloadURL = "anon-url"
   372  	snap.DownloadURL = "AUTH-URL"
   373  	snap.Sha3_384 = ""
   374  	snap.Size = int64(len(expectedContentStr))
   375  
   376  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   377  	err := ioutil.WriteFile(targetFn+".partial", []byte(partialContentStr), 0644)
   378  	c.Assert(err, IsNil)
   379  
   380  	err = s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   381  	c.Assert(err, IsNil)
   382  	c.Assert(n, Equals, 2)
   383  
   384  	c.Assert(targetFn, testutil.FileEquals, expectedContentStr)
   385  }
   386  
   387  func (s *storeDownloadSuite) TestDownloadRangeRequestFailOnHashError(c *C) {
   388  	partialContentStr := "partial content "
   389  
   390  	n := 0
   391  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   392  		n++
   393  		return store.NewHashError("foo", "1234", "5678")
   394  	})
   395  	defer restore()
   396  
   397  	snap := &snap.Info{}
   398  	snap.RealName = "foo"
   399  	snap.AnonDownloadURL = "anon-url"
   400  	snap.DownloadURL = "AUTH-URL"
   401  	snap.Sha3_384 = ""
   402  	snap.Size = int64(len(partialContentStr) + 1)
   403  
   404  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   405  	err := ioutil.WriteFile(targetFn+".partial", []byte(partialContentStr), 0644)
   406  	c.Assert(err, IsNil)
   407  
   408  	err = s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   409  	c.Assert(err, NotNil)
   410  	c.Assert(err, ErrorMatches, `sha3-384 mismatch for "foo": got 1234 but expected 5678`)
   411  	c.Assert(n, Equals, 2)
   412  }
   413  
   414  func (s *storeDownloadSuite) TestAuthenticatedDownloadDoesNotUseAnonURL(c *C) {
   415  	expectedContent := []byte("I was downloaded")
   416  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, _ *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   417  		// check user is pass and auth url is used
   418  		c.Check(user, Equals, s.user)
   419  		c.Check(url, Equals, "AUTH-URL")
   420  
   421  		w.Write(expectedContent)
   422  		return nil
   423  	})
   424  	defer restore()
   425  
   426  	snap := &snap.Info{}
   427  	snap.RealName = "foo"
   428  	snap.AnonDownloadURL = "anon-url"
   429  	snap.DownloadURL = "AUTH-URL"
   430  	snap.Size = int64(len(expectedContent))
   431  
   432  	path := filepath.Join(c.MkDir(), "downloaded-file")
   433  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, s.user, nil)
   434  	c.Assert(err, IsNil)
   435  	defer os.Remove(path)
   436  
   437  	c.Assert(path, testutil.FileEquals, expectedContent)
   438  }
   439  
   440  func (s *storeDownloadSuite) TestAuthenticatedDeviceDoesNotUseAnonURL(c *C) {
   441  	expectedContent := []byte("I was downloaded")
   442  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   443  		// check auth url is used
   444  		c.Check(url, Equals, "AUTH-URL")
   445  
   446  		w.Write(expectedContent)
   447  		return nil
   448  	})
   449  	defer restore()
   450  
   451  	snap := &snap.Info{}
   452  	snap.RealName = "foo"
   453  	snap.AnonDownloadURL = "anon-url"
   454  	snap.DownloadURL = "AUTH-URL"
   455  	snap.Size = int64(len(expectedContent))
   456  
   457  	dauthCtx := &testDauthContext{c: c, device: s.device}
   458  	sto := store.New(&store.Config{}, dauthCtx)
   459  
   460  	path := filepath.Join(c.MkDir(), "downloaded-file")
   461  	err := sto.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, nil)
   462  	c.Assert(err, IsNil)
   463  	defer os.Remove(path)
   464  
   465  	c.Assert(path, testutil.FileEquals, expectedContent)
   466  }
   467  
   468  func (s *storeDownloadSuite) TestLocalUserDownloadUsesAnonURL(c *C) {
   469  	expectedContentStr := "I was downloaded"
   470  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   471  		c.Check(url, Equals, "anon-url")
   472  
   473  		w.Write([]byte(expectedContentStr))
   474  		return nil
   475  	})
   476  	defer restore()
   477  
   478  	snap := &snap.Info{}
   479  	snap.RealName = "foo"
   480  	snap.AnonDownloadURL = "anon-url"
   481  	snap.DownloadURL = "AUTH-URL"
   482  	snap.Size = int64(len(expectedContentStr))
   483  
   484  	path := filepath.Join(c.MkDir(), "downloaded-file")
   485  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, s.localUser, nil)
   486  	c.Assert(err, IsNil)
   487  	defer os.Remove(path)
   488  
   489  	c.Assert(path, testutil.FileEquals, expectedContentStr)
   490  }
   491  
   492  func (s *storeDownloadSuite) TestDownloadFails(c *C) {
   493  	var tmpfile *os.File
   494  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   495  		tmpfile = w.(*os.File)
   496  		return fmt.Errorf("uh, it failed")
   497  	})
   498  	defer restore()
   499  
   500  	snap := &snap.Info{}
   501  	snap.RealName = "foo"
   502  	snap.AnonDownloadURL = "anon-url"
   503  	snap.DownloadURL = "AUTH-URL"
   504  	snap.Size = 1
   505  	// simulate a failed download
   506  	path := filepath.Join(c.MkDir(), "downloaded-file")
   507  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, nil)
   508  	c.Assert(err, ErrorMatches, "uh, it failed")
   509  	// ... and ensure that the tempfile is removed
   510  	c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false)
   511  	// ... and not because it succeeded either
   512  	c.Assert(osutil.FileExists(path), Equals, false)
   513  }
   514  
   515  func (s *storeDownloadSuite) TestDownloadFailsLeavePartial(c *C) {
   516  	var tmpfile *os.File
   517  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   518  		tmpfile = w.(*os.File)
   519  		w.Write([]byte{'X'}) // so it's not empty
   520  		return fmt.Errorf("uh, it failed")
   521  	})
   522  	defer restore()
   523  
   524  	snap := &snap.Info{}
   525  	snap.RealName = "foo"
   526  	snap.AnonDownloadURL = "anon-url"
   527  	snap.DownloadURL = "AUTH-URL"
   528  	snap.Size = 1
   529  	// simulate a failed download
   530  	path := filepath.Join(c.MkDir(), "downloaded-file")
   531  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, &store.DownloadOptions{LeavePartialOnError: true})
   532  	c.Assert(err, ErrorMatches, "uh, it failed")
   533  	// ... and ensure that the tempfile is *NOT* removed
   534  	c.Assert(osutil.FileExists(tmpfile.Name()), Equals, true)
   535  	// ... but the target path isn't there
   536  	c.Assert(osutil.FileExists(path), Equals, false)
   537  }
   538  
   539  func (s *storeDownloadSuite) TestDownloadFailsDoesNotLeavePartialIfEmpty(c *C) {
   540  	var tmpfile *os.File
   541  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   542  		tmpfile = w.(*os.File)
   543  		// no write, so the partial is empty
   544  		return fmt.Errorf("uh, it failed")
   545  	})
   546  	defer restore()
   547  
   548  	snap := &snap.Info{}
   549  	snap.RealName = "foo"
   550  	snap.AnonDownloadURL = "anon-url"
   551  	snap.DownloadURL = "AUTH-URL"
   552  	snap.Size = 1
   553  	// simulate a failed download
   554  	path := filepath.Join(c.MkDir(), "downloaded-file")
   555  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, &store.DownloadOptions{LeavePartialOnError: true})
   556  	c.Assert(err, ErrorMatches, "uh, it failed")
   557  	// ... and ensure that the tempfile *is* removed
   558  	c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false)
   559  	// ... and the target path isn't there
   560  	c.Assert(osutil.FileExists(path), Equals, false)
   561  }
   562  
   563  func (s *storeDownloadSuite) TestDownloadSyncFails(c *C) {
   564  	var tmpfile *os.File
   565  	restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   566  		tmpfile = w.(*os.File)
   567  		w.Write([]byte("sync will fail"))
   568  		err := tmpfile.Close()
   569  		c.Assert(err, IsNil)
   570  		return nil
   571  	})
   572  	defer restore()
   573  
   574  	snap := &snap.Info{}
   575  	snap.RealName = "foo"
   576  	snap.AnonDownloadURL = "anon-url"
   577  	snap.DownloadURL = "AUTH-URL"
   578  	snap.Size = int64(len("sync will fail"))
   579  
   580  	// simulate a failed sync
   581  	path := filepath.Join(c.MkDir(), "downloaded-file")
   582  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, nil)
   583  	c.Assert(err, ErrorMatches, `(sync|fsync:) .*`)
   584  	// ... and ensure that the tempfile is removed
   585  	c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false)
   586  	// ... because it's been renamed to the target path already
   587  	c.Assert(osutil.FileExists(path), Equals, true)
   588  }
   589  
   590  var downloadDeltaTests = []struct {
   591  	info          snap.DownloadInfo
   592  	authenticated bool
   593  	deviceSession bool
   594  	useLocalUser  bool
   595  	format        string
   596  	expectedURL   string
   597  	expectError   bool
   598  }{{
   599  	// An unauthenticated request downloads the anonymous delta url.
   600  	info: snap.DownloadInfo{
   601  		Sha3_384: "sha3",
   602  		Deltas: []snap.DeltaInfo{
   603  			{AnonDownloadURL: "anon-delta-url", Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   604  		},
   605  	},
   606  	authenticated: false,
   607  	deviceSession: false,
   608  	format:        "xdelta3",
   609  	expectedURL:   "anon-delta-url",
   610  	expectError:   false,
   611  }, {
   612  	// An authenticated request downloads the authenticated delta url.
   613  	info: snap.DownloadInfo{
   614  		Sha3_384: "sha3",
   615  		Deltas: []snap.DeltaInfo{
   616  			{AnonDownloadURL: "anon-delta-url", DownloadURL: "auth-delta-url", Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   617  		},
   618  	},
   619  	authenticated: true,
   620  	deviceSession: false,
   621  	useLocalUser:  false,
   622  	format:        "xdelta3",
   623  	expectedURL:   "auth-delta-url",
   624  	expectError:   false,
   625  }, {
   626  	// A device-authenticated request downloads the authenticated delta url.
   627  	info: snap.DownloadInfo{
   628  		Sha3_384: "sha3",
   629  		Deltas: []snap.DeltaInfo{
   630  			{AnonDownloadURL: "anon-delta-url", DownloadURL: "auth-delta-url", Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   631  		},
   632  	},
   633  	authenticated: false,
   634  	deviceSession: true,
   635  	useLocalUser:  false,
   636  	format:        "xdelta3",
   637  	expectedURL:   "auth-delta-url",
   638  	expectError:   false,
   639  }, {
   640  	// A local authenticated request downloads the anonymous delta url.
   641  	info: snap.DownloadInfo{
   642  		Sha3_384: "sha3",
   643  		Deltas: []snap.DeltaInfo{
   644  			{AnonDownloadURL: "anon-delta-url", Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   645  		},
   646  	},
   647  	authenticated: true,
   648  	deviceSession: false,
   649  	useLocalUser:  true,
   650  	format:        "xdelta3",
   651  	expectedURL:   "anon-delta-url",
   652  	expectError:   false,
   653  }, {
   654  	// An error is returned if more than one matching delta is returned by the store,
   655  	// though this may be handled in the future.
   656  	info: snap.DownloadInfo{
   657  		Sha3_384: "sha3",
   658  		Deltas: []snap.DeltaInfo{
   659  			{DownloadURL: "xdelta3-delta-url", Format: "xdelta3", FromRevision: 24, ToRevision: 25},
   660  			{DownloadURL: "bsdiff-delta-url", Format: "xdelta3", FromRevision: 25, ToRevision: 26},
   661  		},
   662  	},
   663  	authenticated: false,
   664  	deviceSession: false,
   665  	format:        "xdelta3",
   666  	expectedURL:   "",
   667  	expectError:   true,
   668  }, {
   669  	// If the supported format isn't available, an error is returned.
   670  	info: snap.DownloadInfo{
   671  		Sha3_384: "sha3",
   672  		Deltas: []snap.DeltaInfo{
   673  			{DownloadURL: "xdelta3-delta-url", Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   674  			{DownloadURL: "ydelta-delta-url", Format: "ydelta", FromRevision: 24, ToRevision: 26},
   675  		},
   676  	},
   677  	authenticated: false,
   678  	deviceSession: false,
   679  	format:        "bsdiff",
   680  	expectedURL:   "",
   681  	expectError:   true,
   682  }}
   683  
   684  func (s *storeDownloadSuite) TestDownloadDelta(c *C) {
   685  	origUseDeltas := os.Getenv("SNAPD_USE_DELTAS_EXPERIMENTAL")
   686  	defer os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", origUseDeltas)
   687  	c.Assert(os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", "1"), IsNil)
   688  
   689  	dauthCtx := &testDauthContext{c: c}
   690  	sto := store.New(nil, dauthCtx)
   691  
   692  	for _, testCase := range downloadDeltaTests {
   693  		sto.SetDeltaFormat(testCase.format)
   694  		restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, _ *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   695  			c.Check(dlOpts, DeepEquals, &store.DownloadOptions{IsAutoRefresh: true})
   696  			expectedUser := s.user
   697  			if testCase.useLocalUser {
   698  				expectedUser = s.localUser
   699  			}
   700  			if !testCase.authenticated {
   701  				expectedUser = nil
   702  			}
   703  			c.Check(user, Equals, expectedUser)
   704  			c.Check(url, Equals, testCase.expectedURL)
   705  			w.Write([]byte("I was downloaded"))
   706  			return nil
   707  		})
   708  		defer restore()
   709  
   710  		w, err := ioutil.TempFile("", "")
   711  		c.Assert(err, IsNil)
   712  		defer os.Remove(w.Name())
   713  
   714  		dauthCtx.device = nil
   715  		if testCase.deviceSession {
   716  			dauthCtx.device = s.device
   717  		}
   718  
   719  		authedUser := s.user
   720  		if testCase.useLocalUser {
   721  			authedUser = s.localUser
   722  		}
   723  		if !testCase.authenticated {
   724  			authedUser = nil
   725  		}
   726  
   727  		err = sto.DownloadDelta("snapname", &testCase.info, w, nil, authedUser, &store.DownloadOptions{IsAutoRefresh: true})
   728  
   729  		if testCase.expectError {
   730  			c.Assert(err, NotNil)
   731  		} else {
   732  			c.Assert(err, IsNil)
   733  			c.Assert(w.Name(), testutil.FileEquals, "I was downloaded")
   734  		}
   735  	}
   736  }
   737  
   738  var applyDeltaTests = []struct {
   739  	deltaInfo       snap.DeltaInfo
   740  	currentRevision uint
   741  	error           string
   742  }{{
   743  	// A supported delta format can be applied.
   744  	deltaInfo:       snap.DeltaInfo{Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   745  	currentRevision: 24,
   746  	error:           "",
   747  }, {
   748  	// An error is returned if the expected current snap does not exist on disk.
   749  	deltaInfo:       snap.DeltaInfo{Format: "xdelta3", FromRevision: 24, ToRevision: 26},
   750  	currentRevision: 23,
   751  	error:           "snap \"foo\" revision 24 not found",
   752  }, {
   753  	// An error is returned if the format is not supported.
   754  	deltaInfo:       snap.DeltaInfo{Format: "nodelta", FromRevision: 24, ToRevision: 26},
   755  	currentRevision: 24,
   756  	error:           "cannot apply unsupported delta format \"nodelta\" (only xdelta3 currently)",
   757  }}
   758  
   759  func (s *storeDownloadSuite) TestApplyDelta(c *C) {
   760  	for _, testCase := range applyDeltaTests {
   761  		name := "foo"
   762  		currentSnapName := fmt.Sprintf("%s_%d.snap", name, testCase.currentRevision)
   763  		currentSnapPath := filepath.Join(dirs.SnapBlobDir, currentSnapName)
   764  		targetSnapName := fmt.Sprintf("%s_%d.snap", name, testCase.deltaInfo.ToRevision)
   765  		targetSnapPath := filepath.Join(dirs.SnapBlobDir, targetSnapName)
   766  		err := os.MkdirAll(filepath.Dir(currentSnapPath), 0755)
   767  		c.Assert(err, IsNil)
   768  		err = ioutil.WriteFile(currentSnapPath, nil, 0644)
   769  		c.Assert(err, IsNil)
   770  		deltaPath := filepath.Join(dirs.SnapBlobDir, "the.delta")
   771  		err = ioutil.WriteFile(deltaPath, nil, 0644)
   772  		c.Assert(err, IsNil)
   773  		// When testing a case where the call to the external
   774  		// xdelta3 is successful,
   775  		// simulate the resulting .partial.
   776  		if testCase.error == "" {
   777  			err = ioutil.WriteFile(targetSnapPath+".partial", nil, 0644)
   778  			c.Assert(err, IsNil)
   779  		}
   780  
   781  		// make a fresh store object to circumvent the caching of xdelta3 info
   782  		// between test cases
   783  		sto := &store.Store{}
   784  		err = store.ApplyDelta(sto, name, deltaPath, &testCase.deltaInfo, targetSnapPath, "")
   785  
   786  		if testCase.error == "" {
   787  			c.Assert(err, IsNil)
   788  			c.Assert(s.mockXDelta.Calls(), DeepEquals, [][]string{
   789  				// since we don't cache xdelta3 in this test, we always check if
   790  				// xdelta3 config is successful before using xdelta3 (and at
   791  				// that point cache xdelta3 and don't call config again)
   792  				{"xdelta3", "config"},
   793  				{"xdelta3", "-d", "-s", currentSnapPath, deltaPath, targetSnapPath + ".partial"},
   794  			})
   795  			c.Assert(osutil.FileExists(targetSnapPath+".partial"), Equals, false)
   796  			st, err := os.Stat(targetSnapPath)
   797  			c.Assert(err, IsNil)
   798  			c.Check(st.Mode(), Equals, os.FileMode(0600))
   799  			c.Assert(os.Remove(targetSnapPath), IsNil)
   800  		} else {
   801  			c.Assert(err, NotNil)
   802  			c.Assert(err.Error()[0:len(testCase.error)], Equals, testCase.error)
   803  			c.Assert(osutil.FileExists(targetSnapPath+".partial"), Equals, false)
   804  			c.Assert(osutil.FileExists(targetSnapPath), Equals, false)
   805  		}
   806  		c.Assert(os.Remove(currentSnapPath), IsNil)
   807  		c.Assert(os.Remove(deltaPath), IsNil)
   808  	}
   809  }
   810  
   811  type cacheObserver struct {
   812  	inCache map[string]bool
   813  
   814  	gets []string
   815  	puts []string
   816  }
   817  
   818  func (co *cacheObserver) Get(cacheKey, targetPath string) error {
   819  	co.gets = append(co.gets, fmt.Sprintf("%s:%s", cacheKey, targetPath))
   820  	if !co.inCache[cacheKey] {
   821  		return fmt.Errorf("cannot find %s in cache", cacheKey)
   822  	}
   823  	return nil
   824  }
   825  func (co *cacheObserver) GetPath(cacheKey string) string {
   826  	return ""
   827  }
   828  func (co *cacheObserver) Put(cacheKey, sourcePath string) error {
   829  	co.puts = append(co.puts, fmt.Sprintf("%s:%s", cacheKey, sourcePath))
   830  	return nil
   831  }
   832  
   833  func (s *storeDownloadSuite) TestDownloadCacheHit(c *C) {
   834  	obs := &cacheObserver{inCache: map[string]bool{"the-snaps-sha3_384": true}}
   835  	restore := s.store.MockCacher(obs)
   836  	defer restore()
   837  
   838  	restore = store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   839  		c.Fatalf("download should not be called when results come from the cache")
   840  		return nil
   841  	})
   842  	defer restore()
   843  
   844  	snap := &snap.Info{}
   845  	snap.Sha3_384 = "the-snaps-sha3_384"
   846  
   847  	path := filepath.Join(c.MkDir(), "downloaded-file")
   848  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, nil)
   849  	c.Assert(err, IsNil)
   850  
   851  	c.Check(obs.gets, DeepEquals, []string{fmt.Sprintf("%s:%s", snap.Sha3_384, path)})
   852  	c.Check(obs.puts, IsNil)
   853  }
   854  
   855  func (s *storeDownloadSuite) TestDownloadCacheMiss(c *C) {
   856  	obs := &cacheObserver{inCache: map[string]bool{}}
   857  	restore := s.store.MockCacher(obs)
   858  	defer restore()
   859  
   860  	downloadWasCalled := false
   861  	restore = store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
   862  		downloadWasCalled = true
   863  		return nil
   864  	})
   865  	defer restore()
   866  
   867  	snap := &snap.Info{}
   868  	snap.Sha3_384 = "the-snaps-sha3_384"
   869  
   870  	path := filepath.Join(c.MkDir(), "downloaded-file")
   871  	err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, nil)
   872  	c.Assert(err, IsNil)
   873  	c.Check(downloadWasCalled, Equals, true)
   874  
   875  	c.Check(obs.gets, DeepEquals, []string{fmt.Sprintf("the-snaps-sha3_384:%s", path)})
   876  	c.Check(obs.puts, DeepEquals, []string{fmt.Sprintf("the-snaps-sha3_384:%s", path)})
   877  }
   878  
   879  func (s *storeDownloadSuite) TestDownloadStreamOK(c *C) {
   880  	expectedContent := []byte("I was downloaded")
   881  	restore := store.MockDoDownloadReq(func(ctx context.Context, url *url.URL, cdnHeader string, resume int64, s *store.Store, user *auth.UserState) (*http.Response, error) {
   882  		c.Check(url.String(), Equals, "http://anon-url")
   883  		r := &http.Response{
   884  			Body: ioutil.NopCloser(bytes.NewReader(expectedContent[resume:])),
   885  		}
   886  		if resume > 0 {
   887  			r.StatusCode = 206
   888  		} else {
   889  			r.StatusCode = 200
   890  		}
   891  		return r, nil
   892  	})
   893  	defer restore()
   894  
   895  	snap := &snap.Info{}
   896  	snap.RealName = "foo"
   897  	snap.AnonDownloadURL = "http://anon-url"
   898  	snap.DownloadURL = "AUTH-URL"
   899  	snap.Size = int64(len(expectedContent))
   900  
   901  	stream, status, err := s.store.DownloadStream(context.TODO(), "foo", &snap.DownloadInfo, 0, nil)
   902  	c.Assert(err, IsNil)
   903  	c.Assert(status, Equals, 200)
   904  
   905  	buf := new(bytes.Buffer)
   906  	buf.ReadFrom(stream)
   907  	c.Check(buf.String(), Equals, string(expectedContent))
   908  
   909  	stream, status, err = s.store.DownloadStream(context.TODO(), "foo", &snap.DownloadInfo, 2, nil)
   910  	c.Assert(err, IsNil)
   911  	c.Check(status, Equals, 206)
   912  
   913  	buf = new(bytes.Buffer)
   914  	buf.ReadFrom(stream)
   915  	c.Check(buf.String(), Equals, string(expectedContent[2:]))
   916  }
   917  
   918  func (s *storeDownloadSuite) TestDownloadStreamCachedOK(c *C) {
   919  	expectedContent := []byte("I was NOT downloaded")
   920  	defer store.MockDoDownloadReq(func(context.Context, *url.URL, string, int64, *store.Store, *auth.UserState) (*http.Response, error) {
   921  		c.Fatalf("should not be here")
   922  		return nil, nil
   923  	})()
   924  
   925  	c.Assert(os.MkdirAll(dirs.SnapDownloadCacheDir, 0700), IsNil)
   926  	c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDownloadCacheDir, "sha3_384-of-foo"), expectedContent, 0600), IsNil)
   927  
   928  	cache := store.NewCacheManager(dirs.SnapDownloadCacheDir, 1)
   929  	defer s.store.MockCacher(cache)()
   930  
   931  	snap := &snap.Info{}
   932  	snap.RealName = "foo"
   933  	snap.AnonDownloadURL = "http://anon-url"
   934  	snap.DownloadURL = "AUTH-URL"
   935  	snap.Size = int64(len(expectedContent))
   936  	snap.Sha3_384 = "sha3_384-of-foo"
   937  
   938  	stream, status, err := s.store.DownloadStream(context.TODO(), "foo", &snap.DownloadInfo, 0, nil)
   939  	c.Check(err, IsNil)
   940  	c.Check(status, Equals, 200)
   941  
   942  	buf := new(bytes.Buffer)
   943  	buf.ReadFrom(stream)
   944  	c.Check(buf.String(), Equals, string(expectedContent))
   945  
   946  	stream, status, err = s.store.DownloadStream(context.TODO(), "foo", &snap.DownloadInfo, 2, nil)
   947  	c.Assert(err, IsNil)
   948  	c.Check(status, Equals, 206)
   949  
   950  	buf = new(bytes.Buffer)
   951  	buf.ReadFrom(stream)
   952  	c.Check(buf.String(), Equals, string(expectedContent[2:]))
   953  }
   954  
   955  func (s *storeDownloadSuite) TestDownloadTimeout(c *C) {
   956  	var mockServer *httptest.Server
   957  
   958  	restore := store.MockDownloadSpeedParams(1*time.Second, 32768)
   959  	defer restore()
   960  
   961  	// our mock download content
   962  	buf := make([]byte, 65535)
   963  
   964  	h := crypto.SHA3_384.New()
   965  	io.Copy(h, bytes.NewBuffer(buf))
   966  
   967  	quit := make(chan bool)
   968  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   969  		w.Header().Add("Content-Length", fmt.Sprintf("%d", len(buf)))
   970  		w.WriteHeader(200)
   971  
   972  		// push enough data to fill in internal buffers, so that download code
   973  		// hits io.Copy over the body and gets stuck there, and not immediately
   974  		// on doRequest.
   975  		w.Write(buf[:20000])
   976  
   977  		// block the handler
   978  		select {
   979  		case <-quit:
   980  		case <-time.After(10 * time.Second):
   981  			c.Fatalf("unexpected server timeout")
   982  		}
   983  		mockServer.CloseClientConnections()
   984  	}))
   985  
   986  	c.Assert(mockServer, NotNil)
   987  
   988  	snap := &snap.Info{}
   989  	snap.RealName = "foo"
   990  	snap.AnonDownloadURL = mockServer.URL
   991  	snap.DownloadURL = "AUTH-URL"
   992  	snap.Sha3_384 = fmt.Sprintf("%x", h.Sum(nil))
   993  	snap.Size = 50000
   994  
   995  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
   996  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
   997  	ok, speed := store.IsTransferSpeedError(err)
   998  	c.Assert(ok, Equals, true)
   999  	// in reality speed can be 0, but here it's an extra sanity check.
  1000  	c.Check(speed > 1, Equals, true)
  1001  	c.Check(speed < 32768, Equals, true)
  1002  	close(quit)
  1003  	defer mockServer.Close()
  1004  }
  1005  
  1006  func (s *storeDownloadSuite) TestTransferSpeedMonitoringWriterHappy(c *C) {
  1007  	origCtx := context.TODO()
  1008  	w, ctx := store.NewTransferSpeedMonitoringWriterAndContext(origCtx, 50*time.Millisecond, 1)
  1009  
  1010  	data := []byte{0, 0, 0, 0, 0}
  1011  	quit := w.Monitor()
  1012  
  1013  	// write a few bytes every ~5ms, this should satisfy >=1 speed in 50ms
  1014  	// measure windows defined above; 100 iterations ensures we hit a few
  1015  	// measurement windows.
  1016  	for i := 0; i < 100; i++ {
  1017  		n, err := w.Write(data)
  1018  		c.Assert(err, IsNil)
  1019  		c.Assert(n, Equals, len(data))
  1020  		time.Sleep(5 * time.Millisecond)
  1021  	}
  1022  	close(quit)
  1023  	c.Check(store.Cancelled(ctx), Equals, false)
  1024  	c.Check(w.Err(), IsNil)
  1025  
  1026  	// we should hit at least 100*5/50 = 10 measurement windows
  1027  	c.Assert(w.MeasuredWindowsCount() >= 10, Equals, true, Commentf("%d", w.MeasuredWindowsCount()))
  1028  }
  1029  
  1030  func (s *storeDownloadSuite) TestTransferSpeedMonitoringWriterUnhappy(c *C) {
  1031  	origCtx := context.TODO()
  1032  	w, ctx := store.NewTransferSpeedMonitoringWriterAndContext(origCtx, 50*time.Millisecond, 1000)
  1033  
  1034  	data := []byte{0}
  1035  	quit := w.Monitor()
  1036  
  1037  	// write just one byte every ~5ms, this will trigger download timeout
  1038  	// since the writer expects 1000 bytes per 50ms as defined above.
  1039  	for i := 0; i < 100; i++ {
  1040  		n, err := w.Write(data)
  1041  		c.Assert(err, IsNil)
  1042  		c.Assert(n, Equals, len(data))
  1043  		time.Sleep(5 * time.Millisecond)
  1044  	}
  1045  	close(quit)
  1046  	c.Check(store.Cancelled(ctx), Equals, true)
  1047  	terr, _ := store.IsTransferSpeedError(w.Err())
  1048  	c.Assert(terr, Equals, true)
  1049  	c.Check(w.Err(), ErrorMatches, "download too slow: .* bytes/sec")
  1050  }
  1051  
  1052  func (s *storeDownloadSuite) TestDownloadTimeoutOnHeaders(c *C) {
  1053  	restore := httputil.MockResponseHeaderTimeout(250 * time.Millisecond)
  1054  	defer restore()
  1055  
  1056  	var mockServer *httptest.Server
  1057  
  1058  	quit := make(chan bool)
  1059  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1060  		// block the handler, do not send response headers.
  1061  		select {
  1062  		case <-quit:
  1063  		case <-time.After(30 * time.Second):
  1064  			// we expect to hit ResponseHeaderTimeout first
  1065  			c.Fatalf("unexpected")
  1066  		}
  1067  		mockServer.CloseClientConnections()
  1068  	}))
  1069  	c.Assert(mockServer, NotNil)
  1070  	defer mockServer.Close()
  1071  
  1072  	snap := &snap.Info{}
  1073  	snap.RealName = "foo"
  1074  	snap.AnonDownloadURL = mockServer.URL
  1075  	snap.DownloadURL = "AUTH-URL"
  1076  	snap.Sha3_384 = "1234"
  1077  	snap.Size = 50000
  1078  
  1079  	targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
  1080  	err := s.store.Download(s.ctx, "foo", targetFn, &snap.DownloadInfo, nil, nil, nil)
  1081  	close(quit)
  1082  	c.Assert(err, ErrorMatches, `.*net/http: timeout awaiting response headers`)
  1083  }