github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapshotstate/snapshotstate_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 snapshotstate_test 21 22 import ( 23 "context" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "os" 28 "os/exec" 29 "os/user" 30 "path/filepath" 31 "sort" 32 "strings" 33 "testing" 34 "time" 35 36 "gopkg.in/check.v1" 37 38 "github.com/snapcore/snapd/client" 39 "github.com/snapcore/snapd/dirs" 40 "github.com/snapcore/snapd/osutil/sys" 41 "github.com/snapcore/snapd/overlord" 42 "github.com/snapcore/snapd/overlord/configstate/config" 43 "github.com/snapcore/snapd/overlord/snapshotstate" 44 "github.com/snapcore/snapd/overlord/snapshotstate/backend" 45 "github.com/snapcore/snapd/overlord/snapstate" 46 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 47 "github.com/snapcore/snapd/overlord/state" 48 "github.com/snapcore/snapd/release" 49 "github.com/snapcore/snapd/snap" 50 "github.com/snapcore/snapd/snap/snaptest" 51 "github.com/snapcore/snapd/testutil" 52 ) 53 54 type snapshotSuite struct{} 55 56 var _ = check.Suite(&snapshotSuite{}) 57 58 // tie gocheck into testing 59 func TestSnapshot(t *testing.T) { check.TestingT(t) } 60 61 func (snapshotSuite) SetUpTest(c *check.C) { 62 dirs.SetRootDir(c.MkDir()) 63 } 64 65 func (snapshotSuite) TearDownTest(c *check.C) { 66 dirs.SetRootDir("/") 67 } 68 69 func (snapshotSuite) TestNewSnapshotSetID(c *check.C) { 70 st := state.New(nil) 71 st.Lock() 72 defer st.Unlock() 73 sid, err := snapshotstate.NewSnapshotSetID(st) 74 c.Assert(err, check.IsNil) 75 c.Check(sid, check.Equals, uint64(1)) 76 77 sid, err = snapshotstate.NewSnapshotSetID(st) 78 c.Assert(err, check.IsNil) 79 c.Check(sid, check.Equals, uint64(2)) 80 } 81 82 func (snapshotSuite) TestAllActiveSnapNames(c *check.C) { 83 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 84 return map[string]*snapstate.SnapState{ 85 "a-snap": {Active: true}, 86 "b-snap": {}, 87 "c-snap": {Active: true}, 88 }, nil 89 } 90 91 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 92 93 // loop to check sortedness 94 for i := 0; i < 100; i++ { 95 names, err := snapshotstate.AllActiveSnapNames(nil) 96 c.Assert(err, check.IsNil) 97 c.Check(names, check.DeepEquals, []string{"a-snap", "c-snap"}) 98 } 99 } 100 101 func (snapshotSuite) TestAllActiveSnapNamesError(c *check.C) { 102 errBad := errors.New("bad") 103 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 104 return nil, errBad 105 } 106 107 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 108 109 names, err := snapshotstate.AllActiveSnapNames(nil) 110 c.Check(err, check.Equals, errBad) 111 c.Check(names, check.IsNil) 112 } 113 114 func (snapshotSuite) TestSnapSummariesInSnapshotSet(c *check.C) { 115 shotfileA, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 116 c.Assert(err, check.IsNil) 117 defer shotfileA.Close() 118 shotfileB, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 119 c.Assert(err, check.IsNil) 120 defer shotfileB.Close() 121 122 setID := uint64(42) 123 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 124 c.Assert(f(&backend.Reader{ 125 // wanted 126 Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}}, 127 File: shotfileA, 128 }), check.IsNil) 129 c.Assert(f(&backend.Reader{ 130 // not wanted (bad set id) 131 Snapshot: client.Snapshot{SetID: setID + 1, Snap: "a-snap", SnapID: "a-id"}, 132 File: shotfileA, 133 }), check.IsNil) 134 c.Assert(f(&backend.Reader{ 135 // wanted 136 Snapshot: client.Snapshot{SetID: setID, Snap: "b-snap", SnapID: "b-id"}, 137 File: shotfileB, 138 }), check.IsNil) 139 return nil 140 } 141 defer snapshotstate.MockBackendIter(fakeIter)() 142 143 summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil) 144 c.Assert(err, check.IsNil) 145 c.Assert(summaries.AsMaps(), check.DeepEquals, []map[string]string{ 146 {"snap": "a-snap", "snapID": "a-id", "filename": shotfileA.Name(), "epoch": `{"read":[42],"write":[17]}`}, 147 {"snap": "b-snap", "snapID": "b-id", "filename": shotfileB.Name(), "epoch": "0"}, 148 }) 149 } 150 151 func (snapshotSuite) TestSnapSummariesInSnapshotSetSnaps(c *check.C) { 152 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 153 c.Assert(err, check.IsNil) 154 defer shotfile.Close() 155 setID := uint64(42) 156 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 157 c.Assert(f(&backend.Reader{ 158 // wanted 159 Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap", SnapID: "a-id"}, 160 File: shotfile, 161 }), check.IsNil) 162 c.Assert(f(&backend.Reader{ 163 // not wanted (bad set id) 164 Snapshot: client.Snapshot{SetID: setID + 1, Snap: "a-snap", SnapID: "a-id"}, 165 File: shotfile, 166 }), check.IsNil) 167 c.Assert(f(&backend.Reader{ 168 // not wanted (bad snap name) 169 Snapshot: client.Snapshot{SetID: setID, Snap: "c-snap", SnapID: "c-id"}, 170 File: shotfile, 171 }), check.IsNil) 172 return nil 173 } 174 defer snapshotstate.MockBackendIter(fakeIter)() 175 176 summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, []string{"a-snap"}) 177 c.Assert(err, check.IsNil) 178 c.Check(summaries.AsMaps(), check.DeepEquals, []map[string]string{ 179 {"snap": "a-snap", "snapID": "a-id", "filename": shotfile.Name(), "epoch": "0"}, 180 }) 181 } 182 183 func (snapshotSuite) TestSnapSummariesInSnapshotSetErrors(c *check.C) { 184 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 185 c.Assert(err, check.IsNil) 186 defer shotfile.Close() 187 setID := uint64(42) 188 errBad := errors.New("bad") 189 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 190 c.Assert(f(&backend.Reader{ 191 // wanted 192 Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap"}, 193 File: shotfile, 194 }), check.IsNil) 195 196 return errBad 197 } 198 defer snapshotstate.MockBackendIter(fakeIter)() 199 200 summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil) 201 c.Assert(err, check.Equals, errBad) 202 c.Check(summaries, check.IsNil) 203 } 204 205 func (snapshotSuite) TestSnapSummariesInSnapshotSetNotFound(c *check.C) { 206 setID := uint64(42) 207 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 208 c.Assert(err, check.IsNil) 209 defer shotfile.Close() 210 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 211 c.Assert(f(&backend.Reader{ 212 // not wanted 213 Snapshot: client.Snapshot{SetID: setID - 1, Snap: "a-snap"}, 214 File: shotfile, 215 }), check.IsNil) 216 217 return nil 218 } 219 defer snapshotstate.MockBackendIter(fakeIter)() 220 221 summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil) 222 c.Assert(err, check.Equals, client.ErrSnapshotSetNotFound) 223 c.Check(summaries, check.IsNil) 224 } 225 226 func (snapshotSuite) TestSnapSummariesInSnapshotSetEmptyNotFound(c *check.C) { 227 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { return nil } 228 defer snapshotstate.MockBackendIter(fakeIter)() 229 230 summaries, err := snapshotstate.SnapSummariesInSnapshotSet(42, nil) 231 c.Assert(err, check.Equals, client.ErrSnapshotSetNotFound) 232 c.Check(summaries, check.IsNil) 233 } 234 235 func (snapshotSuite) TestSnapSummariesInSnapshotSetSnapNotFound(c *check.C) { 236 setID := uint64(42) 237 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 238 c.Assert(err, check.IsNil) 239 defer shotfile.Close() 240 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 241 c.Assert(f(&backend.Reader{ 242 // not wanted 243 Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap"}, 244 File: shotfile, 245 }), check.IsNil) 246 247 return nil 248 } 249 defer snapshotstate.MockBackendIter(fakeIter)() 250 251 summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, []string{"b-snap"}) 252 c.Assert(err, check.Equals, client.ErrSnapshotSnapsNotFound) 253 c.Check(summaries, check.IsNil) 254 } 255 256 func (snapshotSuite) TestCheckConflict(c *check.C) { 257 st := state.New(nil) 258 st.Lock() 259 defer st.Unlock() 260 chg := st.NewChange("some-change", "...") 261 tsk := st.NewTask("some-task", "...") 262 tsk.SetStatus(state.DoingStatus) 263 chg.AddTask(tsk) 264 265 // no snapshot state 266 err := snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task") 267 c.Assert(err, check.ErrorMatches, "internal error: task 1 .some-task. is missing snapshot information") 268 269 // wrong snapshot state 270 tsk.Set("snapshot-setup", "hello") 271 err = snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task") 272 c.Assert(err, check.ErrorMatches, "internal error.* could not unmarshal.*") 273 274 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 275 276 err = snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task") 277 c.Assert(err, check.ErrorMatches, "cannot operate on snapshot set #42 while change \"1\" is in progress") 278 279 // no change with that label 280 c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-other-task"), check.IsNil) 281 282 // no change with that snapshot id 283 c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 43, "some-task"), check.IsNil) 284 285 // no non-ready change 286 tsk.SetStatus(state.DoneStatus) 287 c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task"), check.IsNil) 288 } 289 290 func (snapshotSuite) TestSaveChecksSnapnamesError(c *check.C) { 291 defer snapshotstate.MockSnapstateAll(func(*state.State) (map[string]*snapstate.SnapState, error) { 292 return nil, errors.New("bzzt") 293 })() 294 295 st := state.New(nil) 296 st.Lock() 297 defer st.Unlock() 298 _, _, _, err := snapshotstate.Save(st, nil, nil) 299 c.Check(err, check.ErrorMatches, "bzzt") 300 } 301 302 func (snapshotSuite) createConflictingChange(c *check.C) (st *state.State, restore func()) { 303 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 304 c.Assert(err, check.IsNil) 305 shotfile.Close() 306 307 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 308 c.Assert(f(&backend.Reader{ 309 Snapshot: client.Snapshot{SetID: 42, Snap: "foo"}, 310 File: shotfile, 311 }), check.IsNil) 312 313 return nil 314 } 315 restoreIter := snapshotstate.MockBackendIter(fakeIter) 316 317 o := overlord.Mock() 318 st = o.State() 319 320 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 321 c.Assert(err, check.IsNil) 322 o.AddManager(stmgr) 323 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 324 o.AddManager(shmgr) 325 326 st.Lock() 327 defer func() { 328 if c.Failed() { 329 // something went wrong 330 st.Unlock() 331 } 332 }() 333 334 snapstate.Set(st, "foo", &snapstate.SnapState{ 335 Active: true, 336 Sequence: []*snap.SideInfo{ 337 {RealName: "foo", Revision: snap.R(1)}, 338 }, 339 Current: snap.R(1), 340 SnapType: "app", 341 }) 342 343 r := snapstatetest.UseFallbackDeviceModel() 344 defer r() 345 346 chg := st.NewChange("rm foo", "...") 347 rmTasks, err := snapstate.Remove(st, "foo", snap.R(0), nil) 348 c.Assert(err, check.IsNil) 349 c.Assert(rmTasks, check.NotNil) 350 chg.AddAll(rmTasks) 351 352 return st, func() { 353 shotfile.Close() 354 st.Unlock() 355 restoreIter() 356 } 357 } 358 359 func (s snapshotSuite) TestSaveChecksSnapstateConflict(c *check.C) { 360 st, restore := s.createConflictingChange(c) 361 defer restore() 362 363 _, _, _, err := snapshotstate.Save(st, []string{"foo"}, nil) 364 c.Assert(err, check.NotNil) 365 c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{}) 366 } 367 368 func (snapshotSuite) TestSaveConflictsWithSnapstate(c *check.C) { 369 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 370 return map[string]*snapstate.SnapState{ 371 "foo": {Active: true}, 372 }, nil 373 } 374 375 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 376 377 o := overlord.Mock() 378 st := o.State() 379 380 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 381 c.Assert(err, check.IsNil) 382 o.AddManager(stmgr) 383 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 384 o.AddManager(shmgr) 385 386 st.Lock() 387 defer st.Unlock() 388 389 snapstate.Set(st, "foo", &snapstate.SnapState{ 390 Active: true, 391 Sequence: []*snap.SideInfo{ 392 {RealName: "foo", Revision: snap.R(1)}, 393 }, 394 Current: snap.R(1), 395 SnapType: "app", 396 }) 397 398 chg := st.NewChange("snapshot-save", "...") 399 _, _, saveTasks, err := snapshotstate.Save(st, nil, nil) 400 c.Assert(err, check.IsNil) 401 chg.AddAll(saveTasks) 402 403 _, err = snapstate.Disable(st, "foo") 404 c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-save" change in progress`) 405 } 406 407 func (snapshotSuite) TestSaveChecksSnapstateConflictError(c *check.C) { 408 defer snapshotstate.MockSnapstateCheckChangeConflictMany(func(*state.State, []string, string) error { 409 return errors.New("bzzt") 410 })() 411 412 st := state.New(nil) 413 st.Lock() 414 defer st.Unlock() 415 _, _, _, err := snapshotstate.Save(st, nil, nil) 416 c.Check(err, check.ErrorMatches, "bzzt") 417 } 418 419 func (snapshotSuite) TestSaveChecksSetIDError(c *check.C) { 420 st := state.New(nil) 421 st.Lock() 422 defer st.Unlock() 423 424 st.Set("last-snapshot-set-id", "3/4") 425 426 _, _, _, err := snapshotstate.Save(st, nil, nil) 427 c.Check(err, check.ErrorMatches, ".* could not unmarshal .*") 428 } 429 430 func (snapshotSuite) TestSaveNoSnapsInState(c *check.C) { 431 st := state.New(nil) 432 st.Lock() 433 defer st.Unlock() 434 435 setID, saved, taskset, err := snapshotstate.Save(st, nil, nil) 436 c.Assert(err, check.IsNil) 437 c.Check(setID, check.Equals, uint64(1)) 438 c.Check(saved, check.HasLen, 0) 439 c.Check(taskset.Tasks(), check.HasLen, 0) 440 } 441 442 func (snapshotSuite) TestSaveSomeSnaps(c *check.C) { 443 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 444 return map[string]*snapstate.SnapState{ 445 "a-snap": {Active: true}, 446 "b-snap": {}, 447 "c-snap": {Active: true}, 448 }, nil 449 } 450 451 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 452 453 st := state.New(nil) 454 st.Lock() 455 defer st.Unlock() 456 457 setID, saved, taskset, err := snapshotstate.Save(st, nil, nil) 458 c.Assert(err, check.IsNil) 459 c.Check(setID, check.Equals, uint64(1)) 460 c.Check(saved, check.DeepEquals, []string{"a-snap", "c-snap"}) 461 tasks := taskset.Tasks() 462 c.Assert(tasks, check.HasLen, 2) 463 c.Check(tasks[0].Kind(), check.Equals, "save-snapshot") 464 c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`) 465 c.Check(tasks[1].Kind(), check.Equals, "save-snapshot") 466 c.Check(tasks[1].Summary(), check.Equals, `Save data of snap "c-snap" in snapshot set #1`) 467 } 468 469 func (snapshotSuite) TestSaveOneSnap(c *check.C) { 470 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 471 // snapstate.All isn't called when a snap name is passed in 472 return nil, errors.New("bzzt") 473 } 474 475 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 476 477 st := state.New(nil) 478 st.Lock() 479 defer st.Unlock() 480 481 setID, saved, taskset, err := snapshotstate.Save(st, []string{"a-snap"}, []string{"a-user"}) 482 c.Assert(err, check.IsNil) 483 c.Check(setID, check.Equals, uint64(1)) 484 c.Check(saved, check.DeepEquals, []string{"a-snap"}) 485 tasks := taskset.Tasks() 486 c.Assert(tasks, check.HasLen, 1) 487 c.Check(tasks[0].Kind(), check.Equals, "save-snapshot") 488 c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`) 489 var snapshot map[string]interface{} 490 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 491 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 492 "set-id": 1., 493 "snap": "a-snap", 494 "users": []interface{}{"a-user"}, 495 "current": "unset", 496 }) 497 } 498 499 func (snapshotSuite) TestSaveIntegration(c *check.C) { 500 if os.Geteuid() == 0 { 501 c.Skip("this test cannot run as root (runuser will fail)") 502 } 503 504 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 505 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 506 507 defer backend.MockUserLookup(func(username string) (*user.User, error) { 508 if username != "a-user" { 509 c.Fatalf("unexpected user %q", username) 510 } 511 return &user.User{ 512 Uid: fmt.Sprint(sys.Geteuid()), 513 Username: username, 514 HomeDir: homedir, 515 }, nil 516 })() 517 518 o := overlord.Mock() 519 st := o.State() 520 521 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 522 c.Assert(err, check.IsNil) 523 o.AddManager(stmgr) 524 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 525 o.AddManager(shmgr) 526 o.AddManager(o.TaskRunner()) 527 528 st.Lock() 529 defer st.Unlock() 530 531 snapshots := make(map[string]*client.Snapshot, 3) 532 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 533 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 534 snapstate.Set(st, name, &snapstate.SnapState{ 535 Active: true, 536 Sequence: []*snap.SideInfo{sideInfo}, 537 Current: sideInfo.Revision, 538 SnapType: "app", 539 }) 540 snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo) 541 542 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 543 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil) 544 545 snapshots[name] = &client.Snapshot{ 546 SetID: 1, 547 Snap: name, 548 Version: "v1", 549 Revision: sideInfo.Revision, 550 Epoch: snap.E("0"), 551 } 552 } 553 554 setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"}) 555 c.Assert(err, check.IsNil) 556 c.Check(setID, check.Equals, uint64(1)) 557 c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 558 559 change := st.NewChange("save-snapshot", "...") 560 change.AddAll(taskset) 561 562 t0 := time.Now() 563 564 st.Unlock() 565 c.Assert(o.Settle(5*time.Second), check.IsNil) 566 st.Lock() 567 c.Check(change.Err(), check.IsNil) 568 569 tf := time.Now() 570 c.Assert(backend.Iter(context.TODO(), func(r *backend.Reader) error { 571 c.Check(r.Check(context.TODO(), nil), check.IsNil) 572 573 // check the unknowables, and zero them out 574 c.Check(r.Snapshot.Time.After(t0), check.Equals, true) 575 c.Check(r.Snapshot.Time.Before(tf), check.Equals, true) 576 c.Check(r.Snapshot.Size > 0, check.Equals, true) 577 c.Assert(r.Snapshot.SHA3_384, check.HasLen, 1) 578 c.Check(r.Snapshot.SHA3_384["user/a-user.tgz"], check.HasLen, 96) 579 580 r.Snapshot.Time = time.Time{} 581 r.Snapshot.Size = 0 582 r.Snapshot.SHA3_384 = nil 583 584 c.Check(&r.Snapshot, check.DeepEquals, snapshots[r.Snapshot.Snap]) 585 return nil 586 }), check.IsNil) 587 } 588 589 func (snapshotSuite) TestSaveIntegrationFails(c *check.C) { 590 if os.Geteuid() == 0 { 591 c.Skip("this test cannot run as root (runuser will fail)") 592 } 593 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 594 // sanity check: no files in snapshot dir 595 out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput() 596 c.Assert(err, check.IsNil) 597 c.Check(string(out), check.Equals, "") 598 599 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 600 601 // Mock "tar" so that the tars finish in the expected order. 602 // Locally .01s and .02s do the trick with count=1000; 603 // padded a lot bigger for slower systems. 604 mocktar := testutil.MockCommand(c, "tar", ` 605 case "$*" in 606 */too-snap/*) 607 sleep .5 608 ;; 609 */tri-snap/*) 610 sleep 1 611 ;; 612 esac 613 export LANG=C 614 exec /bin/tar "$@" 615 `) 616 defer mocktar.Restore() 617 618 defer backend.MockUserLookup(func(username string) (*user.User, error) { 619 if username != "a-user" { 620 c.Fatalf("unexpected user %q", username) 621 } 622 return &user.User{ 623 Uid: fmt.Sprint(sys.Geteuid()), 624 Username: username, 625 HomeDir: homedir, 626 }, nil 627 })() 628 629 o := overlord.Mock() 630 st := o.State() 631 632 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 633 c.Assert(err, check.IsNil) 634 o.AddManager(stmgr) 635 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 636 o.AddManager(shmgr) 637 o.AddManager(o.TaskRunner()) 638 639 st.Lock() 640 defer st.Unlock() 641 642 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 643 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 644 snapstate.Set(st, name, &snapstate.SnapState{ 645 Active: true, 646 Sequence: []*snap.SideInfo{sideInfo}, 647 Current: sideInfo.Revision, 648 SnapType: "app", 649 }) 650 snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo) 651 652 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 653 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common"), 0755), check.IsNil) 654 mode := os.FileMode(0755) 655 if i == 1 { 656 mode = 0 657 } 658 c.Assert(os.Mkdir(filepath.Join(homedir, "snap", name, "common", "common-"+name), mode), check.IsNil) 659 } 660 661 setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"}) 662 c.Assert(err, check.IsNil) 663 c.Check(setID, check.Equals, uint64(1)) 664 c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 665 666 change := st.NewChange("save-snapshot", "...") 667 change.AddAll(taskset) 668 669 st.Unlock() 670 c.Assert(o.Settle(5*time.Second), check.IsNil) 671 st.Lock() 672 c.Check(change.Err(), check.NotNil) 673 tasks := change.Tasks() 674 c.Assert(tasks, check.HasLen, 3) 675 676 // task 0 (for "one-snap") will have been undone 677 c.Check(tasks[0].Summary(), testutil.Contains, `"one-snap"`) // sanity check: task 0 is one-snap's 678 c.Check(tasks[0].Status(), check.Equals, state.UndoneStatus) 679 680 // task 1 (for "too-snap") will have errored 681 c.Check(tasks[1].Summary(), testutil.Contains, `"too-snap"`) // sanity check: task 1 is too-snap's 682 c.Check(tasks[1].Status(), check.Equals, state.ErrorStatus) 683 c.Check(strings.Join(tasks[1].Log(), "\n"), check.Matches, `\S+ ERROR cannot create archive: .* Permission denied .and \d+ more.`) 684 685 // task 2 (for "tri-snap") will have errored as well, hopefully, but it's a race (see the "tar" comment above) 686 c.Check(tasks[2].Summary(), testutil.Contains, `"tri-snap"`) // sanity check: task 2 is tri-snap's 687 c.Check(tasks[2].Status(), check.Equals, state.ErrorStatus, check.Commentf("if this ever fails, duplicate the fake tar sleeps please")) 688 // sometimes you'll get one, sometimes you'll get the other (depending on ordering of events) 689 c.Check(strings.Join(tasks[2].Log(), "\n"), check.Matches, `\S+ ERROR( tar failed:)? context canceled`) 690 691 // no zips left behind, not for errors, not for undos \o/ 692 out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput() 693 c.Assert(err, check.IsNil) 694 c.Check(string(out), check.Equals, "") 695 } 696 697 func (snapshotSuite) TestRestoreChecksIterError(c *check.C) { 698 defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error { 699 return errors.New("bzzt") 700 })() 701 702 st := state.New(nil) 703 st.Lock() 704 defer st.Unlock() 705 706 _, _, err := snapshotstate.Restore(st, 42, nil, nil) 707 c.Assert(err, check.ErrorMatches, "bzzt") 708 } 709 710 func (s snapshotSuite) TestRestoreChecksSnapstateConflicts(c *check.C) { 711 st, restore := s.createConflictingChange(c) 712 defer restore() 713 714 _, _, err := snapshotstate.Restore(st, 42, nil, nil) 715 c.Assert(err, check.NotNil) 716 c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{}) 717 718 } 719 720 func (snapshotSuite) TestRestoreConflictsWithSnapstate(c *check.C) { 721 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 722 c.Assert(err, check.IsNil) 723 defer shotfile.Close() 724 725 sideInfo := &snap.SideInfo{RealName: "foo", Revision: snap.R(1)} 726 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 727 return map[string]*snapstate.SnapState{ 728 "foo": { 729 Active: true, 730 Sequence: []*snap.SideInfo{sideInfo}, 731 Current: sideInfo.Revision, 732 }, 733 }, nil 734 } 735 snaptest.MockSnap(c, "{name: foo, version: v1}", sideInfo) 736 737 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 738 739 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 740 c.Assert(f(&backend.Reader{ 741 Snapshot: client.Snapshot{SetID: 42, Snap: "foo"}, 742 File: shotfile, 743 }), check.IsNil) 744 745 return nil 746 } 747 defer snapshotstate.MockBackendIter(fakeIter)() 748 749 o := overlord.Mock() 750 st := o.State() 751 752 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 753 c.Assert(err, check.IsNil) 754 o.AddManager(stmgr) 755 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 756 o.AddManager(shmgr) 757 758 st.Lock() 759 defer st.Unlock() 760 761 snapstate.Set(st, "foo", &snapstate.SnapState{ 762 Active: true, 763 Sequence: []*snap.SideInfo{ 764 {RealName: "foo", Revision: snap.R(1)}, 765 }, 766 Current: snap.R(1), 767 SnapType: "app", 768 }) 769 770 chg := st.NewChange("snapshot-restore", "...") 771 _, restoreTasks, err := snapshotstate.Restore(st, 42, nil, nil) 772 c.Assert(err, check.IsNil) 773 chg.AddAll(restoreTasks) 774 775 _, err = snapstate.Disable(st, "foo") 776 c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-restore" change in progress`) 777 } 778 779 func (snapshotSuite) TestRestoreChecksForgetConflicts(c *check.C) { 780 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 781 c.Assert(err, check.IsNil) 782 defer shotfile.Close() 783 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 784 c.Assert(f(&backend.Reader{ 785 // not wanted 786 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 787 File: shotfile, 788 }), check.IsNil) 789 790 return nil 791 } 792 defer snapshotstate.MockBackendIter(fakeIter)() 793 794 st := state.New(nil) 795 st.Lock() 796 defer st.Unlock() 797 chg := st.NewChange("forget-snapshot-change", "...") 798 tsk := st.NewTask("forget-snapshot", "...") 799 tsk.SetStatus(state.DoingStatus) 800 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 801 chg.AddTask(tsk) 802 803 _, _, err = snapshotstate.Restore(st, 42, nil, nil) 804 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 805 } 806 807 func (snapshotSuite) TestRestoreChecksChangesToSnapID(c *check.C) { 808 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 809 c.Assert(err, check.IsNil) 810 defer shotfile.Close() 811 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 812 return map[string]*snapstate.SnapState{ 813 "a-snap": { 814 Active: true, 815 Sequence: []*snap.SideInfo{ 816 {RealName: "a-snap", Revision: snap.R(1), SnapID: "1234567890"}, 817 }, 818 Current: snap.R(1), 819 }, 820 }, nil 821 } 822 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 823 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 824 c.Assert(f(&backend.Reader{ 825 // not wanted 826 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap", SnapID: "0987654321"}, 827 File: shotfile, 828 }), check.IsNil) 829 830 return nil 831 } 832 defer snapshotstate.MockBackendIter(fakeIter)() 833 834 st := state.New(nil) 835 st.Lock() 836 defer st.Unlock() 837 838 _, _, err = snapshotstate.Restore(st, 42, nil, nil) 839 c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(ID 1234567…\) does not match snapshot \(ID 0987654…\)`) 840 } 841 842 func (snapshotSuite) TestRestoreChecksChangesToEpoch(c *check.C) { 843 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 844 c.Assert(err, check.IsNil) 845 defer shotfile.Close() 846 847 sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)} 848 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 849 return map[string]*snapstate.SnapState{ 850 "a-snap": { 851 Active: true, 852 Sequence: []*snap.SideInfo{sideInfo}, 853 Current: sideInfo.Revision, 854 }, 855 }, nil 856 } 857 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 858 snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: 17}", sideInfo) 859 860 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 861 c.Assert(f(&backend.Reader{ 862 // not wanted 863 Snapshot: client.Snapshot{ 864 SetID: 42, 865 Snap: "a-snap", 866 Epoch: snap.E("42"), 867 }, 868 File: shotfile, 869 }), check.IsNil) 870 871 return nil 872 } 873 defer snapshotstate.MockBackendIter(fakeIter)() 874 875 st := state.New(nil) 876 st.Lock() 877 defer st.Unlock() 878 879 _, _, err = snapshotstate.Restore(st, 42, nil, nil) 880 c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(epoch 17\) cannot read snapshot data \(epoch 42\)`) 881 } 882 883 func (snapshotSuite) TestRestoreWorksWithCompatibleEpoch(c *check.C) { 884 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 885 c.Assert(err, check.IsNil) 886 defer shotfile.Close() 887 888 sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)} 889 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 890 return map[string]*snapstate.SnapState{ 891 "a-snap": { 892 Active: true, 893 Sequence: []*snap.SideInfo{sideInfo}, 894 Current: sideInfo.Revision, 895 }, 896 }, nil 897 } 898 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 899 snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: {read: [17, 42], write: [42]}}", sideInfo) 900 901 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 902 c.Assert(f(&backend.Reader{ 903 // not wanted 904 Snapshot: client.Snapshot{ 905 SetID: 42, 906 Snap: "a-snap", 907 Epoch: snap.E("17"), 908 }, 909 File: shotfile, 910 }), check.IsNil) 911 912 return nil 913 } 914 defer snapshotstate.MockBackendIter(fakeIter)() 915 916 st := state.New(nil) 917 st.Lock() 918 defer st.Unlock() 919 920 found, taskset, err := snapshotstate.Restore(st, 42, nil, nil) 921 c.Assert(err, check.IsNil) 922 c.Check(found, check.DeepEquals, []string{"a-snap"}) 923 tasks := taskset.Tasks() 924 c.Assert(tasks, check.HasLen, 1) 925 c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot") 926 c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`) 927 var snapshot map[string]interface{} 928 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 929 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 930 "set-id": 42., 931 "snap": "a-snap", 932 "filename": shotfile.Name(), 933 "current": "1", 934 }) 935 } 936 937 func (snapshotSuite) TestRestore(c *check.C) { 938 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 939 c.Assert(err, check.IsNil) 940 defer shotfile.Close() 941 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 942 c.Assert(f(&backend.Reader{ 943 // not wanted 944 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 945 File: shotfile, 946 }), check.IsNil) 947 948 return nil 949 } 950 defer snapshotstate.MockBackendIter(fakeIter)() 951 952 st := state.New(nil) 953 st.Lock() 954 defer st.Unlock() 955 956 found, taskset, err := snapshotstate.Restore(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"}) 957 c.Assert(err, check.IsNil) 958 c.Check(found, check.DeepEquals, []string{"a-snap"}) 959 tasks := taskset.Tasks() 960 c.Assert(tasks, check.HasLen, 1) 961 c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot") 962 c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`) 963 var snapshot map[string]interface{} 964 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 965 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 966 "set-id": 42., 967 "snap": "a-snap", 968 "filename": shotfile.Name(), 969 "users": []interface{}{"a-user"}, 970 "current": "unset", 971 }) 972 } 973 974 func (snapshotSuite) TestRestoreIntegration(c *check.C) { 975 if os.Geteuid() == 0 { 976 c.Skip("this test cannot run as root (runuser will fail)") 977 } 978 979 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 980 homedirA := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 981 homedirB := filepath.Join(dirs.GlobalRootDir, "home", "b-user") 982 983 defer backend.MockUserLookup(func(username string) (*user.User, error) { 984 if username != "a-user" && username != "b-user" { 985 c.Fatalf("unexpected user %q", username) 986 return nil, user.UnknownUserError(username) 987 } 988 return &user.User{ 989 Uid: fmt.Sprint(sys.Geteuid()), 990 Username: username, 991 HomeDir: filepath.Join(dirs.GlobalRootDir, "home", username), 992 }, nil 993 994 })() 995 996 o := overlord.Mock() 997 st := o.State() 998 999 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 1000 c.Assert(err, check.IsNil) 1001 o.AddManager(stmgr) 1002 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 1003 o.AddManager(shmgr) 1004 o.AddManager(o.TaskRunner()) 1005 1006 st.Lock() 1007 defer st.Unlock() 1008 1009 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 1010 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 1011 snapstate.Set(st, name, &snapstate.SnapState{ 1012 Active: true, 1013 Sequence: []*snap.SideInfo{sideInfo}, 1014 Current: sideInfo.Revision, 1015 SnapType: "app", 1016 }) 1017 snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo) 1018 1019 for _, home := range []string{homedirA, homedirB} { 1020 c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 1021 c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, "common", "common-"+name), 0755), check.IsNil) 1022 } 1023 1024 _, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user", "b-user"}, nil) 1025 c.Assert(err, check.IsNil) 1026 } 1027 1028 // move the old away 1029 c.Assert(os.Rename(filepath.Join(homedirA, "snap"), filepath.Join(homedirA, "snap.old")), check.IsNil) 1030 // remove b-user's home 1031 c.Assert(os.RemoveAll(homedirB), check.IsNil) 1032 1033 found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user", "b-user"}) 1034 c.Assert(err, check.IsNil) 1035 sort.Strings(found) 1036 c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 1037 1038 change := st.NewChange("restore-snapshot", "...") 1039 change.AddAll(taskset) 1040 1041 st.Unlock() 1042 c.Assert(o.Settle(5*time.Second), check.IsNil) 1043 st.Lock() 1044 c.Check(change.Err(), check.IsNil) 1045 1046 // the three restores warn about the missing home (but no errors, no panics) 1047 for _, task := range change.Tasks() { 1048 c.Check(strings.Join(task.Log(), "\n"), check.Matches, `.* Skipping restore of "[^"]+/home/b-user/[^"]+" as "[^"]+/home/b-user" doesn't exist.`) 1049 } 1050 1051 // check it was all brought back \o/ 1052 out, err := exec.Command("diff", "-rN", filepath.Join(homedirA, "snap"), filepath.Join("snap.old")).CombinedOutput() 1053 c.Assert(err, check.IsNil) 1054 c.Check(string(out), check.Equals, "") 1055 } 1056 1057 func (snapshotSuite) TestRestoreIntegrationFails(c *check.C) { 1058 if os.Geteuid() == 0 { 1059 c.Skip("this test cannot run as root (runuser will fail)") 1060 } 1061 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 1062 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 1063 1064 defer backend.MockUserLookup(func(username string) (*user.User, error) { 1065 if username != "a-user" { 1066 c.Fatalf("unexpected user %q", username) 1067 } 1068 return &user.User{ 1069 Uid: fmt.Sprint(sys.Geteuid()), 1070 Username: username, 1071 HomeDir: homedir, 1072 }, nil 1073 })() 1074 1075 o := overlord.Mock() 1076 st := o.State() 1077 1078 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 1079 c.Assert(err, check.IsNil) 1080 o.AddManager(stmgr) 1081 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 1082 o.AddManager(shmgr) 1083 o.AddManager(o.TaskRunner()) 1084 1085 st.Lock() 1086 defer st.Unlock() 1087 1088 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 1089 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 1090 snapstate.Set(st, name, &snapstate.SnapState{ 1091 Active: true, 1092 Sequence: []*snap.SideInfo{sideInfo}, 1093 Current: sideInfo.Revision, 1094 SnapType: "app", 1095 }) 1096 snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: vv1}", name), sideInfo) 1097 1098 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 1099 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil) 1100 1101 _, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user"}, nil) 1102 c.Assert(err, check.IsNil) 1103 } 1104 1105 // move the old away 1106 c.Assert(os.Rename(filepath.Join(homedir, "snap"), filepath.Join(homedir, "snap.old")), check.IsNil) 1107 // but poison the well 1108 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap"), 0755), check.IsNil) 1109 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", "too-snap"), 0), check.IsNil) 1110 1111 found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user"}) 1112 c.Assert(err, check.IsNil) 1113 sort.Strings(found) 1114 c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 1115 1116 change := st.NewChange("restore-snapshot", "...") 1117 change.AddAll(taskset) 1118 1119 st.Unlock() 1120 c.Assert(o.Settle(5*time.Second), check.IsNil) 1121 st.Lock() 1122 c.Check(change.Err(), check.NotNil) 1123 1124 tasks := change.Tasks() 1125 c.Check(tasks, check.HasLen, 3) 1126 for _, task := range tasks { 1127 if strings.Contains(task.Summary(), `"too-snap"`) { 1128 // too-snap was set up to fail, should always fail with 1129 // 'permission denied' (see the mkdirall w/mode 0 above) 1130 c.Check(task.Status(), check.Equals, state.ErrorStatus) 1131 c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR mkdir \S+: permission denied`) 1132 } else { 1133 // the other two might fail (ErrorStatus) if they're 1134 // still running when too-snap fails, or they might have 1135 // finished and needed to be undone (UndoneStatus); it's 1136 // a race, but either is fine. 1137 if task.Status() == state.ErrorStatus { 1138 c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR.* context canceled`) 1139 } else { 1140 c.Check(task.Status(), check.Equals, state.UndoneStatus) 1141 } 1142 } 1143 } 1144 1145 // remove the poison 1146 c.Assert(os.Remove(filepath.Join(homedir, "snap", "too-snap")), check.IsNil) 1147 1148 // check that nothing else was put there 1149 out, err := exec.Command("find", filepath.Join(homedir, "snap")).CombinedOutput() 1150 c.Assert(err, check.IsNil) 1151 c.Check(strings.TrimSpace(string(out)), check.Equals, filepath.Join(homedir, "snap")) 1152 } 1153 1154 func (snapshotSuite) TestCheckChecksIterError(c *check.C) { 1155 defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error { 1156 return errors.New("bzzt") 1157 })() 1158 1159 st := state.New(nil) 1160 st.Lock() 1161 defer st.Unlock() 1162 1163 _, _, err := snapshotstate.Check(st, 42, nil, nil) 1164 c.Assert(err, check.ErrorMatches, "bzzt") 1165 } 1166 1167 func (s snapshotSuite) TestCheckDoesNotTriggerSnapstateConflict(c *check.C) { 1168 st, restore := s.createConflictingChange(c) 1169 defer restore() 1170 1171 _, _, err := snapshotstate.Check(st, 42, nil, nil) 1172 c.Assert(err, check.IsNil) 1173 } 1174 1175 func (snapshotSuite) TestCheckChecksForgetConflicts(c *check.C) { 1176 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1177 c.Assert(err, check.IsNil) 1178 defer shotfile.Close() 1179 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1180 c.Assert(f(&backend.Reader{ 1181 // not wanted 1182 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1183 File: shotfile, 1184 }), check.IsNil) 1185 1186 return nil 1187 } 1188 defer snapshotstate.MockBackendIter(fakeIter)() 1189 1190 st := state.New(nil) 1191 st.Lock() 1192 defer st.Unlock() 1193 chg := st.NewChange("forget-snapshot-change", "...") 1194 tsk := st.NewTask("forget-snapshot", "...") 1195 tsk.SetStatus(state.DoingStatus) 1196 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1197 chg.AddTask(tsk) 1198 1199 _, _, err = snapshotstate.Check(st, 42, nil, nil) 1200 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 1201 } 1202 1203 func (snapshotSuite) TestCheck(c *check.C) { 1204 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1205 c.Assert(err, check.IsNil) 1206 defer shotfile.Close() 1207 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1208 c.Assert(f(&backend.Reader{ 1209 // not wanted 1210 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1211 File: shotfile, 1212 }), check.IsNil) 1213 1214 return nil 1215 } 1216 defer snapshotstate.MockBackendIter(fakeIter)() 1217 1218 st := state.New(nil) 1219 st.Lock() 1220 defer st.Unlock() 1221 1222 found, taskset, err := snapshotstate.Check(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"}) 1223 c.Assert(err, check.IsNil) 1224 c.Check(found, check.DeepEquals, []string{"a-snap"}) 1225 tasks := taskset.Tasks() 1226 c.Assert(tasks, check.HasLen, 1) 1227 c.Check(tasks[0].Kind(), check.Equals, "check-snapshot") 1228 c.Check(tasks[0].Summary(), check.Equals, `Check data of snap "a-snap" in snapshot set #42`) 1229 var snapshot map[string]interface{} 1230 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1231 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1232 "set-id": 42., 1233 "snap": "a-snap", 1234 "filename": shotfile.Name(), 1235 "users": []interface{}{"a-user"}, 1236 "current": "unset", 1237 }) 1238 } 1239 1240 func (snapshotSuite) TestForgetChecksIterError(c *check.C) { 1241 defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error { 1242 return errors.New("bzzt") 1243 })() 1244 1245 st := state.New(nil) 1246 st.Lock() 1247 defer st.Unlock() 1248 1249 _, _, err := snapshotstate.Forget(st, 42, nil) 1250 c.Assert(err, check.ErrorMatches, "bzzt") 1251 } 1252 1253 func (s snapshotSuite) TestForgetDoesNotTriggerSnapstateConflict(c *check.C) { 1254 st, restore := s.createConflictingChange(c) 1255 defer restore() 1256 1257 _, _, err := snapshotstate.Forget(st, 42, nil) 1258 c.Assert(err, check.IsNil) 1259 } 1260 1261 func (snapshotSuite) TestForgetChecksCheckConflicts(c *check.C) { 1262 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1263 c.Assert(err, check.IsNil) 1264 defer shotfile.Close() 1265 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1266 c.Assert(f(&backend.Reader{ 1267 // not wanted 1268 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1269 File: shotfile, 1270 }), check.IsNil) 1271 1272 return nil 1273 } 1274 defer snapshotstate.MockBackendIter(fakeIter)() 1275 1276 st := state.New(nil) 1277 st.Lock() 1278 defer st.Unlock() 1279 chg := st.NewChange("check-snapshot-change", "...") 1280 tsk := st.NewTask("check-snapshot", "...") 1281 tsk.SetStatus(state.DoingStatus) 1282 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1283 chg.AddTask(tsk) 1284 1285 _, _, err = snapshotstate.Forget(st, 42, nil) 1286 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 1287 } 1288 1289 func (snapshotSuite) TestForgetChecksRestoreConflicts(c *check.C) { 1290 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1291 c.Assert(err, check.IsNil) 1292 defer shotfile.Close() 1293 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1294 c.Assert(f(&backend.Reader{ 1295 // not wanted 1296 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1297 File: shotfile, 1298 }), check.IsNil) 1299 1300 return nil 1301 } 1302 defer snapshotstate.MockBackendIter(fakeIter)() 1303 1304 st := state.New(nil) 1305 st.Lock() 1306 defer st.Unlock() 1307 chg := st.NewChange("restore-snapshot-change", "...") 1308 tsk := st.NewTask("restore-snapshot", "...") 1309 tsk.SetStatus(state.DoingStatus) 1310 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1311 chg.AddTask(tsk) 1312 1313 _, _, err = snapshotstate.Forget(st, 42, nil) 1314 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 1315 } 1316 1317 func (snapshotSuite) TestForget(c *check.C) { 1318 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1319 c.Assert(err, check.IsNil) 1320 defer shotfile.Close() 1321 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1322 c.Assert(f(&backend.Reader{ 1323 // not wanted 1324 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1325 File: shotfile, 1326 }), check.IsNil) 1327 1328 return nil 1329 } 1330 defer snapshotstate.MockBackendIter(fakeIter)() 1331 1332 st := state.New(nil) 1333 st.Lock() 1334 defer st.Unlock() 1335 1336 found, taskset, err := snapshotstate.Forget(st, 42, []string{"a-snap", "b-snap"}) 1337 c.Assert(err, check.IsNil) 1338 c.Check(found, check.DeepEquals, []string{"a-snap"}) 1339 tasks := taskset.Tasks() 1340 c.Assert(tasks, check.HasLen, 1) 1341 c.Check(tasks[0].Kind(), check.Equals, "forget-snapshot") 1342 c.Check(tasks[0].Summary(), check.Equals, `Drop data of snap "a-snap" from snapshot set #42`) 1343 var snapshot map[string]interface{} 1344 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1345 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1346 "set-id": 42., 1347 "snap": "a-snap", 1348 "filename": shotfile.Name(), 1349 "current": "unset", 1350 }) 1351 } 1352 1353 func (snapshotSuite) TestSaveExpiration(c *check.C) { 1354 st := state.New(nil) 1355 st.Lock() 1356 defer st.Unlock() 1357 1358 var expirations map[uint64]interface{} 1359 tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z") 1360 c.Assert(err, check.IsNil) 1361 c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil) 1362 1363 tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z") 1364 c.Assert(err, check.IsNil) 1365 c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil) 1366 1367 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 1368 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 1369 12: map[string]interface{}{"expiry-time": "2019-03-11T11:24:00Z"}, 1370 13: map[string]interface{}{"expiry-time": "2019-02-12T12:50:00Z"}, 1371 }) 1372 } 1373 1374 func (snapshotSuite) TestRemoveSnapshotState(c *check.C) { 1375 st := state.New(nil) 1376 st.Lock() 1377 defer st.Unlock() 1378 1379 st.Set("snapshots", map[uint64]interface{}{ 1380 12: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"}, 1381 13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"}, 1382 14: map[string]interface{}{"expiry-time": "2019-03-12T13:11:00Z"}, 1383 }) 1384 1385 snapshotstate.RemoveSnapshotState(st, 12, 14) 1386 1387 var snapshots map[uint64]interface{} 1388 c.Assert(st.Get("snapshots", &snapshots), check.IsNil) 1389 c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{ 1390 13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"}, 1391 }) 1392 } 1393 1394 func (snapshotSuite) TestExpiredSnapshotSets(c *check.C) { 1395 st := state.New(nil) 1396 st.Lock() 1397 defer st.Unlock() 1398 1399 tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z") 1400 c.Assert(err, check.IsNil) 1401 c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil) 1402 1403 tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z") 1404 c.Assert(err, check.IsNil) 1405 c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil) 1406 1407 tm, err = time.Parse(time.RFC3339, "2020-03-11T11:24:00Z") 1408 c.Assert(err, check.IsNil) 1409 expired, err := snapshotstate.ExpiredSnapshotSets(st, tm) 1410 c.Assert(err, check.IsNil) 1411 c.Check(expired, check.DeepEquals, map[uint64]bool{12: true, 13: true}) 1412 1413 tm, err = time.Parse(time.RFC3339, "2019-03-01T11:24:00Z") 1414 c.Assert(err, check.IsNil) 1415 expired, err = snapshotstate.ExpiredSnapshotSets(st, tm) 1416 c.Assert(err, check.IsNil) 1417 c.Check(expired, check.DeepEquals, map[uint64]bool{13: true}) 1418 } 1419 1420 func (snapshotSuite) TestAutomaticSnapshotDisabled(c *check.C) { 1421 st := state.New(nil) 1422 st.Lock() 1423 defer st.Unlock() 1424 1425 tr := config.NewTransaction(st) 1426 tr.Set("core", "snapshots.automatic.retention", "no") 1427 tr.Commit() 1428 1429 _, err := snapshotstate.AutomaticSnapshot(st, "foo") 1430 c.Assert(err, check.Equals, snapstate.ErrNothingToDo) 1431 } 1432 1433 func (snapshotSuite) TestAutomaticSnapshot(c *check.C) { 1434 st := state.New(nil) 1435 st.Lock() 1436 defer st.Unlock() 1437 1438 tr := config.NewTransaction(st) 1439 tr.Set("core", "snapshots.automatic.retention", "24h") 1440 tr.Commit() 1441 1442 ts, err := snapshotstate.AutomaticSnapshot(st, "foo") 1443 c.Assert(err, check.IsNil) 1444 1445 tasks := ts.Tasks() 1446 c.Assert(tasks, check.HasLen, 1) 1447 c.Check(tasks[0].Kind(), check.Equals, "save-snapshot") 1448 c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "foo" in automatic snapshot set #1`) 1449 var snapshot map[string]interface{} 1450 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1451 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1452 "set-id": 1., 1453 "snap": "foo", 1454 "current": "unset", 1455 "auto": true, 1456 }) 1457 } 1458 1459 func (snapshotSuite) TestAutomaticSnapshotDefaultClassic(c *check.C) { 1460 release.MockOnClassic(true) 1461 1462 st := state.New(nil) 1463 st.Lock() 1464 defer st.Unlock() 1465 1466 du, err := snapshotstate.AutomaticSnapshotExpiration(st) 1467 c.Assert(err, check.IsNil) 1468 c.Assert(du, check.Equals, snapshotstate.DefaultAutomaticSnapshotExpiration) 1469 } 1470 1471 func (snapshotSuite) TestAutomaticSnapshotDefaultUbuntuCore(c *check.C) { 1472 release.MockOnClassic(false) 1473 1474 st := state.New(nil) 1475 st.Lock() 1476 defer st.Unlock() 1477 1478 du, err := snapshotstate.AutomaticSnapshotExpiration(st) 1479 c.Assert(err, check.IsNil) 1480 c.Assert(du, check.Equals, time.Duration(0)) 1481 } 1482 1483 func (snapshotSuite) TestEstimateSnapshotSize(c *check.C) { 1484 st := state.New(nil) 1485 st.Lock() 1486 defer st.Unlock() 1487 1488 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1489 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1490 Active: true, 1491 Sequence: []*snap.SideInfo{sideInfo}, 1492 Current: sideInfo.Revision, 1493 }) 1494 1495 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1496 return 123, nil 1497 })() 1498 1499 sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil) 1500 c.Assert(err, check.IsNil) 1501 c.Check(sz, check.Equals, uint64(123)) 1502 } 1503 1504 func (snapshotSuite) TestEstimateSnapshotSizeWithConfig(c *check.C) { 1505 st := state.New(nil) 1506 st.Lock() 1507 defer st.Unlock() 1508 1509 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1510 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1511 Active: true, 1512 Sequence: []*snap.SideInfo{sideInfo}, 1513 Current: sideInfo.Revision, 1514 }) 1515 1516 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1517 return 100, nil 1518 })() 1519 1520 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 1521 c.Check(snapname, check.Equals, "some-snap") 1522 buf := json.RawMessage(`{"hello": "there"}`) 1523 return &buf, nil 1524 })() 1525 1526 sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil) 1527 c.Assert(err, check.IsNil) 1528 // size is 100 + 18 1529 c.Check(sz, check.Equals, uint64(118)) 1530 } 1531 1532 func (snapshotSuite) TestEstimateSnapshotSizeError(c *check.C) { 1533 st := state.New(nil) 1534 st.Lock() 1535 defer st.Unlock() 1536 1537 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1538 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1539 Active: true, 1540 Sequence: []*snap.SideInfo{sideInfo}, 1541 Current: sideInfo.Revision, 1542 }) 1543 1544 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1545 return 0, fmt.Errorf("an error") 1546 })() 1547 1548 _, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil) 1549 c.Assert(err, check.ErrorMatches, `an error`) 1550 } 1551 1552 func (snapshotSuite) TestEstimateSnapshotSizeWithUsers(c *check.C) { 1553 st := state.New(nil) 1554 st.Lock() 1555 defer st.Unlock() 1556 1557 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1558 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1559 Active: true, 1560 Sequence: []*snap.SideInfo{sideInfo}, 1561 Current: sideInfo.Revision, 1562 }) 1563 1564 var gotUsers []string 1565 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1566 gotUsers = users 1567 return 0, nil 1568 })() 1569 1570 _, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", []string{"user1", "user2"}) 1571 c.Assert(err, check.IsNil) 1572 c.Check(gotUsers, check.DeepEquals, []string{"user1", "user2"}) 1573 }