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