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