github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/kvm/sync_internal_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package kvm
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"path"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/juju/clock/testclock"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	gc "gopkg.in/check.v1"
    22  
    23  	"github.com/juju/juju/environs/imagedownloads"
    24  )
    25  
    26  // syncInternalSuite is gocheck boilerplate.
    27  type syncInternalSuite struct {
    28  	testing.IsolationSuite
    29  }
    30  
    31  var _ = gc.Suite(&syncInternalSuite{})
    32  
    33  const imageContents = "fake img file"
    34  
    35  func (syncInternalSuite) TestFetcher(c *gc.C) {
    36  	ts := newTestServer()
    37  	defer ts.Close()
    38  
    39  	md := newTestMetadata(ts.URL)
    40  
    41  	tmpdir, pathfinder, ok := newTmpdir()
    42  	if !ok {
    43  		c.Fatal("failed to setup temp dir in test")
    44  	}
    45  	defer func() {
    46  		err := os.RemoveAll(tmpdir)
    47  		if err != nil {
    48  			c.Errorf("got error %q when removing tmpdir %q",
    49  				err.Error(),
    50  				tmpdir)
    51  		}
    52  	}()
    53  
    54  	fetcher, err := newDefaultFetcher(md, pathfinder, nil)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  
    57  	// setup a fake command runner.
    58  	stub := runStub{}
    59  	fetcher.image.runCmd = stub.Run
    60  
    61  	err = fetcher.Fetch()
    62  	c.Assert(err, jc.ErrorIsNil)
    63  
    64  	_, err = os.Stat(fetcher.image.tmpFile.Name())
    65  	c.Check(os.IsNotExist(err), jc.IsTrue)
    66  
    67  	// Check that our call was made as expected.
    68  	c.Assert(stub.Calls(), gc.HasLen, 1)
    69  	c.Assert(stub.Calls()[0], gc.Matches, "qemu-img convert -f qcow2 .*/juju-kvm-server.img-.* .*/guests/spammy-archless-backing-file.qcow")
    70  
    71  }
    72  
    73  func (syncInternalSuite) TestFetcherWriteFails(c *gc.C) {
    74  	ts := newTestServer()
    75  	defer ts.Close()
    76  
    77  	md := newTestMetadata(ts.URL)
    78  
    79  	tmpdir, pathfinder, ok := newTmpdir()
    80  	if !ok {
    81  		c.Fatal("failed to setup temp dir in test")
    82  	}
    83  	defer func() {
    84  		err := os.RemoveAll(tmpdir)
    85  		if err != nil {
    86  			c.Errorf("got error %q when removing tmpdir %q",
    87  				err.Error(),
    88  				tmpdir)
    89  		}
    90  	}()
    91  
    92  	fetcher, err := newDefaultFetcher(md, pathfinder, nil)
    93  	c.Assert(err, jc.ErrorIsNil)
    94  
    95  	// setup a fake command runner.
    96  	stub := runStub{err: errors.Errorf("boom")}
    97  	fetcher.image.runCmd = stub.Run
    98  
    99  	// Make sure we got the error.
   100  	err = fetcher.Fetch()
   101  	c.Assert(err, gc.ErrorMatches, "boom")
   102  
   103  	// Check that our call was made as expected.
   104  	c.Assert(stub.Calls(), gc.HasLen, 1)
   105  	c.Assert(stub.Calls()[0], gc.Matches, "qemu-img convert -f qcow2 .*/juju-kvm-server.img-.* .*/guests/spammy-archless-backing-file.qcow")
   106  
   107  }
   108  
   109  func (syncInternalSuite) TestFetcherInvalidSHA(c *gc.C) {
   110  	ts := newTestServer()
   111  	defer ts.Close()
   112  
   113  	md := newTestMetadata(ts.URL)
   114  	md.SHA256 = "invalid"
   115  
   116  	tmpdir, pathfinder, ok := newTmpdir()
   117  	if !ok {
   118  		c.Fatal("failed to setup temp dir in test")
   119  	}
   120  	defer func() {
   121  		err := os.RemoveAll(tmpdir)
   122  		if err != nil {
   123  			c.Errorf("got error %q when removing tmpdir %q",
   124  				err.Error(),
   125  				tmpdir)
   126  		}
   127  	}()
   128  
   129  	fetcher, err := newDefaultFetcher(md, pathfinder, nil)
   130  	c.Assert(err, jc.ErrorIsNil)
   131  
   132  	err = fetcher.Fetch()
   133  	c.Assert(err, gc.ErrorMatches, "hash sum mismatch for /tmp/juju-kvm-.*")
   134  }
   135  
   136  func (syncInternalSuite) TestFetcherNotFound(c *gc.C) {
   137  	ts := newTestServer()
   138  	defer ts.Close()
   139  
   140  	md := newTestMetadata(ts.URL)
   141  	md.Path = "not-there"
   142  
   143  	tmpdir, pathfinder, ok := newTmpdir()
   144  	if !ok {
   145  		c.Fatal("failed to setup temp dir in test")
   146  	}
   147  	defer func() {
   148  		err := os.RemoveAll(tmpdir)
   149  		if err != nil {
   150  			c.Errorf("got error %q when removing tmpdir %q",
   151  				err.Error(),
   152  				tmpdir)
   153  		}
   154  	}()
   155  
   156  	fetcher, err := newDefaultFetcher(md, pathfinder, nil)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  
   159  	err = fetcher.Fetch()
   160  	c.Check(errors.IsNotFound(err), jc.IsTrue)
   161  	c.Assert(err, gc.ErrorMatches, `got 404 fetching image "not-there" not found`)
   162  }
   163  
   164  func newTestMetadata(base string) *imagedownloads.Metadata {
   165  	return &imagedownloads.Metadata{
   166  		Arch:    "archless",
   167  		Release: "spammy",
   168  		Version: "version",
   169  		FType:   "ftype",
   170  		SHA256:  "5e8467e6732923e74de52ef60134ba747aeeb283812c60f69b67f4f79aca1475",
   171  		Path:    "server.img",
   172  		Size:    int64(len(imageContents)),
   173  		BaseURL: base,
   174  	}
   175  }
   176  
   177  func newTestServer() *httptest.Server {
   178  	mtime := time.Unix(1000, 0).UTC()
   179  	imageFile := &fakeFileInfo{
   180  		basename: "series-image.img",
   181  		modtime:  mtime,
   182  		contents: imageContents,
   183  	}
   184  	fs := fakeFS{
   185  		"/": &fakeFileInfo{
   186  			dir:  true,
   187  			ents: []*fakeFileInfo{imageFile},
   188  		},
   189  		"/server.img": imageFile,
   190  	}
   191  	return httptest.NewServer(http.FileServer(fs))
   192  }
   193  
   194  // newTmpdir creates a tmpdir and returns pathfinder func that returns the
   195  // tmpdir.
   196  func newTmpdir() (string, func(string) (string, error), bool) {
   197  	td, err := ioutil.TempDir("", "juju-test-kvm-internalSuite")
   198  	if err != nil {
   199  		return "", nil, false
   200  	}
   201  	pathfinder := func(string) (string, error) { return td, nil }
   202  	return td, pathfinder, true
   203  }
   204  
   205  type fakeFileInfo struct {
   206  	dir      bool
   207  	basename string
   208  	modtime  time.Time
   209  	ents     []*fakeFileInfo
   210  	contents string
   211  	err      error
   212  }
   213  
   214  func (f *fakeFileInfo) Name() string       { return f.basename }
   215  func (f *fakeFileInfo) Sys() interface{}   { return nil }
   216  func (f *fakeFileInfo) ModTime() time.Time { return f.modtime }
   217  func (f *fakeFileInfo) IsDir() bool        { return f.dir }
   218  func (f *fakeFileInfo) Size() int64        { return int64(len(f.contents)) }
   219  func (f *fakeFileInfo) Mode() os.FileMode {
   220  	if f.dir {
   221  		return 0755 | os.ModeDir
   222  	}
   223  	return 0644
   224  }
   225  
   226  type fakeFile struct {
   227  	io.ReadSeeker
   228  	fi     *fakeFileInfo
   229  	path   string // as opened
   230  	entpos int
   231  }
   232  
   233  func (f *fakeFile) Close() error               { return nil }
   234  func (f *fakeFile) Stat() (os.FileInfo, error) { return f.fi, nil }
   235  func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
   236  	if !f.fi.dir {
   237  		return nil, os.ErrInvalid
   238  	}
   239  	var fis []os.FileInfo
   240  
   241  	limit := f.entpos + count
   242  	if count <= 0 || limit > len(f.fi.ents) {
   243  		limit = len(f.fi.ents)
   244  	}
   245  	for ; f.entpos < limit; f.entpos++ {
   246  		fis = append(fis, f.fi.ents[f.entpos])
   247  	}
   248  
   249  	if len(fis) == 0 && count > 0 {
   250  		return fis, io.EOF
   251  	}
   252  	return fis, nil
   253  }
   254  
   255  type fakeFS map[string]*fakeFileInfo
   256  
   257  func (fs fakeFS) Open(name string) (http.File, error) {
   258  	name = path.Clean(name)
   259  	f, ok := fs[name]
   260  	if !ok {
   261  		return nil, os.ErrNotExist
   262  	}
   263  	if f.err != nil {
   264  		return nil, f.err
   265  	}
   266  	return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil
   267  }
   268  
   269  type progressWriterSuite struct {
   270  	testing.IsolationSuite
   271  }
   272  
   273  var _ = gc.Suite(&progressWriterSuite{})
   274  
   275  func (s *progressWriterSuite) TestOnlyPercentChanges(c *gc.C) {
   276  	cbLog := []string{}
   277  	loggingCB := func(msg string) {
   278  		cbLog = append(cbLog, msg)
   279  	}
   280  	clock := testclock.NewClock(time.Date(2007, 1, 1, 10, 20, 30, 1234, time.UTC))
   281  	// We are using clock to actually measure time, not trigger an event, which
   282  	// causes the testclock.Clock to think we're doing something wrong, so we
   283  	// just create one waiter that we'll otherwise ignore.
   284  	ignored := clock.After(10 * time.Second)
   285  	_ = ignored
   286  	writer := progressWriter{
   287  		callback: loggingCB,
   288  		url:      "http://host/path",
   289  		total:    0,
   290  		maxBytes: 100 * 1024 * 1024, // 100 MB
   291  		clock:    clock,
   292  	}
   293  	content := make([]byte, 50*1024)
   294  	// Start the clock before the first tick, that way every tick represents
   295  	// exactly 1ms and 50kiB written.
   296  	now := clock.Now()
   297  	writer.startTime = &now
   298  	for i := 0; i < 2048; i++ {
   299  		clock.Advance(time.Millisecond)
   300  		n, err := writer.Write(content)
   301  		c.Assert(err, jc.ErrorIsNil)
   302  		c.Check(n, gc.Equals, len(content))
   303  	}
   304  	expectedCB := []string{}
   305  	for i := 1; i <= 100; i++ {
   306  		// We tick every 1ms and add 50kiB each time, which is
   307  		// 50*1024 *1000/ 1000/1000  = 51MB/s
   308  		expectedCB = append(expectedCB, fmt.Sprintf("copying http://host/path %d%% (51MB/s)", i))
   309  	}
   310  	// There are 2048 calls to Write, but there should only be 100 calls to progress update
   311  	c.Check(len(cbLog), gc.Equals, 100)
   312  	c.Check(cbLog, gc.DeepEquals, expectedCB)
   313  }