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 }