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