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