github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/snapshotstate/backend/backend_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018-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 backend_test 21 22 import ( 23 "archive/tar" 24 "archive/zip" 25 "bytes" 26 "context" 27 "crypto" 28 "encoding/json" 29 "errors" 30 "fmt" 31 "io" 32 "io/ioutil" 33 "os" 34 "os/exec" 35 "os/user" 36 "path" 37 "path/filepath" 38 "sort" 39 "strings" 40 "testing" 41 "time" 42 43 "gopkg.in/check.v1" 44 45 "github.com/snapcore/snapd/client" 46 "github.com/snapcore/snapd/dirs" 47 "github.com/snapcore/snapd/logger" 48 "github.com/snapcore/snapd/osutil" 49 "github.com/snapcore/snapd/osutil/sys" 50 "github.com/snapcore/snapd/overlord/snapshotstate/backend" 51 "github.com/snapcore/snapd/snap" 52 "github.com/snapcore/snapd/testutil" 53 ) 54 55 type snapshotSuite struct { 56 root string 57 restore []func() 58 tarPath string 59 isTesting bool 60 } 61 62 // silly wrappers to get better failure messages 63 type isTestingSuite struct{ snapshotSuite } 64 type noTestingSuite struct{ snapshotSuite } 65 66 var _ = check.Suite(&isTestingSuite{snapshotSuite{isTesting: true}}) 67 var _ = check.Suite(&noTestingSuite{snapshotSuite{isTesting: false}}) 68 69 // tie gocheck into testing 70 func TestSnapshot(t *testing.T) { check.TestingT(t) } 71 72 type tableT struct { 73 dir string 74 name string 75 content string 76 } 77 78 func table(si snap.PlaceInfo, homeDir string) []tableT { 79 return []tableT{ 80 { 81 dir: si.DataDir(), 82 name: "foo", 83 content: "versioned system canary\n", 84 }, { 85 dir: si.CommonDataDir(), 86 name: "bar", 87 content: "common system canary\n", 88 }, { 89 dir: si.UserDataDir(homeDir), 90 name: "ufoo", 91 content: "versioned user canary\n", 92 }, { 93 dir: si.UserCommonDataDir(homeDir), 94 name: "ubar", 95 content: "common user canary\n", 96 }, 97 } 98 } 99 100 func (s *snapshotSuite) SetUpTest(c *check.C) { 101 s.root = c.MkDir() 102 103 dirs.SetRootDir(s.root) 104 105 si := snap.MinimalPlaceInfo("hello-snap", snap.R(42)) 106 107 for _, t := range table(si, filepath.Join(dirs.GlobalRootDir, "home/snapuser")) { 108 c.Check(os.MkdirAll(t.dir, 0755), check.IsNil) 109 c.Check(ioutil.WriteFile(filepath.Join(t.dir, t.name), []byte(t.content), 0644), check.IsNil) 110 } 111 112 cur, err := user.Current() 113 c.Assert(err, check.IsNil) 114 115 s.restore = append(s.restore, backend.MockUserLookup(func(username string) (*user.User, error) { 116 if username != "snapuser" { 117 return nil, user.UnknownUserError(username) 118 } 119 rv := *cur 120 rv.Username = username 121 rv.HomeDir = filepath.Join(dirs.GlobalRootDir, "home/snapuser") 122 return &rv, nil 123 }), 124 backend.MockIsTesting(s.isTesting), 125 ) 126 127 s.tarPath, err = exec.LookPath("tar") 128 c.Assert(err, check.IsNil) 129 } 130 131 func (s *snapshotSuite) TearDownTest(c *check.C) { 132 dirs.SetRootDir("") 133 for _, restore := range s.restore { 134 restore() 135 } 136 } 137 138 func hashkeys(snapshot *client.Snapshot) (keys []string) { 139 for k := range snapshot.SHA3_384 { 140 keys = append(keys, k) 141 } 142 sort.Strings(keys) 143 144 return keys 145 } 146 147 func (s *snapshotSuite) TestLastSnapshotID(c *check.C) { 148 // LastSnapshotSetID is happy without any snapshots 149 setID, err := backend.LastSnapshotSetID() 150 c.Assert(err, check.IsNil) 151 c.Check(setID, check.Equals, uint64(0)) 152 153 // create snapshots dir and dummy snapshots 154 os.MkdirAll(dirs.SnapshotsDir, os.ModePerm) 155 for _, name := range []string{ 156 "9_some-snap-1.zip", "1234_not-a-snapshot", "12_other-snap.zip", "3_foo.zip", 157 } { 158 c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, name), []byte{}, 0644), check.IsNil) 159 } 160 setID, err = backend.LastSnapshotSetID() 161 c.Assert(err, check.IsNil) 162 c.Check(setID, check.Equals, uint64(12)) 163 } 164 165 func (s *snapshotSuite) TestLastSnapshotIDErrorOnDirNames(c *check.C) { 166 // we need snapshots dir, otherwise LastSnapshotSetID exits early. 167 c.Assert(os.MkdirAll(dirs.SnapshotsDir, os.ModePerm), check.IsNil) 168 169 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 170 return nil, fmt.Errorf("fail") 171 })() 172 setID, err := backend.LastSnapshotSetID() 173 c.Assert(err, check.ErrorMatches, "fail") 174 c.Check(setID, check.Equals, uint64(0)) 175 } 176 177 func (s *snapshotSuite) TestIsSnapshotFilename(c *check.C) { 178 tests := []struct { 179 name string 180 valid bool 181 setID uint64 182 }{ 183 {"1_foo.zip", true, 1}, 184 {"14_hello-world_6.4_29.zip", true, 14}, 185 {"1_.zip", false, 0}, 186 {"1_foo.zip.bak", false, 0}, 187 {"foo_1_foo.zip", false, 0}, 188 {"foo_bar_baz.zip", false, 0}, 189 {"", false, 0}, 190 {"1_", false, 0}, 191 } 192 193 for _, t := range tests { 194 ok, setID := backend.IsSnapshotFilename(t.name) 195 c.Check(ok, check.Equals, t.valid, check.Commentf("fail: %s", t.name)) 196 c.Check(setID, check.Equals, t.setID, check.Commentf("fail: %s", t.name)) 197 } 198 } 199 200 func (s *snapshotSuite) TestIterBailsIfContextDone(c *check.C) { 201 ctx, cancel := context.WithCancel(context.Background()) 202 cancel() 203 triedToOpenDir := false 204 defer backend.MockOsOpen(func(string) (*os.File, error) { 205 triedToOpenDir = true 206 return nil, nil // deal with it 207 })() 208 209 err := backend.Iter(ctx, nil) 210 c.Check(err, check.Equals, context.Canceled) 211 c.Check(triedToOpenDir, check.Equals, false) 212 } 213 214 func (s *snapshotSuite) TestIterBailsIfContextDoneMidway(c *check.C) { 215 ctx, cancel := context.WithCancel(context.Background()) 216 triedToOpenDir := false 217 defer backend.MockOsOpen(func(string) (*os.File, error) { 218 triedToOpenDir = true 219 return os.Open(os.DevNull) 220 })() 221 readNames := 0 222 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 223 readNames++ 224 cancel() 225 return []string{"hello"}, nil 226 })() 227 triedToOpenSnapshot := false 228 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 229 triedToOpenSnapshot = true 230 return nil, nil 231 })() 232 233 err := backend.Iter(ctx, nil) 234 c.Check(err, check.Equals, context.Canceled) 235 c.Check(triedToOpenDir, check.Equals, true) 236 // bails as soon as 237 c.Check(readNames, check.Equals, 1) 238 c.Check(triedToOpenSnapshot, check.Equals, false) 239 } 240 241 func (s *snapshotSuite) TestIterReturnsOkIfSnapshotsDirNonexistent(c *check.C) { 242 triedToOpenDir := false 243 defer backend.MockOsOpen(func(string) (*os.File, error) { 244 triedToOpenDir = true 245 return nil, os.ErrNotExist 246 })() 247 248 err := backend.Iter(context.Background(), nil) 249 c.Check(err, check.IsNil) 250 c.Check(triedToOpenDir, check.Equals, true) 251 } 252 253 func (s *snapshotSuite) TestIterBailsIfSnapshotsDirFails(c *check.C) { 254 triedToOpenDir := false 255 defer backend.MockOsOpen(func(string) (*os.File, error) { 256 triedToOpenDir = true 257 return nil, os.ErrInvalid 258 })() 259 260 err := backend.Iter(context.Background(), nil) 261 c.Check(err, check.ErrorMatches, "cannot open snapshots directory: invalid argument") 262 c.Check(triedToOpenDir, check.Equals, true) 263 } 264 265 func (s *snapshotSuite) TestIterWarnsOnOpenErrorIfSnapshotNil(c *check.C) { 266 logbuf, restore := logger.MockLogger() 267 defer restore() 268 triedToOpenDir := false 269 defer backend.MockOsOpen(func(string) (*os.File, error) { 270 triedToOpenDir = true 271 return new(os.File), nil 272 })() 273 readNames := 0 274 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 275 readNames++ 276 if readNames > 1 { 277 return nil, io.EOF 278 } 279 return []string{"1_hello.zip"}, nil 280 })() 281 triedToOpenSnapshot := false 282 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 283 triedToOpenSnapshot = true 284 return nil, os.ErrInvalid 285 })() 286 287 calledF := false 288 f := func(snapshot *backend.Reader) error { 289 calledF = true 290 return nil 291 } 292 293 err := backend.Iter(context.Background(), f) 294 // snapshot open errors are not failures: 295 c.Check(err, check.IsNil) 296 c.Check(triedToOpenDir, check.Equals, true) 297 c.Check(readNames, check.Equals, 2) 298 c.Check(triedToOpenSnapshot, check.Equals, true) 299 c.Check(logbuf.String(), check.Matches, `(?m).* Cannot open snapshot "1_hello.zip": invalid argument.`) 300 c.Check(calledF, check.Equals, false) 301 } 302 303 func (s *snapshotSuite) TestIterCallsFuncIfSnapshotNotNil(c *check.C) { 304 logbuf, restore := logger.MockLogger() 305 defer restore() 306 triedToOpenDir := false 307 defer backend.MockOsOpen(func(string) (*os.File, error) { 308 triedToOpenDir = true 309 return new(os.File), nil 310 })() 311 readNames := 0 312 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 313 readNames++ 314 if readNames > 1 { 315 return nil, io.EOF 316 } 317 return []string{"1_hello.zip"}, nil 318 })() 319 triedToOpenSnapshot := false 320 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 321 triedToOpenSnapshot = true 322 // NOTE non-nil reader, and error, returned 323 r := backend.Reader{} 324 r.SetID = 1 325 r.Broken = "xyzzy" 326 return &r, os.ErrInvalid 327 })() 328 329 calledF := false 330 f := func(snapshot *backend.Reader) error { 331 c.Check(snapshot.Broken, check.Equals, "xyzzy") 332 calledF = true 333 return nil 334 } 335 336 err := backend.Iter(context.Background(), f) 337 // snapshot open errors are not failures: 338 c.Check(err, check.IsNil) 339 c.Check(triedToOpenDir, check.Equals, true) 340 c.Check(readNames, check.Equals, 2) 341 c.Check(triedToOpenSnapshot, check.Equals, true) 342 c.Check(logbuf.String(), check.Equals, "") 343 c.Check(calledF, check.Equals, true) 344 } 345 346 func (s *snapshotSuite) TestIterReportsCloseError(c *check.C) { 347 logbuf, restore := logger.MockLogger() 348 defer restore() 349 triedToOpenDir := false 350 defer backend.MockOsOpen(func(string) (*os.File, error) { 351 triedToOpenDir = true 352 return new(os.File), nil 353 })() 354 readNames := 0 355 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 356 readNames++ 357 if readNames > 1 { 358 return nil, io.EOF 359 } 360 return []string{"42_hello.zip"}, nil 361 })() 362 triedToOpenSnapshot := false 363 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 364 triedToOpenSnapshot = true 365 r := backend.Reader{} 366 r.SetID = 42 367 return &r, nil 368 })() 369 370 calledF := false 371 f := func(snapshot *backend.Reader) error { 372 c.Check(snapshot.SetID, check.Equals, uint64(42)) 373 calledF = true 374 return nil 375 } 376 377 err := backend.Iter(context.Background(), f) 378 // snapshot close errors _are_ failures (because they're completely unexpected): 379 c.Check(err, check.Equals, os.ErrInvalid) 380 c.Check(triedToOpenDir, check.Equals, true) 381 c.Check(readNames, check.Equals, 1) // never gets to read another one 382 c.Check(triedToOpenSnapshot, check.Equals, true) 383 c.Check(logbuf.String(), check.Equals, "") 384 c.Check(calledF, check.Equals, true) 385 } 386 387 func readerForFilename(fname string, c *check.C) *backend.Reader { 388 var snapname string 389 var id uint64 390 fn := strings.TrimSuffix(filepath.Base(fname), ".zip") 391 _, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname) 392 c.Assert(err, check.IsNil, check.Commentf(fn)) 393 f, err := os.Open(os.DevNull) 394 c.Assert(err, check.IsNil, check.Commentf(fn)) 395 return &backend.Reader{ 396 File: f, 397 Snapshot: client.Snapshot{ 398 SetID: id, 399 Snap: snapname, 400 }, 401 } 402 } 403 404 func (s *snapshotSuite) TestIterIgnoresSnapshotsWithInvalidNames(c *check.C) { 405 logbuf, restore := logger.MockLogger() 406 defer restore() 407 408 defer backend.MockOsOpen(func(string) (*os.File, error) { 409 return new(os.File), nil 410 })() 411 readNames := 0 412 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 413 readNames++ 414 if readNames > 1 { 415 return nil, io.EOF 416 } 417 return []string{ 418 "_foo.zip", 419 "43_bar.zip", 420 "foo_bar.zip", 421 "bar.", 422 }, nil 423 })() 424 defer backend.MockOpen(func(fname string, setID uint64) (*backend.Reader, error) { 425 return readerForFilename(fname, c), nil 426 })() 427 428 var calledF int 429 f := func(snapshot *backend.Reader) error { 430 calledF++ 431 c.Check(snapshot.SetID, check.Equals, uint64(43)) 432 return nil 433 } 434 435 err := backend.Iter(context.Background(), f) 436 c.Check(err, check.IsNil) 437 c.Check(logbuf.String(), check.Equals, "") 438 c.Check(calledF, check.Equals, 1) 439 } 440 441 func (s *snapshotSuite) TestIterSetIDoverride(c *check.C) { 442 if os.Geteuid() == 0 { 443 c.Skip("this test cannot run as root (runuser will fail)") 444 } 445 logger.SimpleSetup() 446 447 epoch := snap.E("42*") 448 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 449 cfg := map[string]interface{}{"some-setting": false} 450 451 shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"}) 452 c.Assert(err, check.IsNil) 453 c.Check(shw.SetID, check.Equals, uint64(12)) 454 455 snapshotPath := filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip") 456 c.Check(backend.Filename(shw), check.Equals, snapshotPath) 457 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 458 459 // rename the snapshot, verify that set id from the filename is used by the reader. 460 c.Assert(os.Rename(snapshotPath, filepath.Join(dirs.SnapshotsDir, "33_hello.zip")), check.IsNil) 461 462 var calledF int 463 f := func(snapshot *backend.Reader) error { 464 calledF++ 465 c.Check(snapshot.SetID, check.Equals, uint64(uint(33))) 466 c.Check(snapshot.Snap, check.Equals, "hello-snap") 467 return nil 468 } 469 470 c.Assert(backend.Iter(context.Background(), f), check.IsNil) 471 c.Check(calledF, check.Equals, 1) 472 } 473 474 func (s *snapshotSuite) TestList(c *check.C) { 475 logbuf, restore := logger.MockLogger() 476 defer restore() 477 defer backend.MockOsOpen(func(string) (*os.File, error) { return new(os.File), nil })() 478 479 readNames := 0 480 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 481 readNames++ 482 if readNames > 4 { 483 return nil, io.EOF 484 } 485 return []string{ 486 fmt.Sprintf("%d_foo.zip", readNames), 487 fmt.Sprintf("%d_bar.zip", readNames), 488 fmt.Sprintf("%d_baz.zip", readNames), 489 }, nil 490 })() 491 defer backend.MockOpen(func(fn string, setID uint64) (*backend.Reader, error) { 492 var id uint64 493 var snapname string 494 c.Assert(strings.HasSuffix(fn, ".zip"), check.Equals, true) 495 fn = strings.TrimSuffix(filepath.Base(fn), ".zip") 496 _, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname) 497 c.Assert(err, check.IsNil, check.Commentf(fn)) 498 f, err := os.Open(os.DevNull) 499 c.Assert(err, check.IsNil, check.Commentf(fn)) 500 return &backend.Reader{ 501 File: f, 502 Snapshot: client.Snapshot{ 503 SetID: id, 504 Snap: snapname, 505 SnapID: "id-for-" + snapname, 506 Version: "v1.0-" + snapname, 507 Revision: snap.R(int(id)), 508 }, 509 }, nil 510 })() 511 512 type tableT struct { 513 setID uint64 514 snapnames []string 515 numSets int 516 numShots int 517 predicate func(*client.Snapshot) bool 518 } 519 table := []tableT{ 520 {0, nil, 4, 12, nil}, 521 {0, []string{"foo"}, 4, 4, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" }}, 522 {1, nil, 1, 3, func(snapshot *client.Snapshot) bool { return snapshot.SetID == 1 }}, 523 {2, []string{"bar"}, 1, 1, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "bar" && snapshot.SetID == 2 }}, 524 {0, []string{"foo", "bar"}, 4, 8, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" || snapshot.Snap == "bar" }}, 525 } 526 527 for i, t := range table { 528 comm := check.Commentf("%d: %d/%v", i, t.setID, t.snapnames) 529 // reset 530 readNames = 0 531 logbuf.Reset() 532 533 sets, err := backend.List(context.Background(), t.setID, t.snapnames) 534 c.Check(err, check.IsNil, comm) 535 c.Check(readNames, check.Equals, 5, comm) 536 c.Check(logbuf.String(), check.Equals, "", comm) 537 c.Check(sets, check.HasLen, t.numSets, comm) 538 nShots := 0 539 fnTpl := filepath.Join(dirs.SnapshotsDir, "%d_%s_%s_%s.zip") 540 for j, ss := range sets { 541 for k, snapshot := range ss.Snapshots { 542 comm := check.Commentf("%d: %d/%v #%d/%d", i, t.setID, t.snapnames, j, k) 543 if t.predicate != nil { 544 c.Check(t.predicate(snapshot), check.Equals, true, comm) 545 } 546 nShots++ 547 fn := fmt.Sprintf(fnTpl, snapshot.SetID, snapshot.Snap, snapshot.Version, snapshot.Revision) 548 c.Check(backend.Filename(snapshot), check.Equals, fn, comm) 549 c.Check(snapshot.SnapID, check.Equals, "id-for-"+snapshot.Snap) 550 } 551 } 552 c.Check(nShots, check.Equals, t.numShots) 553 } 554 } 555 556 func (s *snapshotSuite) TestAddDirToZipBails(c *check.C) { 557 snapshot := &client.Snapshot{SetID: 42, Snap: "a-snap"} 558 buf, restore := logger.MockLogger() 559 defer restore() 560 // note as the zip is nil this would panic if it didn't bail 561 c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", filepath.Join(s.root, "nonexistent")), check.IsNil) 562 // no log for the non-existent case 563 c.Check(buf.String(), check.Equals, "") 564 buf.Reset() 565 c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", "/etc/passwd"), check.IsNil) 566 c.Check(buf.String(), check.Matches, "(?m).* is not a directory.") 567 } 568 569 func (s *snapshotSuite) TestAddDirToZipTarFails(c *check.C) { 570 d := filepath.Join(s.root, "foo") 571 c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil) 572 c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil) 573 574 ctx, cancel := context.WithCancel(context.Background()) 575 cancel() 576 577 var buf bytes.Buffer 578 z := zip.NewWriter(&buf) 579 c.Assert(backend.AddDirToZip(ctx, nil, z, "", "an/entry", d), check.ErrorMatches, ".* context canceled") 580 } 581 582 func (s *snapshotSuite) TestAddDirToZip(c *check.C) { 583 d := filepath.Join(s.root, "foo") 584 c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil) 585 c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil) 586 c.Assert(ioutil.WriteFile(filepath.Join(d, "bar", "baz"), []byte("hello\n"), 0644), check.IsNil) 587 588 var buf bytes.Buffer 589 z := zip.NewWriter(&buf) 590 snapshot := &client.Snapshot{ 591 SHA3_384: map[string]string{}, 592 } 593 c.Assert(backend.AddDirToZip(context.Background(), snapshot, z, "", "an/entry", d), check.IsNil) 594 z.Close() // write out the central directory 595 596 c.Check(snapshot.SHA3_384, check.HasLen, 1) 597 c.Check(snapshot.SHA3_384["an/entry"], check.HasLen, 96) 598 c.Check(snapshot.Size > 0, check.Equals, true) // actual size most likely system-dependent 599 br := bytes.NewReader(buf.Bytes()) 600 r, err := zip.NewReader(br, int64(br.Len())) 601 c.Assert(err, check.IsNil) 602 c.Check(r.File, check.HasLen, 1) 603 c.Check(r.File[0].Name, check.Equals, "an/entry") 604 } 605 606 func (s *snapshotSuite) TestHappyRoundtrip(c *check.C) { 607 s.testHappyRoundtrip(c, "marker") 608 } 609 610 func (s *snapshotSuite) TestHappyRoundtripNoCommon(c *check.C) { 611 for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) { 612 if _, d := filepath.Split(t.dir); d == "common" { 613 c.Assert(os.RemoveAll(t.dir), check.IsNil) 614 } 615 } 616 s.testHappyRoundtrip(c, "marker") 617 } 618 619 func (s *snapshotSuite) TestHappyRoundtripNoRev(c *check.C) { 620 for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) { 621 if _, d := filepath.Split(t.dir); d == "42" { 622 c.Assert(os.RemoveAll(t.dir), check.IsNil) 623 } 624 } 625 s.testHappyRoundtrip(c, "../common/marker") 626 } 627 628 func (s *snapshotSuite) testHappyRoundtrip(c *check.C, marker string) { 629 if os.Geteuid() == 0 { 630 c.Skip("this test cannot run as root (runuser will fail)") 631 } 632 logger.SimpleSetup() 633 634 epoch := snap.E("42*") 635 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 636 cfg := map[string]interface{}{"some-setting": false} 637 shID := uint64(12) 638 639 shw, err := backend.Save(context.TODO(), shID, info, cfg, []string{"snapuser"}) 640 c.Assert(err, check.IsNil) 641 c.Check(shw.SetID, check.Equals, shID) 642 c.Check(shw.Snap, check.Equals, info.InstanceName()) 643 c.Check(shw.SnapID, check.Equals, info.SnapID) 644 c.Check(shw.Version, check.Equals, info.Version) 645 c.Check(shw.Epoch, check.DeepEquals, epoch) 646 c.Check(shw.Revision, check.Equals, info.Revision) 647 c.Check(shw.Conf, check.DeepEquals, cfg) 648 c.Check(shw.Auto, check.Equals, false) 649 c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 650 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 651 652 shs, err := backend.List(context.TODO(), 0, nil) 653 c.Assert(err, check.IsNil) 654 c.Assert(shs, check.HasLen, 1) 655 c.Assert(shs[0].Snapshots, check.HasLen, 1) 656 657 shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID) 658 c.Assert(err, check.IsNil) 659 defer shr.Close() 660 661 for label, sh := range map[string]*client.Snapshot{"open": &shr.Snapshot, "list": shs[0].Snapshots[0]} { 662 comm := check.Commentf("%q", label) 663 c.Check(sh.SetID, check.Equals, shID, comm) 664 c.Check(sh.Snap, check.Equals, info.InstanceName(), comm) 665 c.Check(sh.SnapID, check.Equals, info.SnapID, comm) 666 c.Check(sh.Version, check.Equals, info.Version, comm) 667 c.Check(sh.Epoch, check.DeepEquals, epoch) 668 c.Check(sh.Revision, check.Equals, info.Revision, comm) 669 c.Check(sh.Conf, check.DeepEquals, cfg, comm) 670 c.Check(sh.SHA3_384, check.DeepEquals, shw.SHA3_384, comm) 671 c.Check(sh.Auto, check.Equals, false) 672 } 673 c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 674 c.Check(shr.Check(context.TODO(), nil), check.IsNil) 675 676 newroot := c.MkDir() 677 c.Assert(os.MkdirAll(filepath.Join(newroot, "home/snapuser"), 0755), check.IsNil) 678 dirs.SetRootDir(newroot) 679 680 var diff = func() *exec.Cmd { 681 cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot) 682 // cmd.Stdout = os.Stdout 683 // cmd.Stderr = os.Stderr 684 return cmd 685 } 686 687 for i := 0; i < 3; i++ { 688 comm := check.Commentf("%d", i) 689 // sanity check 690 c.Check(diff().Run(), check.NotNil, comm) 691 692 // restore leaves things like they were (again and again) 693 rs, err := shr.Restore(context.TODO(), snap.R(0), nil, logger.Debugf) 694 c.Assert(err, check.IsNil, comm) 695 rs.Cleanup() 696 c.Check(diff().Run(), check.IsNil, comm) 697 698 // dirty it -> no longer like it was 699 c.Check(ioutil.WriteFile(filepath.Join(info.DataDir(), marker), []byte("scribble\n"), 0644), check.IsNil, comm) 700 } 701 } 702 703 func (s *snapshotSuite) TestOpenSetIDoverride(c *check.C) { 704 if os.Geteuid() == 0 { 705 c.Skip("this test cannot run as root (runuser will fail)") 706 } 707 logger.SimpleSetup() 708 709 epoch := snap.E("42*") 710 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 711 cfg := map[string]interface{}{"some-setting": false} 712 713 shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"}) 714 c.Assert(err, check.IsNil) 715 c.Check(shw.SetID, check.Equals, uint64(12)) 716 717 c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 718 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 719 720 shr, err := backend.Open(backend.Filename(shw), 99) 721 c.Assert(err, check.IsNil) 722 defer shr.Close() 723 724 c.Check(shr.SetID, check.Equals, uint64(99)) 725 } 726 727 func (s *snapshotSuite) TestRestoreRoundtripDifferentRevision(c *check.C) { 728 if os.Geteuid() == 0 { 729 c.Skip("this test cannot run as root (runuser will fail)") 730 } 731 logger.SimpleSetup() 732 733 epoch := snap.E("42*") 734 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 735 shID := uint64(12) 736 737 shw, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}) 738 c.Assert(err, check.IsNil) 739 c.Check(shw.Revision, check.Equals, info.Revision) 740 741 shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID) 742 c.Assert(err, check.IsNil) 743 defer shr.Close() 744 745 c.Check(shr.Revision, check.Equals, info.Revision) 746 c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 747 748 // move the expected data to its expected place 749 for _, dir := range []string{ 750 filepath.Join(s.root, "home", "snapuser", "snap", "hello-snap"), 751 filepath.Join(dirs.SnapDataDir, "hello-snap"), 752 } { 753 c.Check(os.Rename(filepath.Join(dir, "42"), filepath.Join(dir, "17")), check.IsNil) 754 } 755 756 newroot := c.MkDir() 757 c.Assert(os.MkdirAll(filepath.Join(newroot, "home", "snapuser"), 0755), check.IsNil) 758 dirs.SetRootDir(newroot) 759 760 var diff = func() *exec.Cmd { 761 cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot) 762 // cmd.Stdout = os.Stdout 763 // cmd.Stderr = os.Stderr 764 return cmd 765 } 766 767 // sanity check 768 c.Check(diff().Run(), check.NotNil) 769 770 // restore leaves things like they were, but in the new dir 771 rs, err := shr.Restore(context.TODO(), snap.R("17"), nil, logger.Debugf) 772 c.Assert(err, check.IsNil) 773 rs.Cleanup() 774 c.Check(diff().Run(), check.IsNil) 775 } 776 777 func (s *snapshotSuite) TestPickUserWrapperRunuser(c *check.C) { 778 n := 0 779 defer backend.MockExecLookPath(func(s string) (string, error) { 780 n++ 781 if s != "runuser" { 782 c.Fatalf(`expected to get "runuser", got %q`, s) 783 } 784 return "/sbin/runuser", nil 785 })() 786 787 c.Check(backend.PickUserWrapper(), check.Equals, "/sbin/runuser") 788 c.Check(n, check.Equals, 1) 789 } 790 791 func (s *snapshotSuite) TestPickUserWrapperSudo(c *check.C) { 792 n := 0 793 defer backend.MockExecLookPath(func(s string) (string, error) { 794 n++ 795 if n == 1 { 796 if s != "runuser" { 797 c.Fatalf(`expected to get "runuser" first, got %q`, s) 798 } 799 return "", errors.New("no such thing") 800 } 801 if s != "sudo" { 802 c.Fatalf(`expected to get "sudo" next, got %q`, s) 803 } 804 return "/usr/bin/sudo", nil 805 })() 806 807 c.Check(backend.PickUserWrapper(), check.Equals, "/usr/bin/sudo") 808 c.Check(n, check.Equals, 2) 809 } 810 811 func (s *snapshotSuite) TestPickUserWrapperNothing(c *check.C) { 812 n := 0 813 defer backend.MockExecLookPath(func(s string) (string, error) { 814 n++ 815 return "", errors.New("no such thing") 816 })() 817 818 c.Check(backend.PickUserWrapper(), check.Equals, "") 819 c.Check(n, check.Equals, 2) 820 } 821 822 func (s *snapshotSuite) TestMaybeRunuserHappyRunuser(c *check.C) { 823 uid := sys.UserID(0) 824 defer backend.MockSysGeteuid(func() sys.UserID { return uid })() 825 defer backend.SetUserWrapper("/sbin/runuser")() 826 logbuf, restore := logger.MockLogger() 827 defer restore() 828 829 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 830 Path: "/sbin/runuser", 831 Args: []string{"/sbin/runuser", "-u", "test", "--", "tar", "--bar"}, 832 }) 833 c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{ 834 Path: s.tarPath, 835 Args: []string{"tar", "--bar"}, 836 }) 837 uid = 42 838 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 839 Path: s.tarPath, 840 Args: []string{"tar", "--bar"}, 841 }) 842 c.Check(logbuf.String(), check.Equals, "") 843 } 844 845 func (s *snapshotSuite) TestMaybeRunuserHappySudo(c *check.C) { 846 uid := sys.UserID(0) 847 defer backend.MockSysGeteuid(func() sys.UserID { return uid })() 848 defer backend.SetUserWrapper("/usr/bin/sudo")() 849 logbuf, restore := logger.MockLogger() 850 defer restore() 851 852 cmd := backend.TarAsUser("test", "--bar") 853 c.Check(cmd, check.DeepEquals, &exec.Cmd{ 854 Path: "/usr/bin/sudo", 855 Args: []string{"/usr/bin/sudo", "-u", "test", "--", "tar", "--bar"}, 856 }) 857 c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{ 858 Path: s.tarPath, 859 Args: []string{"tar", "--bar"}, 860 }) 861 uid = 42 862 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 863 Path: s.tarPath, 864 Args: []string{"tar", "--bar"}, 865 }) 866 c.Check(logbuf.String(), check.Equals, "") 867 } 868 869 func (s *snapshotSuite) TestMaybeRunuserNoHappy(c *check.C) { 870 uid := sys.UserID(0) 871 defer backend.MockSysGeteuid(func() sys.UserID { return uid })() 872 defer backend.SetUserWrapper("")() 873 logbuf, restore := logger.MockLogger() 874 defer restore() 875 876 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 877 Path: s.tarPath, 878 Args: []string{"tar", "--bar"}, 879 }) 880 c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{ 881 Path: s.tarPath, 882 Args: []string{"tar", "--bar"}, 883 }) 884 uid = 42 885 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 886 Path: s.tarPath, 887 Args: []string{"tar", "--bar"}, 888 }) 889 c.Check(strings.TrimSpace(logbuf.String()), check.Matches, ".* No user wrapper found.*") 890 } 891 892 func (s *snapshotSuite) TestImport(c *check.C) { 893 tempdir := c.MkDir() 894 895 // create snapshot export file 896 tarFile1 := path.Join(tempdir, "exported1.snapshot") 897 err := createTestExportFile(tarFile1, &createTestExportFlags{exportJSON: true}) 898 c.Check(err, check.IsNil) 899 900 // create an exported snapshot with missing export.json 901 tarFile2 := path.Join(tempdir, "exported2.snapshot") 902 err = createTestExportFile(tarFile2, &createTestExportFlags{}) 903 c.Check(err, check.IsNil) 904 905 // create invalid exported file 906 tarFile3 := path.Join(tempdir, "exported3.snapshot") 907 err = ioutil.WriteFile(tarFile3, []byte("invalid"), 0755) 908 c.Check(err, check.IsNil) 909 910 // create an exported snapshot with a directory 911 tarFile4 := path.Join(tempdir, "exported4.snapshot") 912 flags := &createTestExportFlags{ 913 exportJSON: true, 914 withDir: true, 915 } 916 err = createTestExportFile(tarFile4, flags) 917 c.Check(err, check.IsNil) 918 919 type tableT struct { 920 setID uint64 921 filename string 922 inProgress bool 923 error string 924 } 925 926 table := []tableT{ 927 {14, tarFile1, false, ""}, 928 {14, tarFile2, false, "cannot import snapshot 14: no export.json file in uploaded data"}, 929 {14, tarFile3, false, "cannot import snapshot 14: cannot read snapshot import: unexpected EOF"}, 930 {14, tarFile4, false, "cannot import snapshot 14: unexpected directory in import file"}, 931 {14, tarFile1, true, "cannot import snapshot 14: already in progress for this set id"}, 932 } 933 934 for i, t := range table { 935 comm := check.Commentf("%d: %d %s", i, t.setID, t.filename) 936 937 // reset 938 err = os.RemoveAll(dirs.SnapshotsDir) 939 c.Assert(err, check.IsNil, comm) 940 err := os.MkdirAll(dirs.SnapshotsDir, 0700) 941 c.Assert(err, check.IsNil, comm) 942 importingFile := filepath.Join(dirs.SnapshotsDir, fmt.Sprintf("%d_importing", t.setID)) 943 if t.inProgress { 944 err = ioutil.WriteFile(importingFile, nil, 0644) 945 c.Assert(err, check.IsNil) 946 } else { 947 err = os.RemoveAll(importingFile) 948 c.Assert(err, check.IsNil, comm) 949 } 950 951 f, err := os.Open(t.filename) 952 c.Assert(err, check.IsNil, comm) 953 defer f.Close() 954 955 snapNames, err := backend.Import(context.Background(), t.setID, f) 956 if t.error != "" { 957 c.Check(err, check.ErrorMatches, t.error, comm) 958 continue 959 } 960 c.Check(err, check.IsNil, comm) 961 sort.Strings(snapNames) 962 c.Check(snapNames, check.DeepEquals, []string{"bar", "baz", "foo"}) 963 964 dir, err := os.Open(dirs.SnapshotsDir) 965 c.Assert(err, check.IsNil, comm) 966 defer dir.Close() 967 names, err := dir.Readdirnames(100) 968 c.Assert(err, check.IsNil, comm) 969 c.Check(len(names), check.Equals, 3, comm) 970 } 971 } 972 973 func (s *snapshotSuite) TestImportCheckErorr(c *check.C) { 974 err := os.MkdirAll(dirs.SnapshotsDir, 0755) 975 c.Assert(err, check.IsNil) 976 977 // create snapshot export file 978 tarFile1 := path.Join(c.MkDir(), "exported1.snapshot") 979 flags := &createTestExportFlags{ 980 exportJSON: true, 981 corruptChecksum: true, 982 } 983 err = createTestExportFile(tarFile1, flags) 984 c.Assert(err, check.IsNil) 985 986 f, err := os.Open(tarFile1) 987 c.Assert(err, check.IsNil) 988 _, err = backend.Import(context.Background(), 14, f) 989 c.Assert(err, check.ErrorMatches, `cannot import snapshot 14: validation failed for .+/14_foo_1.0_199.zip": snapshot entry "archive.tgz" expected hash \(d5ef563…\) does not match actual \(6655519…\)`) 990 } 991 992 func (s *snapshotSuite) TestImportExportRoundtrip(c *check.C) { 993 err := os.MkdirAll(dirs.SnapshotsDir, 0755) 994 c.Assert(err, check.IsNil) 995 996 ctx := context.TODO() 997 998 epoch := snap.E("42*") 999 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 1000 cfg := map[string]interface{}{"some-setting": false} 1001 shID := uint64(12) 1002 1003 shw, err := backend.Save(ctx, shID, info, cfg, []string{"snapuser"}) 1004 c.Assert(err, check.IsNil) 1005 c.Check(shw.SetID, check.Equals, shID) 1006 1007 c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 1008 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 1009 1010 export, err := backend.NewSnapshotExport(ctx, shw.SetID) 1011 c.Assert(err, check.IsNil) 1012 c.Assert(export.Init(), check.IsNil) 1013 1014 buf := bytes.NewBuffer(nil) 1015 c.Assert(export.StreamTo(buf), check.IsNil) 1016 c.Check(buf.Len(), check.Equals, int(export.Size())) 1017 1018 // now import it 1019 c.Assert(os.Remove(filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")), check.IsNil) 1020 1021 names, err := backend.Import(ctx, 123, buf) 1022 c.Assert(err, check.IsNil) 1023 c.Check(names, check.DeepEquals, []string{"hello-snap"}) 1024 1025 sets, err := backend.List(ctx, 0, nil) 1026 c.Assert(err, check.IsNil) 1027 c.Assert(sets, check.HasLen, 1) 1028 c.Check(sets[0].ID, check.Equals, uint64(123)) 1029 1030 rdr, err := backend.Open(filepath.Join(dirs.SnapshotsDir, "123_hello-snap_v1.33_42.zip"), backend.ExtractFnameSetID) 1031 defer rdr.Close() 1032 c.Check(err, check.IsNil) 1033 c.Check(rdr.SetID, check.Equals, uint64(123)) 1034 c.Check(rdr.Snap, check.Equals, "hello-snap") 1035 c.Check(rdr.IsValid(), check.Equals, true) 1036 } 1037 1038 func (s *snapshotSuite) TestEstimateSnapshotSize(c *check.C) { 1039 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 1040 return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil 1041 }) 1042 defer restore() 1043 1044 var info = &snap.Info{ 1045 SuggestedName: "foo", 1046 SideInfo: snap.SideInfo{ 1047 Revision: snap.R(7), 1048 }, 1049 } 1050 1051 snapData := []string{ 1052 "/var/snap/foo/7/somedatadir", 1053 "/var/snap/foo/7/otherdata", 1054 "/var/snap/foo/7", 1055 "/var/snap/foo/common", 1056 "/var/snap/foo/common/a", 1057 "/home/user1/snap/foo/7/somedata", 1058 "/home/user1/snap/foo/common", 1059 } 1060 var data []byte 1061 var expected int 1062 for _, d := range snapData { 1063 data = append(data, 0) 1064 expected += len(data) 1065 c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil) 1066 c.Assert(ioutil.WriteFile(filepath.Join(s.root, d, "somfile"), data, 0644), check.IsNil) 1067 } 1068 1069 sz, err := backend.EstimateSnapshotSize(info, nil) 1070 c.Assert(err, check.IsNil) 1071 c.Check(sz, check.Equals, uint64(expected)) 1072 } 1073 1074 func (s *snapshotSuite) TestEstimateSnapshotSizeEmpty(c *check.C) { 1075 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 1076 return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil 1077 }) 1078 defer restore() 1079 1080 var info = &snap.Info{ 1081 SuggestedName: "foo", 1082 SideInfo: snap.SideInfo{ 1083 Revision: snap.R(7), 1084 }, 1085 } 1086 1087 snapData := []string{ 1088 "/var/snap/foo/common", 1089 "/var/snap/foo/7", 1090 "/home/user1/snap/foo/7", 1091 "/home/user1/snap/foo/common", 1092 } 1093 for _, d := range snapData { 1094 c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil) 1095 } 1096 1097 sz, err := backend.EstimateSnapshotSize(info, nil) 1098 c.Assert(err, check.IsNil) 1099 c.Check(sz, check.Equals, uint64(0)) 1100 } 1101 1102 func (s *snapshotSuite) TestEstimateSnapshotPassesUsernames(c *check.C) { 1103 var gotUsernames []string 1104 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 1105 gotUsernames = usernames 1106 return nil, nil 1107 }) 1108 defer restore() 1109 1110 var info = &snap.Info{ 1111 SuggestedName: "foo", 1112 SideInfo: snap.SideInfo{ 1113 Revision: snap.R(7), 1114 }, 1115 } 1116 1117 _, err := backend.EstimateSnapshotSize(info, []string{"user1", "user2"}) 1118 c.Assert(err, check.IsNil) 1119 c.Check(gotUsernames, check.DeepEquals, []string{"user1", "user2"}) 1120 } 1121 1122 func (s *snapshotSuite) TestEstimateSnapshotSizeNotDataDirs(c *check.C) { 1123 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 1124 return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil 1125 }) 1126 defer restore() 1127 1128 var info = &snap.Info{ 1129 SuggestedName: "foo", 1130 SideInfo: snap.SideInfo{Revision: snap.R(7)}, 1131 } 1132 1133 sz, err := backend.EstimateSnapshotSize(info, nil) 1134 c.Assert(err, check.IsNil) 1135 c.Check(sz, check.Equals, uint64(0)) 1136 } 1137 func (s *snapshotSuite) TestExportTwice(c *check.C) { 1138 // use mocking done in snapshotSuite.SetUpTest 1139 info := &snap.Info{ 1140 SideInfo: snap.SideInfo{ 1141 RealName: "hello-snap", 1142 Revision: snap.R(42), 1143 SnapID: "hello-id", 1144 }, 1145 Version: "v1.33", 1146 } 1147 // create a snapshot 1148 shID := uint64(12) 1149 _, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}) 1150 c.Check(err, check.IsNil) 1151 1152 // num_files + export.json + footer 1153 expectedSize := int64(4*512 + 1024 + 2*512) 1154 // do on export at the start of the epoch 1155 restore := backend.MockTimeNow(func() time.Time { return time.Time{} }) 1156 defer restore() 1157 // export once 1158 buf := bytes.NewBuffer(nil) 1159 ctx := context.Background() 1160 se, err := backend.NewSnapshotExport(ctx, shID) 1161 c.Check(err, check.IsNil) 1162 err = se.Init() 1163 c.Assert(err, check.IsNil) 1164 c.Check(se.Size(), check.Equals, expectedSize) 1165 // and we can stream the data 1166 err = se.StreamTo(buf) 1167 c.Assert(err, check.IsNil) 1168 c.Check(buf.Len(), check.Equals, int(expectedSize)) 1169 1170 // and again to ensure size does not change when exported again 1171 // 1172 // Note that moving beyond year 2242 will change the tar format 1173 // used by the go internal tar and that will make the size actually 1174 // change. 1175 restore = backend.MockTimeNow(func() time.Time { return time.Date(2242, 1, 1, 12, 0, 0, 0, time.UTC) }) 1176 defer restore() 1177 se2, err := backend.NewSnapshotExport(ctx, shID) 1178 c.Check(err, check.IsNil) 1179 err = se2.Init() 1180 c.Assert(err, check.IsNil) 1181 c.Check(se2.Size(), check.Equals, expectedSize) 1182 // and we can stream the data 1183 buf.Reset() 1184 err = se2.StreamTo(buf) 1185 c.Assert(err, check.IsNil) 1186 c.Check(buf.Len(), check.Equals, int(expectedSize)) 1187 } 1188 1189 func (s *snapshotSuite) TestExportUnhappy(c *check.C) { 1190 se, err := backend.NewSnapshotExport(context.Background(), 5) 1191 c.Assert(err, check.ErrorMatches, "no snapshot data found for 5") 1192 c.Assert(se, check.IsNil) 1193 } 1194 1195 type createTestExportFlags struct { 1196 exportJSON bool 1197 withDir bool 1198 corruptChecksum bool 1199 } 1200 1201 func createTestExportFile(filename string, flags *createTestExportFlags) error { 1202 tf, err := os.Create(filename) 1203 if err != nil { 1204 return err 1205 } 1206 defer tf.Close() 1207 tw := tar.NewWriter(tf) 1208 defer tw.Close() 1209 1210 for _, s := range []string{"foo", "bar", "baz"} { 1211 fname := fmt.Sprintf("5_%s_1.0_199.zip", s) 1212 1213 buf := bytes.NewBuffer(nil) 1214 zipW := zip.NewWriter(buf) 1215 defer zipW.Close() 1216 1217 sha := map[string]string{} 1218 1219 // create dummy archive.tgz 1220 archiveWriter, err := zipW.CreateHeader(&zip.FileHeader{Name: "archive.tgz"}) 1221 if err != nil { 1222 return err 1223 } 1224 var sz osutil.Sizer 1225 hasher := crypto.SHA3_384.New() 1226 out := io.MultiWriter(archiveWriter, hasher, &sz) 1227 if _, err := out.Write([]byte(s)); err != nil { 1228 return err 1229 } 1230 1231 if flags.corruptChecksum { 1232 hasher.Write([]byte{0}) 1233 } 1234 sha["archive.tgz"] = fmt.Sprintf("%x", hasher.Sum(nil)) 1235 1236 snapshot := backend.MockSnapshot(5, s, snap.Revision{N: 199}, sz.Size(), sha) 1237 1238 // create meta.json 1239 metaWriter, err := zipW.Create("meta.json") 1240 if err != nil { 1241 return err 1242 } 1243 hasher = crypto.SHA3_384.New() 1244 enc := json.NewEncoder(io.MultiWriter(metaWriter, hasher)) 1245 if err := enc.Encode(snapshot); err != nil { 1246 return err 1247 } 1248 1249 // write meta.sha3_384 1250 metaSha3Writer, err := zipW.Create("meta.sha3_384") 1251 if err != nil { 1252 return err 1253 } 1254 fmt.Fprintf(metaSha3Writer, "%x\n", hasher.Sum(nil)) 1255 zipW.Close() 1256 1257 hdr := &tar.Header{ 1258 Name: fname, 1259 Mode: 0644, 1260 Size: int64(buf.Len()), 1261 } 1262 if err := tw.WriteHeader(hdr); err != nil { 1263 return err 1264 } 1265 if _, err := tw.Write(buf.Bytes()); err != nil { 1266 return err 1267 } 1268 } 1269 1270 if flags.withDir { 1271 hdr := &tar.Header{ 1272 Name: dirs.SnapshotsDir, 1273 Mode: 0700, 1274 Size: int64(0), 1275 Typeflag: tar.TypeDir, 1276 } 1277 if err := tw.WriteHeader(hdr); err != nil { 1278 return err 1279 } 1280 if _, err = tw.Write([]byte("")); err != nil { 1281 return nil 1282 } 1283 } 1284 1285 if flags.exportJSON { 1286 exp := fmt.Sprintf(`{"format":1, "date":"%s"}`, time.Now().Format(time.RFC3339)) 1287 hdr := &tar.Header{ 1288 Name: "export.json", 1289 Mode: 0644, 1290 Size: int64(len(exp)), 1291 } 1292 if err := tw.WriteHeader(hdr); err != nil { 1293 return err 1294 } 1295 if _, err = tw.Write([]byte(exp)); err != nil { 1296 return nil 1297 } 1298 } 1299 1300 return nil 1301 } 1302 1303 func makeMockSnapshotZipContent(c *check.C) []byte { 1304 buf := bytes.NewBuffer(nil) 1305 zipW := zip.NewWriter(buf) 1306 1307 // create dummy archive.tgz 1308 archiveWriter, err := zipW.CreateHeader(&zip.FileHeader{Name: "archive.tgz"}) 1309 c.Assert(err, check.IsNil) 1310 _, err = archiveWriter.Write([]byte("mock archive.tgz content")) 1311 c.Assert(err, check.IsNil) 1312 1313 // create dummy meta.json 1314 archiveWriter, err = zipW.CreateHeader(&zip.FileHeader{Name: "meta.json"}) 1315 c.Assert(err, check.IsNil) 1316 _, err = archiveWriter.Write([]byte("{}")) 1317 c.Assert(err, check.IsNil) 1318 1319 zipW.Close() 1320 return buf.Bytes() 1321 } 1322 1323 func (s *snapshotSuite) TestIterWithMockedSnapshotFiles(c *check.C) { 1324 err := os.MkdirAll(dirs.SnapshotsDir, 0755) 1325 c.Assert(err, check.IsNil) 1326 1327 fn := "1_hello_1.0_x1.zip" 1328 err = ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, fn), makeMockSnapshotZipContent(c), 0644) 1329 c.Assert(err, check.IsNil) 1330 1331 callbackCalled := 0 1332 f := func(snapshot *backend.Reader) error { 1333 callbackCalled++ 1334 return nil 1335 } 1336 1337 err = backend.Iter(context.Background(), f) 1338 c.Check(err, check.IsNil) 1339 c.Check(callbackCalled, check.Equals, 1) 1340 1341 // now pretend we are importing snapshot id 1 1342 callbackCalled = 0 1343 fn = "1_importing" 1344 err = ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, fn), nil, 0644) 1345 c.Assert(err, check.IsNil) 1346 1347 // and while importing Iter() does not call the callback 1348 err = backend.Iter(context.Background(), f) 1349 c.Check(err, check.IsNil) 1350 c.Check(callbackCalled, check.Equals, 0) 1351 } 1352 1353 func (s *snapshotSuite) TestCleanupAbandondedImports(c *check.C) { 1354 err := os.MkdirAll(dirs.SnapshotsDir, 0755) 1355 c.Assert(err, check.IsNil) 1356 1357 // create 2 snapshot IDs 1,2 1358 snapshotFiles := map[int][]string{} 1359 for i := 1; i < 3; i++ { 1360 fn := fmt.Sprintf("%d_hello_%d.0_x1.zip", i, i) 1361 p := filepath.Join(dirs.SnapshotsDir, fn) 1362 snapshotFiles[i] = append(snapshotFiles[i], p) 1363 err = ioutil.WriteFile(p, makeMockSnapshotZipContent(c), 0644) 1364 c.Assert(err, check.IsNil) 1365 1366 fn = fmt.Sprintf("%d_olleh_%d.0_x1.zip", i, i) 1367 p = filepath.Join(dirs.SnapshotsDir, fn) 1368 snapshotFiles[i] = append(snapshotFiles[i], p) 1369 err = ioutil.WriteFile(p, makeMockSnapshotZipContent(c), 0644) 1370 c.Assert(err, check.IsNil) 1371 } 1372 1373 // pretend setID 2 has a import file which means which means that 1374 // an import was started in the past but did not complete 1375 fn := "2_importing" 1376 err = ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, fn), nil, 0644) 1377 c.Assert(err, check.IsNil) 1378 1379 // run cleanup 1380 cleaned, err := backend.CleanupAbandondedImports() 1381 c.Check(cleaned, check.Equals, 1) 1382 c.Check(err, check.IsNil) 1383 1384 // id1 untouched 1385 c.Check(snapshotFiles[1][0], testutil.FilePresent) 1386 c.Check(snapshotFiles[1][1], testutil.FilePresent) 1387 // id2 cleaned 1388 c.Check(snapshotFiles[2][0], testutil.FileAbsent) 1389 c.Check(snapshotFiles[2][1], testutil.FileAbsent) 1390 } 1391 1392 func (s *snapshotSuite) TestCleanupAbandondedImportsFailMany(c *check.C) { 1393 restore := backend.MockFilepathGlob(func(string) ([]string, error) { 1394 return []string{ 1395 "/var/lib/snapd/snapshots/NaN_importing", 1396 "/var/lib/snapd/snapshots/11_importing", 1397 "/var/lib/snapd/snapshots/22_importing", 1398 }, nil 1399 }) 1400 defer restore() 1401 1402 _, err := backend.CleanupAbandondedImports() 1403 c.Assert(err, check.ErrorMatches, `cannot cleanup imports: 1404 - cannot determine snapshot id from "/var/lib/snapd/snapshots/NaN_importing" 1405 - cannot cancel import for set id 11: 1406 - remove /.*/var/lib/snapd/snapshots/11_importing: no such file or directory 1407 - cannot cancel import for set id 22: 1408 - remove /.*/var/lib/snapd/snapshots/22_importing: no such file or directory`) 1409 } 1410 1411 func (s *snapshotSuite) TestMultiError(c *check.C) { 1412 me2 := backend.NewMultiError("deeper nested wrongness", []error{ 1413 fmt.Errorf("some error in level 2"), 1414 }) 1415 me1 := backend.NewMultiError("nested wrongness", []error{ 1416 fmt.Errorf("some error in level 1"), 1417 me2, 1418 fmt.Errorf("other error in level 1"), 1419 }) 1420 me := backend.NewMultiError("many things went wrong", []error{ 1421 fmt.Errorf("some normal error"), 1422 me1, 1423 }) 1424 1425 c.Check(me, check.ErrorMatches, `many things went wrong: 1426 - some normal error 1427 - nested wrongness: 1428 - some error in level 1 1429 - deeper nested wrongness: 1430 - some error in level 2 1431 - other error in level 1`) 1432 1433 // do it again 1434 c.Check(me, check.ErrorMatches, `many things went wrong: 1435 - some normal error 1436 - nested wrongness: 1437 - some error in level 1 1438 - deeper nested wrongness: 1439 - some error in level 2 1440 - other error in level 1`) 1441 } 1442 1443 func (s *snapshotSuite) TestMultiErrorCycle(c *check.C) { 1444 errs := []error{nil, fmt.Errorf("e5")} 1445 me5 := backend.NewMultiError("he5", errs) 1446 // very hard to happen in practice 1447 errs[0] = me5 1448 me4 := backend.NewMultiError("he4", []error{me5}) 1449 me3 := backend.NewMultiError("he3", []error{me4}) 1450 me2 := backend.NewMultiError("he3", []error{me3}) 1451 me1 := backend.NewMultiError("he1", []error{me2}) 1452 me := backend.NewMultiError("he", []error{me1}) 1453 1454 c.Check(me, check.ErrorMatches, `he: 1455 - he1: 1456 - he3: 1457 - he3: 1458 - he4: 1459 - he5: 1460 - he5: 1461 - he5: 1462 - he5: 1463 - circular or too deep error nesting \(max 8\)\?! 1464 - e5 1465 - e5 1466 - e5 1467 - e5`) 1468 }