github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/overlord/snapshotstate/backend/backend_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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/zip" 24 "bytes" 25 "context" 26 "errors" 27 "fmt" 28 "io" 29 "io/ioutil" 30 "os" 31 "os/exec" 32 "os/user" 33 "path/filepath" 34 "sort" 35 "strings" 36 "testing" 37 "time" 38 39 "gopkg.in/check.v1" 40 41 "github.com/snapcore/snapd/client" 42 "github.com/snapcore/snapd/dirs" 43 "github.com/snapcore/snapd/logger" 44 "github.com/snapcore/snapd/osutil/sys" 45 "github.com/snapcore/snapd/overlord/snapshotstate/backend" 46 "github.com/snapcore/snapd/snap" 47 ) 48 49 type snapshotSuite struct { 50 root string 51 restore []func() 52 tarPath string 53 isTesting bool 54 } 55 56 // silly wrappers to get better failure messages 57 type isTestingSuite struct{ snapshotSuite } 58 type noTestingSuite struct{ snapshotSuite } 59 60 var _ = check.Suite(&isTestingSuite{snapshotSuite{isTesting: true}}) 61 var _ = check.Suite(&noTestingSuite{snapshotSuite{isTesting: false}}) 62 63 // tie gocheck into testing 64 func TestSnapshot(t *testing.T) { check.TestingT(t) } 65 66 type tableT struct { 67 dir string 68 name string 69 content string 70 } 71 72 func table(si snap.PlaceInfo, homeDir string) []tableT { 73 return []tableT{ 74 { 75 dir: si.DataDir(), 76 name: "foo", 77 content: "versioned system canary\n", 78 }, { 79 dir: si.CommonDataDir(), 80 name: "bar", 81 content: "common system canary\n", 82 }, { 83 dir: si.UserDataDir(homeDir), 84 name: "ufoo", 85 content: "versioned user canary\n", 86 }, { 87 dir: si.UserCommonDataDir(homeDir), 88 name: "ubar", 89 content: "common user canary\n", 90 }, 91 } 92 } 93 94 func (s *snapshotSuite) SetUpTest(c *check.C) { 95 s.root = c.MkDir() 96 97 dirs.SetRootDir(s.root) 98 99 si := snap.MinimalPlaceInfo("hello-snap", snap.R(42)) 100 101 for _, t := range table(si, filepath.Join(dirs.GlobalRootDir, "home/snapuser")) { 102 c.Check(os.MkdirAll(t.dir, 0755), check.IsNil) 103 c.Check(ioutil.WriteFile(filepath.Join(t.dir, t.name), []byte(t.content), 0644), check.IsNil) 104 } 105 106 cur, err := user.Current() 107 c.Assert(err, check.IsNil) 108 109 s.restore = append(s.restore, backend.MockUserLookup(func(username string) (*user.User, error) { 110 if username != "snapuser" { 111 return nil, user.UnknownUserError(username) 112 } 113 rv := *cur 114 rv.Username = username 115 rv.HomeDir = filepath.Join(dirs.GlobalRootDir, "home/snapuser") 116 return &rv, nil 117 }), 118 backend.MockIsTesting(s.isTesting), 119 ) 120 121 s.tarPath, err = exec.LookPath("tar") 122 c.Assert(err, check.IsNil) 123 } 124 125 func (s *snapshotSuite) TearDownTest(c *check.C) { 126 dirs.SetRootDir("") 127 for _, restore := range s.restore { 128 restore() 129 } 130 } 131 132 func hashkeys(snapshot *client.Snapshot) (keys []string) { 133 for k := range snapshot.SHA3_384 { 134 keys = append(keys, k) 135 } 136 sort.Strings(keys) 137 138 return keys 139 } 140 141 func (s *snapshotSuite) TestLastSnapshotID(c *check.C) { 142 // LastSnapshotSetID is happy without any snapshots 143 setID, err := backend.LastSnapshotSetID() 144 c.Assert(err, check.IsNil) 145 c.Check(setID, check.Equals, uint64(0)) 146 147 // create snapshots dir and dummy snapshots 148 os.MkdirAll(dirs.SnapshotsDir, os.ModePerm) 149 for _, name := range []string{ 150 "9_some-snap-1.zip", "1234_not-a-snapshot", "12_other-snap.zip", "3_foo.zip", 151 } { 152 c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, name), []byte{}, 0644), check.IsNil) 153 } 154 setID, err = backend.LastSnapshotSetID() 155 c.Assert(err, check.IsNil) 156 c.Check(setID, check.Equals, uint64(12)) 157 } 158 159 func (s *snapshotSuite) TestLastSnapshotIDErrorOnDirNames(c *check.C) { 160 // we need snapshots dir, otherwise LastSnapshotSetID exits early. 161 c.Assert(os.MkdirAll(dirs.SnapshotsDir, os.ModePerm), check.IsNil) 162 163 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 164 return nil, fmt.Errorf("fail") 165 })() 166 setID, err := backend.LastSnapshotSetID() 167 c.Assert(err, check.ErrorMatches, "fail") 168 c.Check(setID, check.Equals, uint64(0)) 169 } 170 171 func (s *snapshotSuite) TestIsSnapshotFilename(c *check.C) { 172 tests := []struct { 173 name string 174 valid bool 175 setID uint64 176 }{ 177 {"1_foo.zip", true, 1}, 178 {"14_hello-world_6.4_29.zip", true, 14}, 179 {"1_.zip", false, 0}, 180 {"1_foo.zip.bak", false, 0}, 181 {"foo_1_foo.zip", false, 0}, 182 {"foo_bar_baz.zip", false, 0}, 183 {"", false, 0}, 184 {"1_", false, 0}, 185 } 186 187 for _, t := range tests { 188 ok, setID := backend.IsSnapshotFilename(t.name) 189 c.Check(ok, check.Equals, t.valid, check.Commentf("fail: %s", t.name)) 190 c.Check(setID, check.Equals, t.setID, check.Commentf("fail: %s", t.name)) 191 } 192 } 193 194 func (s *snapshotSuite) TestIterBailsIfContextDone(c *check.C) { 195 ctx, cancel := context.WithCancel(context.Background()) 196 cancel() 197 triedToOpenDir := false 198 defer backend.MockOsOpen(func(string) (*os.File, error) { 199 triedToOpenDir = true 200 return nil, nil // deal with it 201 })() 202 203 err := backend.Iter(ctx, nil) 204 c.Check(err, check.Equals, context.Canceled) 205 c.Check(triedToOpenDir, check.Equals, false) 206 } 207 208 func (s *snapshotSuite) TestIterBailsIfContextDoneMidway(c *check.C) { 209 ctx, cancel := context.WithCancel(context.Background()) 210 triedToOpenDir := false 211 defer backend.MockOsOpen(func(string) (*os.File, error) { 212 triedToOpenDir = true 213 return os.Open(os.DevNull) 214 })() 215 readNames := 0 216 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 217 readNames++ 218 cancel() 219 return []string{"hello"}, nil 220 })() 221 triedToOpenSnapshot := false 222 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 223 triedToOpenSnapshot = true 224 return nil, nil 225 })() 226 227 err := backend.Iter(ctx, nil) 228 c.Check(err, check.Equals, context.Canceled) 229 c.Check(triedToOpenDir, check.Equals, true) 230 // bails as soon as 231 c.Check(readNames, check.Equals, 1) 232 c.Check(triedToOpenSnapshot, check.Equals, false) 233 } 234 235 func (s *snapshotSuite) TestIterReturnsOkIfSnapshotsDirNonexistent(c *check.C) { 236 triedToOpenDir := false 237 defer backend.MockOsOpen(func(string) (*os.File, error) { 238 triedToOpenDir = true 239 return nil, os.ErrNotExist 240 })() 241 242 err := backend.Iter(context.Background(), nil) 243 c.Check(err, check.IsNil) 244 c.Check(triedToOpenDir, check.Equals, true) 245 } 246 247 func (s *snapshotSuite) TestIterBailsIfSnapshotsDirFails(c *check.C) { 248 triedToOpenDir := false 249 defer backend.MockOsOpen(func(string) (*os.File, error) { 250 triedToOpenDir = true 251 return nil, os.ErrInvalid 252 })() 253 254 err := backend.Iter(context.Background(), nil) 255 c.Check(err, check.ErrorMatches, "cannot open snapshots directory: invalid argument") 256 c.Check(triedToOpenDir, check.Equals, true) 257 } 258 259 func (s *snapshotSuite) TestIterWarnsOnOpenErrorIfSnapshotNil(c *check.C) { 260 logbuf, restore := logger.MockLogger() 261 defer restore() 262 triedToOpenDir := false 263 defer backend.MockOsOpen(func(string) (*os.File, error) { 264 triedToOpenDir = true 265 return new(os.File), nil 266 })() 267 readNames := 0 268 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 269 readNames++ 270 if readNames > 1 { 271 return nil, io.EOF 272 } 273 return []string{"1_hello.zip"}, nil 274 })() 275 triedToOpenSnapshot := false 276 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 277 triedToOpenSnapshot = true 278 return nil, os.ErrInvalid 279 })() 280 281 calledF := false 282 f := func(snapshot *backend.Reader) error { 283 calledF = true 284 return nil 285 } 286 287 err := backend.Iter(context.Background(), f) 288 // snapshot open errors are not failures: 289 c.Check(err, check.IsNil) 290 c.Check(triedToOpenDir, check.Equals, true) 291 c.Check(readNames, check.Equals, 2) 292 c.Check(triedToOpenSnapshot, check.Equals, true) 293 c.Check(logbuf.String(), check.Matches, `(?m).* Cannot open snapshot "1_hello.zip": invalid argument.`) 294 c.Check(calledF, check.Equals, false) 295 } 296 297 func (s *snapshotSuite) TestIterCallsFuncIfSnapshotNotNil(c *check.C) { 298 logbuf, restore := logger.MockLogger() 299 defer restore() 300 triedToOpenDir := false 301 defer backend.MockOsOpen(func(string) (*os.File, error) { 302 triedToOpenDir = true 303 return new(os.File), nil 304 })() 305 readNames := 0 306 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 307 readNames++ 308 if readNames > 1 { 309 return nil, io.EOF 310 } 311 return []string{"1_hello.zip"}, nil 312 })() 313 triedToOpenSnapshot := false 314 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 315 triedToOpenSnapshot = true 316 // NOTE non-nil reader, and error, returned 317 r := backend.Reader{} 318 r.SetID = 1 319 r.Broken = "xyzzy" 320 return &r, os.ErrInvalid 321 })() 322 323 calledF := false 324 f := func(snapshot *backend.Reader) error { 325 c.Check(snapshot.Broken, check.Equals, "xyzzy") 326 calledF = true 327 return nil 328 } 329 330 err := backend.Iter(context.Background(), f) 331 // snapshot open errors are not failures: 332 c.Check(err, check.IsNil) 333 c.Check(triedToOpenDir, check.Equals, true) 334 c.Check(readNames, check.Equals, 2) 335 c.Check(triedToOpenSnapshot, check.Equals, true) 336 c.Check(logbuf.String(), check.Equals, "") 337 c.Check(calledF, check.Equals, true) 338 } 339 340 func (s *snapshotSuite) TestIterReportsCloseError(c *check.C) { 341 logbuf, restore := logger.MockLogger() 342 defer restore() 343 triedToOpenDir := false 344 defer backend.MockOsOpen(func(string) (*os.File, error) { 345 triedToOpenDir = true 346 return new(os.File), nil 347 })() 348 readNames := 0 349 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 350 readNames++ 351 if readNames > 1 { 352 return nil, io.EOF 353 } 354 return []string{"42_hello.zip"}, nil 355 })() 356 triedToOpenSnapshot := false 357 defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) { 358 triedToOpenSnapshot = true 359 r := backend.Reader{} 360 r.SetID = 42 361 return &r, nil 362 })() 363 364 calledF := false 365 f := func(snapshot *backend.Reader) error { 366 c.Check(snapshot.SetID, check.Equals, uint64(42)) 367 calledF = true 368 return nil 369 } 370 371 err := backend.Iter(context.Background(), f) 372 // snapshot close errors _are_ failures (because they're completely unexpected): 373 c.Check(err, check.Equals, os.ErrInvalid) 374 c.Check(triedToOpenDir, check.Equals, true) 375 c.Check(readNames, check.Equals, 1) // never gets to read another one 376 c.Check(triedToOpenSnapshot, check.Equals, true) 377 c.Check(logbuf.String(), check.Equals, "") 378 c.Check(calledF, check.Equals, true) 379 } 380 381 func readerForFilename(fname string, c *check.C) *backend.Reader { 382 var snapname string 383 var id uint64 384 fn := strings.TrimSuffix(filepath.Base(fname), ".zip") 385 _, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname) 386 c.Assert(err, check.IsNil, check.Commentf(fn)) 387 f, err := os.Open(os.DevNull) 388 c.Assert(err, check.IsNil, check.Commentf(fn)) 389 return &backend.Reader{ 390 File: f, 391 Snapshot: client.Snapshot{ 392 SetID: id, 393 Snap: snapname, 394 }, 395 } 396 } 397 398 func (s *snapshotSuite) TestIterIgnoresSnapshotsWithInvalidNames(c *check.C) { 399 logbuf, restore := logger.MockLogger() 400 defer restore() 401 402 defer backend.MockOsOpen(func(string) (*os.File, error) { 403 return new(os.File), nil 404 })() 405 readNames := 0 406 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 407 readNames++ 408 if readNames > 1 { 409 return nil, io.EOF 410 } 411 return []string{ 412 "_foo.zip", 413 "43_bar.zip", 414 "foo_bar.zip", 415 "bar.", 416 }, nil 417 })() 418 defer backend.MockOpen(func(fname string, setID uint64) (*backend.Reader, error) { 419 return readerForFilename(fname, c), nil 420 })() 421 422 var calledF int 423 f := func(snapshot *backend.Reader) error { 424 calledF++ 425 c.Check(snapshot.SetID, check.Equals, uint64(43)) 426 return nil 427 } 428 429 err := backend.Iter(context.Background(), f) 430 c.Check(err, check.IsNil) 431 c.Check(logbuf.String(), check.Equals, "") 432 c.Check(calledF, check.Equals, 1) 433 } 434 435 func (s *snapshotSuite) TestIterSetIDoverride(c *check.C) { 436 if os.Geteuid() == 0 { 437 c.Skip("this test cannot run as root (runuser will fail)") 438 } 439 logger.SimpleSetup() 440 441 epoch := snap.E("42*") 442 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 443 cfg := map[string]interface{}{"some-setting": false} 444 445 shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"}, &backend.Flags{}) 446 c.Assert(err, check.IsNil) 447 c.Check(shw.SetID, check.Equals, uint64(12)) 448 449 snapshotPath := filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip") 450 c.Check(backend.Filename(shw), check.Equals, snapshotPath) 451 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 452 453 // rename the snapshot, verify that set id from the filename is used by the reader. 454 c.Assert(os.Rename(snapshotPath, filepath.Join(dirs.SnapshotsDir, "33_hello.zip")), check.IsNil) 455 456 var calledF int 457 f := func(snapshot *backend.Reader) error { 458 calledF++ 459 c.Check(snapshot.SetID, check.Equals, uint64(uint(33))) 460 c.Check(snapshot.Snap, check.Equals, "hello-snap") 461 return nil 462 } 463 464 c.Assert(backend.Iter(context.Background(), f), check.IsNil) 465 c.Check(calledF, check.Equals, 1) 466 } 467 468 func (s *snapshotSuite) TestList(c *check.C) { 469 logbuf, restore := logger.MockLogger() 470 defer restore() 471 defer backend.MockOsOpen(func(string) (*os.File, error) { return new(os.File), nil })() 472 473 readNames := 0 474 defer backend.MockDirNames(func(*os.File, int) ([]string, error) { 475 readNames++ 476 if readNames > 4 { 477 return nil, io.EOF 478 } 479 return []string{ 480 fmt.Sprintf("%d_foo.zip", readNames), 481 fmt.Sprintf("%d_bar.zip", readNames), 482 fmt.Sprintf("%d_baz.zip", readNames), 483 }, nil 484 })() 485 defer backend.MockOpen(func(fn string, setID uint64) (*backend.Reader, error) { 486 var id uint64 487 var snapname string 488 c.Assert(strings.HasSuffix(fn, ".zip"), check.Equals, true) 489 fn = strings.TrimSuffix(filepath.Base(fn), ".zip") 490 _, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname) 491 c.Assert(err, check.IsNil, check.Commentf(fn)) 492 f, err := os.Open(os.DevNull) 493 c.Assert(err, check.IsNil, check.Commentf(fn)) 494 return &backend.Reader{ 495 File: f, 496 Snapshot: client.Snapshot{ 497 SetID: id, 498 Snap: snapname, 499 SnapID: "id-for-" + snapname, 500 Version: "v1.0-" + snapname, 501 Revision: snap.R(int(id)), 502 }, 503 }, nil 504 })() 505 506 type tableT struct { 507 setID uint64 508 snapnames []string 509 numSets int 510 numShots int 511 predicate func(*client.Snapshot) bool 512 } 513 table := []tableT{ 514 {0, nil, 4, 12, nil}, 515 {0, []string{"foo"}, 4, 4, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" }}, 516 {1, nil, 1, 3, func(snapshot *client.Snapshot) bool { return snapshot.SetID == 1 }}, 517 {2, []string{"bar"}, 1, 1, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "bar" && snapshot.SetID == 2 }}, 518 {0, []string{"foo", "bar"}, 4, 8, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" || snapshot.Snap == "bar" }}, 519 } 520 521 for i, t := range table { 522 comm := check.Commentf("%d: %d/%v", i, t.setID, t.snapnames) 523 // reset 524 readNames = 0 525 logbuf.Reset() 526 527 sets, err := backend.List(context.Background(), t.setID, t.snapnames) 528 c.Check(err, check.IsNil, comm) 529 c.Check(readNames, check.Equals, 5, comm) 530 c.Check(logbuf.String(), check.Equals, "", comm) 531 c.Check(sets, check.HasLen, t.numSets, comm) 532 nShots := 0 533 fnTpl := filepath.Join(dirs.SnapshotsDir, "%d_%s_%s_%s.zip") 534 for j, ss := range sets { 535 for k, snapshot := range ss.Snapshots { 536 comm := check.Commentf("%d: %d/%v #%d/%d", i, t.setID, t.snapnames, j, k) 537 if t.predicate != nil { 538 c.Check(t.predicate(snapshot), check.Equals, true, comm) 539 } 540 nShots++ 541 fn := fmt.Sprintf(fnTpl, snapshot.SetID, snapshot.Snap, snapshot.Version, snapshot.Revision) 542 c.Check(backend.Filename(snapshot), check.Equals, fn, comm) 543 c.Check(snapshot.SnapID, check.Equals, "id-for-"+snapshot.Snap) 544 } 545 } 546 c.Check(nShots, check.Equals, t.numShots) 547 } 548 } 549 550 func (s *snapshotSuite) TestAddDirToZipBails(c *check.C) { 551 snapshot := &client.Snapshot{SetID: 42, Snap: "a-snap"} 552 buf, restore := logger.MockLogger() 553 defer restore() 554 // note as the zip is nil this would panic if it didn't bail 555 c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", filepath.Join(s.root, "nonexistent")), check.IsNil) 556 // no log for the non-existent case 557 c.Check(buf.String(), check.Equals, "") 558 buf.Reset() 559 c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", "/etc/passwd"), check.IsNil) 560 c.Check(buf.String(), check.Matches, "(?m).* is not a directory.") 561 } 562 563 func (s *snapshotSuite) TestAddDirToZipTarFails(c *check.C) { 564 d := filepath.Join(s.root, "foo") 565 c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil) 566 c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil) 567 568 ctx, cancel := context.WithCancel(context.Background()) 569 cancel() 570 571 var buf bytes.Buffer 572 z := zip.NewWriter(&buf) 573 c.Assert(backend.AddDirToZip(ctx, nil, z, "", "an/entry", d), check.ErrorMatches, ".* context canceled") 574 } 575 576 func (s *snapshotSuite) TestAddDirToZip(c *check.C) { 577 d := filepath.Join(s.root, "foo") 578 c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil) 579 c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil) 580 c.Assert(ioutil.WriteFile(filepath.Join(d, "bar", "baz"), []byte("hello\n"), 0644), check.IsNil) 581 582 var buf bytes.Buffer 583 z := zip.NewWriter(&buf) 584 snapshot := &client.Snapshot{ 585 SHA3_384: map[string]string{}, 586 } 587 c.Assert(backend.AddDirToZip(context.Background(), snapshot, z, "", "an/entry", d), check.IsNil) 588 z.Close() // write out the central directory 589 590 c.Check(snapshot.SHA3_384, check.HasLen, 1) 591 c.Check(snapshot.SHA3_384["an/entry"], check.HasLen, 96) 592 c.Check(snapshot.Size > 0, check.Equals, true) // actual size most likely system-dependent 593 br := bytes.NewReader(buf.Bytes()) 594 r, err := zip.NewReader(br, int64(br.Len())) 595 c.Assert(err, check.IsNil) 596 c.Check(r.File, check.HasLen, 1) 597 c.Check(r.File[0].Name, check.Equals, "an/entry") 598 } 599 600 func (s *snapshotSuite) TestHappyRoundtrip(c *check.C) { 601 s.testHappyRoundtrip(c, "marker", false) 602 } 603 604 func (s *snapshotSuite) TestHappyRoundtripAutomaticSnapshot(c *check.C) { 605 s.testHappyRoundtrip(c, "marker", true) 606 } 607 608 func (s *snapshotSuite) TestHappyRoundtripNoCommon(c *check.C) { 609 for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) { 610 if _, d := filepath.Split(t.dir); d == "common" { 611 c.Assert(os.RemoveAll(t.dir), check.IsNil) 612 } 613 } 614 s.testHappyRoundtrip(c, "marker", false) 615 } 616 617 func (s *snapshotSuite) TestHappyRoundtripNoRev(c *check.C) { 618 for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) { 619 if _, d := filepath.Split(t.dir); d == "42" { 620 c.Assert(os.RemoveAll(t.dir), check.IsNil) 621 } 622 } 623 s.testHappyRoundtrip(c, "../common/marker", false) 624 } 625 626 func (s *snapshotSuite) testHappyRoundtrip(c *check.C, marker string, auto bool) { 627 if os.Geteuid() == 0 { 628 c.Skip("this test cannot run as root (runuser will fail)") 629 } 630 logger.SimpleSetup() 631 632 epoch := snap.E("42*") 633 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 634 cfg := map[string]interface{}{"some-setting": false} 635 shID := uint64(12) 636 637 shw, err := backend.Save(context.TODO(), shID, info, cfg, []string{"snapuser"}, &backend.Flags{Auto: auto}) 638 c.Assert(err, check.IsNil) 639 c.Check(shw.SetID, check.Equals, shID) 640 c.Check(shw.Snap, check.Equals, info.InstanceName()) 641 c.Check(shw.SnapID, check.Equals, info.SnapID) 642 c.Check(shw.Version, check.Equals, info.Version) 643 c.Check(shw.Epoch, check.DeepEquals, epoch) 644 c.Check(shw.Revision, check.Equals, info.Revision) 645 c.Check(shw.Conf, check.DeepEquals, cfg) 646 c.Check(shw.Auto, check.Equals, auto) 647 c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 648 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 649 650 shs, err := backend.List(context.TODO(), 0, nil) 651 c.Assert(err, check.IsNil) 652 c.Assert(shs, check.HasLen, 1) 653 c.Assert(shs[0].Snapshots, check.HasLen, 1) 654 655 shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID) 656 c.Assert(err, check.IsNil) 657 defer shr.Close() 658 659 for label, sh := range map[string]*client.Snapshot{"open": &shr.Snapshot, "list": shs[0].Snapshots[0]} { 660 comm := check.Commentf("%q", label) 661 c.Check(sh.SetID, check.Equals, shID, comm) 662 c.Check(sh.Snap, check.Equals, info.InstanceName(), comm) 663 c.Check(sh.SnapID, check.Equals, info.SnapID, comm) 664 c.Check(sh.Version, check.Equals, info.Version, comm) 665 c.Check(sh.Epoch, check.DeepEquals, epoch) 666 c.Check(sh.Revision, check.Equals, info.Revision, comm) 667 c.Check(sh.Conf, check.DeepEquals, cfg, comm) 668 c.Check(sh.SHA3_384, check.DeepEquals, shw.SHA3_384, comm) 669 c.Check(sh.Auto, check.Equals, auto) 670 } 671 c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 672 c.Check(shr.Check(context.TODO(), nil), check.IsNil) 673 674 newroot := c.MkDir() 675 c.Assert(os.MkdirAll(filepath.Join(newroot, "home/snapuser"), 0755), check.IsNil) 676 dirs.SetRootDir(newroot) 677 678 var diff = func() *exec.Cmd { 679 cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot) 680 // cmd.Stdout = os.Stdout 681 // cmd.Stderr = os.Stderr 682 return cmd 683 } 684 685 for i := 0; i < 3; i++ { 686 comm := check.Commentf("%d", i) 687 // sanity check 688 c.Check(diff().Run(), check.NotNil, comm) 689 690 // restore leaves things like they were (again and again) 691 rs, err := shr.Restore(context.TODO(), snap.R(0), nil, logger.Debugf) 692 c.Assert(err, check.IsNil, comm) 693 rs.Cleanup() 694 c.Check(diff().Run(), check.IsNil, comm) 695 696 // dirty it -> no longer like it was 697 c.Check(ioutil.WriteFile(filepath.Join(info.DataDir(), marker), []byte("scribble\n"), 0644), check.IsNil, comm) 698 } 699 } 700 701 func (s *snapshotSuite) TestOpenSetIDoverride(c *check.C) { 702 if os.Geteuid() == 0 { 703 c.Skip("this test cannot run as root (runuser will fail)") 704 } 705 logger.SimpleSetup() 706 707 epoch := snap.E("42*") 708 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 709 cfg := map[string]interface{}{"some-setting": false} 710 711 shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"}, &backend.Flags{}) 712 c.Assert(err, check.IsNil) 713 c.Check(shw.SetID, check.Equals, uint64(12)) 714 715 c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 716 c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"}) 717 718 shr, err := backend.Open(backend.Filename(shw), 99) 719 c.Assert(err, check.IsNil) 720 defer shr.Close() 721 722 c.Check(shr.SetID, check.Equals, uint64(99)) 723 } 724 725 func (s *snapshotSuite) TestRestoreRoundtripDifferentRevision(c *check.C) { 726 if os.Geteuid() == 0 { 727 c.Skip("this test cannot run as root (runuser will fail)") 728 } 729 logger.SimpleSetup() 730 731 epoch := snap.E("42*") 732 info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch} 733 shID := uint64(12) 734 735 shw, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}, nil) 736 c.Assert(err, check.IsNil) 737 c.Check(shw.Revision, check.Equals, info.Revision) 738 739 shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID) 740 c.Assert(err, check.IsNil) 741 defer shr.Close() 742 743 c.Check(shr.Revision, check.Equals, info.Revision) 744 c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")) 745 746 // move the expected data to its expected place 747 for _, dir := range []string{ 748 filepath.Join(s.root, "home", "snapuser", "snap", "hello-snap"), 749 filepath.Join(dirs.SnapDataDir, "hello-snap"), 750 } { 751 c.Check(os.Rename(filepath.Join(dir, "42"), filepath.Join(dir, "17")), check.IsNil) 752 } 753 754 newroot := c.MkDir() 755 c.Assert(os.MkdirAll(filepath.Join(newroot, "home", "snapuser"), 0755), check.IsNil) 756 dirs.SetRootDir(newroot) 757 758 var diff = func() *exec.Cmd { 759 cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot) 760 // cmd.Stdout = os.Stdout 761 // cmd.Stderr = os.Stderr 762 return cmd 763 } 764 765 // sanity check 766 c.Check(diff().Run(), check.NotNil) 767 768 // restore leaves things like they were, but in the new dir 769 rs, err := shr.Restore(context.TODO(), snap.R("17"), nil, logger.Debugf) 770 c.Assert(err, check.IsNil) 771 rs.Cleanup() 772 c.Check(diff().Run(), check.IsNil) 773 } 774 775 func (s *snapshotSuite) TestPickUserWrapperRunuser(c *check.C) { 776 n := 0 777 defer backend.MockExecLookPath(func(s string) (string, error) { 778 n++ 779 if s != "runuser" { 780 c.Fatalf(`expected to get "runuser", got %q`, s) 781 } 782 return "/sbin/runuser", nil 783 })() 784 785 c.Check(backend.PickUserWrapper(), check.Equals, "/sbin/runuser") 786 c.Check(n, check.Equals, 1) 787 } 788 789 func (s *snapshotSuite) TestPickUserWrapperSudo(c *check.C) { 790 n := 0 791 defer backend.MockExecLookPath(func(s string) (string, error) { 792 n++ 793 if n == 1 { 794 if s != "runuser" { 795 c.Fatalf(`expected to get "runuser" first, got %q`, s) 796 } 797 return "", errors.New("no such thing") 798 } 799 if s != "sudo" { 800 c.Fatalf(`expected to get "sudo" next, got %q`, s) 801 } 802 return "/usr/bin/sudo", nil 803 })() 804 805 c.Check(backend.PickUserWrapper(), check.Equals, "/usr/bin/sudo") 806 c.Check(n, check.Equals, 2) 807 } 808 809 func (s *snapshotSuite) TestPickUserWrapperNothing(c *check.C) { 810 n := 0 811 defer backend.MockExecLookPath(func(s string) (string, error) { 812 n++ 813 return "", errors.New("no such thing") 814 })() 815 816 c.Check(backend.PickUserWrapper(), check.Equals, "") 817 c.Check(n, check.Equals, 2) 818 } 819 820 func (s *snapshotSuite) TestMaybeRunuserHappyRunuser(c *check.C) { 821 uid := sys.UserID(0) 822 defer backend.MockSysGeteuid(func() sys.UserID { return uid })() 823 defer backend.SetUserWrapper("/sbin/runuser")() 824 logbuf, restore := logger.MockLogger() 825 defer restore() 826 827 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 828 Path: "/sbin/runuser", 829 Args: []string{"/sbin/runuser", "-u", "test", "--", "tar", "--bar"}, 830 }) 831 c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{ 832 Path: s.tarPath, 833 Args: []string{"tar", "--bar"}, 834 }) 835 uid = 42 836 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 837 Path: s.tarPath, 838 Args: []string{"tar", "--bar"}, 839 }) 840 c.Check(logbuf.String(), check.Equals, "") 841 } 842 843 func (s *snapshotSuite) TestMaybeRunuserHappySudo(c *check.C) { 844 uid := sys.UserID(0) 845 defer backend.MockSysGeteuid(func() sys.UserID { return uid })() 846 defer backend.SetUserWrapper("/usr/bin/sudo")() 847 logbuf, restore := logger.MockLogger() 848 defer restore() 849 850 cmd := backend.TarAsUser("test", "--bar") 851 c.Check(cmd, check.DeepEquals, &exec.Cmd{ 852 Path: "/usr/bin/sudo", 853 Args: []string{"/usr/bin/sudo", "-u", "test", "--", "tar", "--bar"}, 854 }) 855 c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{ 856 Path: s.tarPath, 857 Args: []string{"tar", "--bar"}, 858 }) 859 uid = 42 860 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 861 Path: s.tarPath, 862 Args: []string{"tar", "--bar"}, 863 }) 864 c.Check(logbuf.String(), check.Equals, "") 865 } 866 867 func (s *snapshotSuite) TestMaybeRunuserNoHappy(c *check.C) { 868 uid := sys.UserID(0) 869 defer backend.MockSysGeteuid(func() sys.UserID { return uid })() 870 defer backend.SetUserWrapper("")() 871 logbuf, restore := logger.MockLogger() 872 defer restore() 873 874 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 875 Path: s.tarPath, 876 Args: []string{"tar", "--bar"}, 877 }) 878 c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{ 879 Path: s.tarPath, 880 Args: []string{"tar", "--bar"}, 881 }) 882 uid = 42 883 c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{ 884 Path: s.tarPath, 885 Args: []string{"tar", "--bar"}, 886 }) 887 c.Check(strings.TrimSpace(logbuf.String()), check.Matches, ".* No user wrapper found.*") 888 } 889 890 func (s *snapshotSuite) TestEstimateSnapshotSize(c *check.C) { 891 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 892 return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil 893 }) 894 defer restore() 895 896 var info = &snap.Info{ 897 SuggestedName: "foo", 898 SideInfo: snap.SideInfo{ 899 Revision: snap.R(7), 900 }, 901 } 902 903 snapData := []string{ 904 "/var/snap/foo/7/somedatadir", 905 "/var/snap/foo/7/otherdata", 906 "/var/snap/foo/7", 907 "/var/snap/foo/common", 908 "/var/snap/foo/common/a", 909 "/home/user1/snap/foo/7/somedata", 910 "/home/user1/snap/foo/common", 911 } 912 var data []byte 913 var expected int 914 for _, d := range snapData { 915 data = append(data, 0) 916 expected += len(data) 917 c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil) 918 c.Assert(ioutil.WriteFile(filepath.Join(s.root, d, "somfile"), data, 0644), check.IsNil) 919 } 920 921 sz, err := backend.EstimateSnapshotSize(info, nil) 922 c.Assert(err, check.IsNil) 923 c.Check(sz, check.Equals, uint64(expected)) 924 } 925 926 func (s *snapshotSuite) TestEstimateSnapshotSizeEmpty(c *check.C) { 927 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 928 return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil 929 }) 930 defer restore() 931 932 var info = &snap.Info{ 933 SuggestedName: "foo", 934 SideInfo: snap.SideInfo{ 935 Revision: snap.R(7), 936 }, 937 } 938 939 snapData := []string{ 940 "/var/snap/foo/common", 941 "/var/snap/foo/7", 942 "/home/user1/snap/foo/7", 943 "/home/user1/snap/foo/common", 944 } 945 for _, d := range snapData { 946 c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil) 947 } 948 949 sz, err := backend.EstimateSnapshotSize(info, nil) 950 c.Assert(err, check.IsNil) 951 c.Check(sz, check.Equals, uint64(0)) 952 } 953 954 func (s *snapshotSuite) TestEstimateSnapshotPassesUsernames(c *check.C) { 955 var gotUsernames []string 956 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 957 gotUsernames = usernames 958 return nil, nil 959 }) 960 defer restore() 961 962 var info = &snap.Info{ 963 SuggestedName: "foo", 964 SideInfo: snap.SideInfo{ 965 Revision: snap.R(7), 966 }, 967 } 968 969 _, err := backend.EstimateSnapshotSize(info, []string{"user1", "user2"}) 970 c.Assert(err, check.IsNil) 971 c.Check(gotUsernames, check.DeepEquals, []string{"user1", "user2"}) 972 } 973 974 func (s *snapshotSuite) TestEstimateSnapshotSizeNotDataDirs(c *check.C) { 975 restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) { 976 return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil 977 }) 978 defer restore() 979 980 var info = &snap.Info{ 981 SuggestedName: "foo", 982 SideInfo: snap.SideInfo{Revision: snap.R(7)}, 983 } 984 985 sz, err := backend.EstimateSnapshotSize(info, nil) 986 c.Assert(err, check.IsNil) 987 c.Check(sz, check.Equals, uint64(0)) 988 } 989 func (s *snapshotSuite) TestExportTwice(c *check.C) { 990 // use mocking done in snapshotSuite.SetUpTest 991 info := &snap.Info{ 992 SideInfo: snap.SideInfo{ 993 RealName: "hello-snap", 994 Revision: snap.R(42), 995 SnapID: "hello-id", 996 }, 997 Version: "v1.33", 998 } 999 // create a snapshot 1000 shID := uint64(12) 1001 _, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}, &backend.Flags{}) 1002 c.Check(err, check.IsNil) 1003 1004 // num_files + export.json + footer 1005 expectedSize := int64(4*512 + 1024 + 2*512) 1006 // do on export at the start of the epoch 1007 restore := backend.MockTimeNow(func() time.Time { return time.Time{} }) 1008 defer restore() 1009 // export once 1010 buf := bytes.NewBuffer(nil) 1011 ctx := context.Background() 1012 se, err := backend.NewSnapshotExport(ctx, shID) 1013 c.Check(err, check.IsNil) 1014 err = se.Init() 1015 c.Assert(err, check.IsNil) 1016 c.Check(se.Size(), check.Equals, expectedSize) 1017 // and we can stream the data 1018 err = se.StreamTo(buf) 1019 c.Assert(err, check.IsNil) 1020 c.Check(buf.Len(), check.Equals, int(expectedSize)) 1021 1022 // and again to ensure size does not change when exported again 1023 // 1024 // Note that moving beyond year 2242 will change the tar format 1025 // used by the go internal tar and that will make the size actually 1026 // change. 1027 restore = backend.MockTimeNow(func() time.Time { return time.Date(2242, 1, 1, 12, 0, 0, 0, time.UTC) }) 1028 defer restore() 1029 se2, err := backend.NewSnapshotExport(ctx, shID) 1030 c.Check(err, check.IsNil) 1031 err = se2.Init() 1032 c.Assert(err, check.IsNil) 1033 c.Check(se2.Size(), check.Equals, expectedSize) 1034 // and we can stream the data 1035 buf.Reset() 1036 err = se2.StreamTo(buf) 1037 c.Assert(err, check.IsNil) 1038 c.Check(buf.Len(), check.Equals, int(expectedSize)) 1039 } 1040 1041 func (s *snapshotSuite) TestExportUnhappy(c *check.C) { 1042 se, err := backend.NewSnapshotExport(context.Background(), 5) 1043 c.Assert(err, check.ErrorMatches, "no snapshot data found for 5") 1044 c.Assert(se, check.IsNil) 1045 }