gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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.CheckSnapshotConflict(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.CheckSnapshotConflict(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.CheckSnapshotConflict(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.CheckSnapshotConflict(st, 42, "some-other-task"), check.IsNil) 315 316 // no change with that snapshot id 317 c.Assert(snapshotstate.CheckSnapshotConflict(st, 43, "some-task"), check.IsNil) 318 319 // no non-ready change 320 tsk.SetStatus(state.DoneStatus) 321 c.Assert(snapshotstate.CheckSnapshotConflict(st, 42, "some-task"), check.IsNil) 322 } 323 324 func (snapshotSuite) TestCheckConflictSnapshotOpInProgress(c *check.C) { 325 st := state.New(nil) 326 st.Lock() 327 defer st.Unlock() 328 329 snapshotstate.SetSnapshotOpInProgress(st, 1, "foo-op") 330 snapshotstate.SetSnapshotOpInProgress(st, 2, "bar-op") 331 332 c.Assert(snapshotstate.CheckSnapshotConflict(st, 1, "foo-op"), check.ErrorMatches, `cannot operate on snapshot set #1 while operation foo-op is in progress`) 333 // unrelated set-id doesn't conflict 334 c.Assert(snapshotstate.CheckSnapshotConflict(st, 3, "foo-op"), check.IsNil) 335 c.Assert(snapshotstate.CheckSnapshotConflict(st, 3, "bar-op"), check.IsNil) 336 // non-conflicting op 337 c.Assert(snapshotstate.CheckSnapshotConflict(st, 1, "safe-op"), check.IsNil) 338 } 339 340 func (snapshotSuite) TestSaveChecksSnapnamesError(c *check.C) { 341 defer snapshotstate.MockSnapstateAll(func(*state.State) (map[string]*snapstate.SnapState, error) { 342 return nil, errors.New("bzzt") 343 })() 344 345 st := state.New(nil) 346 st.Lock() 347 defer st.Unlock() 348 _, _, _, err := snapshotstate.Save(st, nil, nil) 349 c.Check(err, check.ErrorMatches, "bzzt") 350 } 351 352 func (snapshotSuite) createConflictingChange(c *check.C) (st *state.State, restore func()) { 353 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 354 c.Assert(err, check.IsNil) 355 shotfile.Close() 356 357 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 358 c.Assert(f(&backend.Reader{ 359 Snapshot: client.Snapshot{SetID: 42, Snap: "foo"}, 360 File: shotfile, 361 }), check.IsNil) 362 363 return nil 364 } 365 restoreIter := snapshotstate.MockBackendIter(fakeIter) 366 367 o := overlord.Mock() 368 st = o.State() 369 370 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 371 c.Assert(err, check.IsNil) 372 o.AddManager(stmgr) 373 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 374 o.AddManager(shmgr) 375 376 st.Lock() 377 defer func() { 378 if c.Failed() { 379 // something went wrong 380 st.Unlock() 381 } 382 }() 383 384 snapstate.Set(st, "foo", &snapstate.SnapState{ 385 Active: true, 386 Sequence: []*snap.SideInfo{ 387 {RealName: "foo", Revision: snap.R(1)}, 388 }, 389 Current: snap.R(1), 390 SnapType: "app", 391 }) 392 393 r := snapstatetest.UseFallbackDeviceModel() 394 defer r() 395 396 chg := st.NewChange("rm foo", "...") 397 rmTasks, err := snapstate.Remove(st, "foo", snap.R(0), nil) 398 c.Assert(err, check.IsNil) 399 c.Assert(rmTasks, check.NotNil) 400 chg.AddAll(rmTasks) 401 402 return st, func() { 403 shotfile.Close() 404 st.Unlock() 405 restoreIter() 406 } 407 } 408 409 func (s snapshotSuite) TestSaveChecksSnapstateConflict(c *check.C) { 410 st, restore := s.createConflictingChange(c) 411 defer restore() 412 413 _, _, _, err := snapshotstate.Save(st, []string{"foo"}, nil) 414 c.Assert(err, check.NotNil) 415 c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{}) 416 } 417 418 func (snapshotSuite) TestSaveConflictsWithSnapstate(c *check.C) { 419 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 420 return map[string]*snapstate.SnapState{ 421 "foo": {Active: true}, 422 }, nil 423 } 424 425 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 426 427 o := overlord.Mock() 428 st := o.State() 429 430 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 431 c.Assert(err, check.IsNil) 432 o.AddManager(stmgr) 433 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 434 o.AddManager(shmgr) 435 436 st.Lock() 437 defer st.Unlock() 438 439 snapstate.Set(st, "foo", &snapstate.SnapState{ 440 Active: true, 441 Sequence: []*snap.SideInfo{ 442 {RealName: "foo", Revision: snap.R(1)}, 443 }, 444 Current: snap.R(1), 445 SnapType: "app", 446 }) 447 448 chg := st.NewChange("snapshot-save", "...") 449 _, _, saveTasks, err := snapshotstate.Save(st, nil, nil) 450 c.Assert(err, check.IsNil) 451 chg.AddAll(saveTasks) 452 453 _, err = snapstate.Disable(st, "foo") 454 c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-save" change in progress`) 455 } 456 457 func (snapshotSuite) TestSaveChecksSnapstateConflictError(c *check.C) { 458 defer snapshotstate.MockSnapstateCheckChangeConflictMany(func(*state.State, []string, string) error { 459 return errors.New("bzzt") 460 })() 461 462 st := state.New(nil) 463 st.Lock() 464 defer st.Unlock() 465 _, _, _, err := snapshotstate.Save(st, nil, nil) 466 c.Check(err, check.ErrorMatches, "bzzt") 467 } 468 469 func (snapshotSuite) TestSaveChecksSetIDError(c *check.C) { 470 st := state.New(nil) 471 st.Lock() 472 defer st.Unlock() 473 474 st.Set("last-snapshot-set-id", "3/4") 475 476 _, _, _, err := snapshotstate.Save(st, nil, nil) 477 c.Check(err, check.ErrorMatches, ".* could not unmarshal .*") 478 } 479 480 func (snapshotSuite) TestSaveNoSnapsInState(c *check.C) { 481 st := state.New(nil) 482 st.Lock() 483 defer st.Unlock() 484 485 setID, saved, taskset, err := snapshotstate.Save(st, nil, nil) 486 c.Assert(err, check.IsNil) 487 c.Check(setID, check.Equals, uint64(1)) 488 c.Check(saved, check.HasLen, 0) 489 c.Check(taskset.Tasks(), check.HasLen, 0) 490 } 491 492 func (snapshotSuite) TestSaveSomeSnaps(c *check.C) { 493 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 494 return map[string]*snapstate.SnapState{ 495 "a-snap": {Active: true}, 496 "b-snap": {}, 497 "c-snap": {Active: true}, 498 }, nil 499 } 500 501 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 502 503 st := state.New(nil) 504 st.Lock() 505 defer st.Unlock() 506 507 setID, saved, taskset, err := snapshotstate.Save(st, nil, nil) 508 c.Assert(err, check.IsNil) 509 c.Check(setID, check.Equals, uint64(1)) 510 c.Check(saved, check.DeepEquals, []string{"a-snap", "c-snap"}) 511 tasks := taskset.Tasks() 512 c.Assert(tasks, check.HasLen, 2) 513 c.Check(tasks[0].Kind(), check.Equals, "save-snapshot") 514 c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`) 515 c.Check(tasks[1].Kind(), check.Equals, "save-snapshot") 516 c.Check(tasks[1].Summary(), check.Equals, `Save data of snap "c-snap" in snapshot set #1`) 517 } 518 519 func (snapshotSuite) TestSaveOneSnap(c *check.C) { 520 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 521 // snapstate.All isn't called when a snap name is passed in 522 return nil, errors.New("bzzt") 523 } 524 525 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 526 527 st := state.New(nil) 528 st.Lock() 529 defer st.Unlock() 530 531 setID, saved, taskset, err := snapshotstate.Save(st, []string{"a-snap"}, []string{"a-user"}) 532 c.Assert(err, check.IsNil) 533 c.Check(setID, check.Equals, uint64(1)) 534 c.Check(saved, check.DeepEquals, []string{"a-snap"}) 535 tasks := taskset.Tasks() 536 c.Assert(tasks, check.HasLen, 1) 537 c.Check(tasks[0].Kind(), check.Equals, "save-snapshot") 538 c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`) 539 var snapshot map[string]interface{} 540 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 541 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 542 "set-id": 1., 543 "snap": "a-snap", 544 "users": []interface{}{"a-user"}, 545 "current": "unset", 546 }) 547 } 548 549 func (snapshotSuite) TestSaveIntegration(c *check.C) { 550 if os.Geteuid() == 0 { 551 c.Skip("this test cannot run as root (runuser will fail)") 552 } 553 554 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 555 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 556 557 defer backend.MockUserLookup(func(username string) (*user.User, error) { 558 if username != "a-user" { 559 c.Fatalf("unexpected user %q", username) 560 } 561 return &user.User{ 562 Uid: fmt.Sprint(sys.Geteuid()), 563 Username: username, 564 HomeDir: homedir, 565 }, nil 566 })() 567 568 o := overlord.Mock() 569 st := o.State() 570 571 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 572 c.Assert(err, check.IsNil) 573 o.AddManager(stmgr) 574 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 575 o.AddManager(shmgr) 576 o.AddManager(o.TaskRunner()) 577 578 st.Lock() 579 defer st.Unlock() 580 581 snapshots := make(map[string]*client.Snapshot, 3) 582 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 583 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 584 snapstate.Set(st, name, &snapstate.SnapState{ 585 Active: true, 586 Sequence: []*snap.SideInfo{sideInfo}, 587 Current: sideInfo.Revision, 588 SnapType: "app", 589 }) 590 snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo) 591 592 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 593 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil) 594 595 snapshots[name] = &client.Snapshot{ 596 SetID: 1, 597 Snap: name, 598 Version: "v1", 599 Revision: sideInfo.Revision, 600 Epoch: snap.E("0"), 601 } 602 } 603 604 setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"}) 605 c.Assert(err, check.IsNil) 606 c.Check(setID, check.Equals, uint64(1)) 607 c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 608 609 change := st.NewChange("save-snapshot", "...") 610 change.AddAll(taskset) 611 612 t0 := time.Now() 613 614 st.Unlock() 615 c.Assert(o.Settle(5*time.Second), check.IsNil) 616 st.Lock() 617 c.Check(change.Err(), check.IsNil) 618 619 tf := time.Now() 620 c.Assert(backend.Iter(context.TODO(), func(r *backend.Reader) error { 621 c.Check(r.Check(context.TODO(), nil), check.IsNil) 622 623 // check the unknowables, and zero them out 624 c.Check(r.Snapshot.Time.After(t0), check.Equals, true) 625 c.Check(r.Snapshot.Time.Before(tf), check.Equals, true) 626 c.Check(r.Snapshot.Size > 0, check.Equals, true) 627 c.Assert(r.Snapshot.SHA3_384, check.HasLen, 1) 628 c.Check(r.Snapshot.SHA3_384["user/a-user.tgz"], check.HasLen, 96) 629 630 r.Snapshot.Time = time.Time{} 631 r.Snapshot.Size = 0 632 r.Snapshot.SHA3_384 = nil 633 634 c.Check(&r.Snapshot, check.DeepEquals, snapshots[r.Snapshot.Snap]) 635 return nil 636 }), check.IsNil) 637 } 638 639 func (snapshotSuite) TestSaveIntegrationFails(c *check.C) { 640 if os.Geteuid() == 0 { 641 c.Skip("this test cannot run as root (runuser will fail)") 642 } 643 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 644 // sanity check: no files in snapshot dir 645 out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput() 646 c.Assert(err, check.IsNil) 647 c.Check(string(out), check.Equals, "") 648 649 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 650 651 // Mock "tar" so that the tars finish in the expected order. 652 // Locally .01s and .02s do the trick with count=1000; 653 // padded a lot bigger for slower systems. 654 mocktar := testutil.MockCommand(c, "tar", ` 655 case "$*" in 656 */too-snap/*) 657 sleep .5 658 ;; 659 */tri-snap/*) 660 sleep 1 661 ;; 662 esac 663 export LANG=C 664 exec /bin/tar "$@" 665 `) 666 defer mocktar.Restore() 667 668 defer backend.MockUserLookup(func(username string) (*user.User, error) { 669 if username != "a-user" { 670 c.Fatalf("unexpected user %q", username) 671 } 672 return &user.User{ 673 Uid: fmt.Sprint(sys.Geteuid()), 674 Username: username, 675 HomeDir: homedir, 676 }, nil 677 })() 678 679 o := overlord.Mock() 680 st := o.State() 681 682 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 683 c.Assert(err, check.IsNil) 684 o.AddManager(stmgr) 685 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 686 o.AddManager(shmgr) 687 o.AddManager(o.TaskRunner()) 688 689 st.Lock() 690 defer st.Unlock() 691 692 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 693 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 694 snapstate.Set(st, name, &snapstate.SnapState{ 695 Active: true, 696 Sequence: []*snap.SideInfo{sideInfo}, 697 Current: sideInfo.Revision, 698 SnapType: "app", 699 }) 700 snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo) 701 702 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 703 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common"), 0755), check.IsNil) 704 mode := os.FileMode(0755) 705 if i == 1 { 706 mode = 0 707 } 708 c.Assert(os.Mkdir(filepath.Join(homedir, "snap", name, "common", "common-"+name), mode), check.IsNil) 709 } 710 711 setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"}) 712 c.Assert(err, check.IsNil) 713 c.Check(setID, check.Equals, uint64(1)) 714 c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 715 716 change := st.NewChange("save-snapshot", "...") 717 change.AddAll(taskset) 718 719 st.Unlock() 720 c.Assert(o.Settle(5*time.Second), check.IsNil) 721 st.Lock() 722 c.Check(change.Err(), check.NotNil) 723 tasks := change.Tasks() 724 c.Assert(tasks, check.HasLen, 3) 725 726 // task 0 (for "one-snap") will have been undone 727 c.Check(tasks[0].Summary(), testutil.Contains, `"one-snap"`) // sanity check: task 0 is one-snap's 728 c.Check(tasks[0].Status(), check.Equals, state.UndoneStatus) 729 730 // task 1 (for "too-snap") will have errored 731 c.Check(tasks[1].Summary(), testutil.Contains, `"too-snap"`) // sanity check: task 1 is too-snap's 732 c.Check(tasks[1].Status(), check.Equals, state.ErrorStatus) 733 c.Check(strings.Join(tasks[1].Log(), "\n"), check.Matches, `(?ms)\S+ ERROR cannot create archive: 734 /bin/tar: common/common-too-snap: .* Permission denied 735 /bin/tar: Exiting with failure status due to previous errors`) 736 737 // task 2 (for "tri-snap") will have errored as well, hopefully, but it's a race (see the "tar" comment above) 738 c.Check(tasks[2].Summary(), testutil.Contains, `"tri-snap"`) // sanity check: task 2 is tri-snap's 739 c.Check(tasks[2].Status(), check.Equals, state.ErrorStatus, check.Commentf("if this ever fails, duplicate the fake tar sleeps please")) 740 // sometimes you'll get one, sometimes you'll get the other (depending on ordering of events) 741 c.Check(strings.Join(tasks[2].Log(), "\n"), check.Matches, `\S+ ERROR( tar failed:)? context canceled`) 742 743 // no zips left behind, not for errors, not for undos \o/ 744 out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput() 745 c.Assert(err, check.IsNil) 746 c.Check(string(out), check.Equals, "") 747 } 748 749 func (snapshotSuite) testSaveIntegrationTarFails(c *check.C, tarLogLines int, expectedErr string) { 750 if os.Geteuid() == 0 { 751 c.Skip("this test cannot run as root (runuser will fail)") 752 } 753 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 754 // sanity check: no files in snapshot dir 755 out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput() 756 c.Assert(err, check.IsNil) 757 c.Check(string(out), check.Equals, "") 758 759 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 760 // mock tar so that it outputs a desired number of lines 761 tarFailScript := fmt.Sprintf(` 762 export LANG=C 763 for c in $(seq %d); do echo "log line $c" >&2 ; done 764 exec /bin/tar "$@" 765 `, tarLogLines) 766 if tarLogLines == 1 { 767 tarFailScript = "echo nope >&2 ; exit 1" 768 } else if tarLogLines == 0 { 769 tarFailScript = "exit 1" 770 } 771 mocktar := testutil.MockCommand(c, "tar", tarFailScript) 772 defer mocktar.Restore() 773 774 defer backend.MockUserLookup(func(username string) (*user.User, error) { 775 if username != "a-user" { 776 c.Fatalf("unexpected user %q", username) 777 } 778 return &user.User{ 779 Uid: fmt.Sprint(sys.Geteuid()), 780 Username: username, 781 HomeDir: homedir, 782 }, nil 783 })() 784 785 o := overlord.Mock() 786 st := o.State() 787 788 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 789 c.Assert(err, check.IsNil) 790 o.AddManager(stmgr) 791 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 792 o.AddManager(shmgr) 793 o.AddManager(o.TaskRunner()) 794 795 st.Lock() 796 defer st.Unlock() 797 798 sideInfo := &snap.SideInfo{RealName: "tar-fail-snap", Revision: snap.R(1)} 799 snapstate.Set(st, "tar-fail-snap", &snapstate.SnapState{ 800 Active: true, 801 Sequence: []*snap.SideInfo{sideInfo}, 802 Current: sideInfo.Revision, 803 SnapType: "app", 804 }) 805 snaptest.MockSnap(c, "name: tar-fail-snap\nversion: v1", sideInfo) 806 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap/tar-fail-snap/1/canary-tar-fail-snap"), 0755), check.IsNil) 807 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap/tar-fail-snap/common"), 0755), check.IsNil) 808 // these dir permissions (000) make tar unhappy 809 c.Assert(os.Mkdir(filepath.Join(homedir, "snap/tar-fail-snap/common/common-tar-fail-snap"), 00), check.IsNil) 810 811 setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"}) 812 c.Assert(err, check.IsNil) 813 c.Check(setID, check.Equals, uint64(1)) 814 c.Check(saved, check.DeepEquals, []string{"tar-fail-snap"}) 815 816 change := st.NewChange("save-snapshot", "...") 817 change.AddAll(taskset) 818 819 st.Unlock() 820 c.Assert(o.Settle(testutil.HostScaledTimeout(5*time.Second)), check.IsNil) 821 st.Lock() 822 c.Check(change.Err(), check.NotNil) 823 tasks := change.Tasks() 824 c.Assert(tasks, check.HasLen, 1) 825 826 // task 1 (for "too-snap") will have errored 827 c.Check(tasks[0].Summary(), testutil.Contains, `"tar-fail-snap"`) // sanity check: task 1 is too-snap's 828 c.Check(tasks[0].Status(), check.Equals, state.ErrorStatus) 829 c.Check(strings.Join(tasks[0].Log(), "\n"), check.Matches, expectedErr) 830 831 // no zips left behind, not for errors, not for undos \o/ 832 out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput() 833 c.Assert(err, check.IsNil) 834 c.Check(string(out), check.Equals, "") 835 } 836 837 func (s *snapshotSuite) TestSaveIntegrationTarFailsManyLines(c *check.C) { 838 // cutoff at 5 lines, 3 lines of log + 2 lines from tar 839 s.testSaveIntegrationTarFails(c, 3, `(?ms)\S+ ERROR cannot create archive: 840 log line 1 841 log line 2 842 log line 3 843 /bin/tar: common/common-tar-fail-snap: .* Permission denied 844 /bin/tar: Exiting with failure status due to previous errors`) 845 } 846 847 func (s *snapshotSuite) TestSaveIntegrationTarFailsTrimmedLines(c *check.C) { 848 s.testSaveIntegrationTarFails(c, 10, `(?ms)\S+ ERROR cannot create archive \(showing last 5 lines out of 12\): 849 log line 8 850 log line 9 851 log line 10 852 /bin/tar: common/common-tar-fail-snap: .* Permission denied 853 /bin/tar: Exiting with failure status due to previous errors`) 854 } 855 856 func (s *snapshotSuite) TestSaveIntegrationTarFailsSingleLine(c *check.C) { 857 s.testSaveIntegrationTarFails(c, 1, `(?ms)\S+ ERROR cannot create archive: 858 nope`) 859 } 860 861 func (s *snapshotSuite) TestSaveIntegrationTarFailsNoLines(c *check.C) { 862 s.testSaveIntegrationTarFails(c, 0, `(?ms)\S+ ERROR tar failed: exit status 1`) 863 } 864 865 func (snapshotSuite) TestRestoreChecksIterError(c *check.C) { 866 defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error { 867 return errors.New("bzzt") 868 })() 869 870 st := state.New(nil) 871 st.Lock() 872 defer st.Unlock() 873 874 _, _, err := snapshotstate.Restore(st, 42, nil, nil) 875 c.Assert(err, check.ErrorMatches, "bzzt") 876 } 877 878 func (s snapshotSuite) TestRestoreChecksSnapstateConflicts(c *check.C) { 879 st, restore := s.createConflictingChange(c) 880 defer restore() 881 882 _, _, err := snapshotstate.Restore(st, 42, nil, nil) 883 c.Assert(err, check.NotNil) 884 c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{}) 885 886 } 887 888 func (snapshotSuite) TestRestoreConflictsWithSnapstate(c *check.C) { 889 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 890 c.Assert(err, check.IsNil) 891 defer shotfile.Close() 892 893 sideInfo := &snap.SideInfo{RealName: "foo", Revision: snap.R(1)} 894 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 895 return map[string]*snapstate.SnapState{ 896 "foo": { 897 Active: true, 898 Sequence: []*snap.SideInfo{sideInfo}, 899 Current: sideInfo.Revision, 900 }, 901 }, nil 902 } 903 snaptest.MockSnap(c, "{name: foo, version: v1}", sideInfo) 904 905 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 906 907 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 908 c.Assert(f(&backend.Reader{ 909 Snapshot: client.Snapshot{SetID: 42, Snap: "foo"}, 910 File: shotfile, 911 }), check.IsNil) 912 913 return nil 914 } 915 defer snapshotstate.MockBackendIter(fakeIter)() 916 917 o := overlord.Mock() 918 st := o.State() 919 920 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 921 c.Assert(err, check.IsNil) 922 o.AddManager(stmgr) 923 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 924 o.AddManager(shmgr) 925 926 st.Lock() 927 defer st.Unlock() 928 929 snapstate.Set(st, "foo", &snapstate.SnapState{ 930 Active: true, 931 Sequence: []*snap.SideInfo{ 932 {RealName: "foo", Revision: snap.R(1)}, 933 }, 934 Current: snap.R(1), 935 SnapType: "app", 936 }) 937 938 chg := st.NewChange("snapshot-restore", "...") 939 _, restoreTasks, err := snapshotstate.Restore(st, 42, nil, nil) 940 c.Assert(err, check.IsNil) 941 chg.AddAll(restoreTasks) 942 943 _, err = snapstate.Disable(st, "foo") 944 c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-restore" change in progress`) 945 } 946 947 func (snapshotSuite) TestRestoreChecksForgetConflicts(c *check.C) { 948 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 949 c.Assert(err, check.IsNil) 950 defer shotfile.Close() 951 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 952 c.Assert(f(&backend.Reader{ 953 // not wanted 954 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 955 File: shotfile, 956 }), check.IsNil) 957 958 return nil 959 } 960 defer snapshotstate.MockBackendIter(fakeIter)() 961 962 st := state.New(nil) 963 st.Lock() 964 defer st.Unlock() 965 chg := st.NewChange("forget-snapshot-change", "...") 966 tsk := st.NewTask("forget-snapshot", "...") 967 tsk.SetStatus(state.DoingStatus) 968 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 969 chg.AddTask(tsk) 970 971 _, _, err = snapshotstate.Restore(st, 42, nil, nil) 972 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 973 } 974 975 func (snapshotSuite) TestRestoreChecksChangesToSnapID(c *check.C) { 976 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 977 c.Assert(err, check.IsNil) 978 defer shotfile.Close() 979 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 980 return map[string]*snapstate.SnapState{ 981 "a-snap": { 982 Active: true, 983 Sequence: []*snap.SideInfo{ 984 {RealName: "a-snap", Revision: snap.R(1), SnapID: "1234567890"}, 985 }, 986 Current: snap.R(1), 987 }, 988 }, nil 989 } 990 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 991 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 992 c.Assert(f(&backend.Reader{ 993 // not wanted 994 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap", SnapID: "0987654321"}, 995 File: shotfile, 996 }), check.IsNil) 997 998 return nil 999 } 1000 defer snapshotstate.MockBackendIter(fakeIter)() 1001 1002 st := state.New(nil) 1003 st.Lock() 1004 defer st.Unlock() 1005 1006 _, _, err = snapshotstate.Restore(st, 42, nil, nil) 1007 c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(ID 1234567…\) does not match snapshot \(ID 0987654…\)`) 1008 } 1009 1010 func (snapshotSuite) TestRestoreChecksChangesToEpoch(c *check.C) { 1011 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1012 c.Assert(err, check.IsNil) 1013 defer shotfile.Close() 1014 1015 sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)} 1016 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 1017 return map[string]*snapstate.SnapState{ 1018 "a-snap": { 1019 Active: true, 1020 Sequence: []*snap.SideInfo{sideInfo}, 1021 Current: sideInfo.Revision, 1022 }, 1023 }, nil 1024 } 1025 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 1026 snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: 17}", sideInfo) 1027 1028 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1029 c.Assert(f(&backend.Reader{ 1030 // not wanted 1031 Snapshot: client.Snapshot{ 1032 SetID: 42, 1033 Snap: "a-snap", 1034 Epoch: snap.E("42"), 1035 }, 1036 File: shotfile, 1037 }), check.IsNil) 1038 1039 return nil 1040 } 1041 defer snapshotstate.MockBackendIter(fakeIter)() 1042 1043 st := state.New(nil) 1044 st.Lock() 1045 defer st.Unlock() 1046 1047 _, _, err = snapshotstate.Restore(st, 42, nil, nil) 1048 c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(epoch 17\) cannot read snapshot data \(epoch 42\)`) 1049 } 1050 1051 func (snapshotSuite) TestRestoreWorksWithCompatibleEpoch(c *check.C) { 1052 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1053 c.Assert(err, check.IsNil) 1054 defer shotfile.Close() 1055 1056 sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)} 1057 fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) { 1058 return map[string]*snapstate.SnapState{ 1059 "a-snap": { 1060 Active: true, 1061 Sequence: []*snap.SideInfo{sideInfo}, 1062 Current: sideInfo.Revision, 1063 }, 1064 }, nil 1065 } 1066 defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)() 1067 snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: {read: [17, 42], write: [42]}}", sideInfo) 1068 1069 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1070 c.Assert(f(&backend.Reader{ 1071 // not wanted 1072 Snapshot: client.Snapshot{ 1073 SetID: 42, 1074 Snap: "a-snap", 1075 Epoch: snap.E("17"), 1076 }, 1077 File: shotfile, 1078 }), check.IsNil) 1079 1080 return nil 1081 } 1082 defer snapshotstate.MockBackendIter(fakeIter)() 1083 1084 st := state.New(nil) 1085 st.Lock() 1086 defer st.Unlock() 1087 1088 found, taskset, err := snapshotstate.Restore(st, 42, nil, nil) 1089 c.Assert(err, check.IsNil) 1090 c.Check(found, check.DeepEquals, []string{"a-snap"}) 1091 tasks := taskset.Tasks() 1092 c.Assert(tasks, check.HasLen, 1) 1093 c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot") 1094 c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`) 1095 var snapshot map[string]interface{} 1096 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1097 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1098 "set-id": 42., 1099 "snap": "a-snap", 1100 "filename": shotfile.Name(), 1101 "current": "1", 1102 }) 1103 } 1104 1105 func (snapshotSuite) TestRestore(c *check.C) { 1106 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1107 c.Assert(err, check.IsNil) 1108 defer shotfile.Close() 1109 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1110 c.Assert(f(&backend.Reader{ 1111 // not wanted 1112 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1113 File: shotfile, 1114 }), check.IsNil) 1115 1116 return nil 1117 } 1118 defer snapshotstate.MockBackendIter(fakeIter)() 1119 1120 st := state.New(nil) 1121 st.Lock() 1122 defer st.Unlock() 1123 1124 found, taskset, err := snapshotstate.Restore(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"}) 1125 c.Assert(err, check.IsNil) 1126 c.Check(found, check.DeepEquals, []string{"a-snap"}) 1127 tasks := taskset.Tasks() 1128 c.Assert(tasks, check.HasLen, 1) 1129 c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot") 1130 c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`) 1131 var snapshot map[string]interface{} 1132 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1133 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1134 "set-id": 42., 1135 "snap": "a-snap", 1136 "filename": shotfile.Name(), 1137 "users": []interface{}{"a-user"}, 1138 "current": "unset", 1139 }) 1140 } 1141 1142 func (snapshotSuite) TestRestoreIntegration(c *check.C) { 1143 if os.Geteuid() == 0 { 1144 c.Skip("this test cannot run as root (runuser will fail)") 1145 } 1146 1147 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 1148 homedirA := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 1149 homedirB := filepath.Join(dirs.GlobalRootDir, "home", "b-user") 1150 1151 defer backend.MockUserLookup(func(username string) (*user.User, error) { 1152 if username != "a-user" && username != "b-user" { 1153 c.Fatalf("unexpected user %q", username) 1154 return nil, user.UnknownUserError(username) 1155 } 1156 return &user.User{ 1157 Uid: fmt.Sprint(sys.Geteuid()), 1158 Username: username, 1159 HomeDir: filepath.Join(dirs.GlobalRootDir, "home", username), 1160 }, nil 1161 1162 })() 1163 1164 o := overlord.Mock() 1165 st := o.State() 1166 1167 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 1168 c.Assert(err, check.IsNil) 1169 o.AddManager(stmgr) 1170 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 1171 o.AddManager(shmgr) 1172 o.AddManager(o.TaskRunner()) 1173 1174 st.Lock() 1175 1176 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 1177 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 1178 snapstate.Set(st, name, &snapstate.SnapState{ 1179 Active: true, 1180 Sequence: []*snap.SideInfo{sideInfo}, 1181 Current: sideInfo.Revision, 1182 SnapType: "app", 1183 }) 1184 snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo) 1185 1186 for _, home := range []string{homedirA, homedirB} { 1187 c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 1188 c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, "common", "common-"+name), 0755), check.IsNil) 1189 } 1190 1191 _, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user", "b-user"}) 1192 c.Assert(err, check.IsNil) 1193 } 1194 1195 // move the old away 1196 c.Assert(os.Rename(filepath.Join(homedirA, "snap"), filepath.Join(homedirA, "snap.old")), check.IsNil) 1197 // remove b-user's home 1198 c.Assert(os.RemoveAll(homedirB), check.IsNil) 1199 1200 found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user", "b-user"}) 1201 c.Assert(err, check.IsNil) 1202 sort.Strings(found) 1203 c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 1204 1205 change := st.NewChange("restore-snapshot", "...") 1206 change.AddAll(taskset) 1207 1208 st.Unlock() 1209 c.Assert(o.Settle(5*time.Second), check.IsNil) 1210 st.Lock() 1211 c.Check(change.Err(), check.IsNil) 1212 defer st.Unlock() 1213 1214 // the three restores warn about the missing home (but no errors, no panics) 1215 for _, task := range change.Tasks() { 1216 c.Check(strings.Join(task.Log(), "\n"), check.Matches, `.* Skipping restore of "[^"]+/home/b-user/[^"]+" as "[^"]+/home/b-user" doesn't exist.`) 1217 } 1218 1219 // check it was all brought back \o/ 1220 out, err := exec.Command("diff", "-rN", filepath.Join(homedirA, "snap"), filepath.Join("snap.old")).CombinedOutput() 1221 c.Assert(err, check.IsNil) 1222 c.Check(string(out), check.Equals, "") 1223 } 1224 1225 func (snapshotSuite) TestRestoreIntegrationFails(c *check.C) { 1226 if os.Geteuid() == 0 { 1227 c.Skip("this test cannot run as root (runuser will fail)") 1228 } 1229 c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil) 1230 homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user") 1231 1232 defer backend.MockUserLookup(func(username string) (*user.User, error) { 1233 if username != "a-user" { 1234 c.Fatalf("unexpected user %q", username) 1235 } 1236 return &user.User{ 1237 Uid: fmt.Sprint(sys.Geteuid()), 1238 Username: username, 1239 HomeDir: homedir, 1240 }, nil 1241 })() 1242 1243 o := overlord.Mock() 1244 st := o.State() 1245 1246 stmgr, err := snapstate.Manager(st, o.TaskRunner()) 1247 c.Assert(err, check.IsNil) 1248 o.AddManager(stmgr) 1249 shmgr := snapshotstate.Manager(st, o.TaskRunner()) 1250 o.AddManager(shmgr) 1251 o.AddManager(o.TaskRunner()) 1252 1253 st.Lock() 1254 1255 for i, name := range []string{"one-snap", "too-snap", "tri-snap"} { 1256 sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)} 1257 snapstate.Set(st, name, &snapstate.SnapState{ 1258 Active: true, 1259 Sequence: []*snap.SideInfo{sideInfo}, 1260 Current: sideInfo.Revision, 1261 SnapType: "app", 1262 }) 1263 snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: vv1}", name), sideInfo) 1264 1265 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil) 1266 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil) 1267 1268 _, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user"}) 1269 c.Assert(err, check.IsNil) 1270 } 1271 1272 // move the old away 1273 c.Assert(os.Rename(filepath.Join(homedir, "snap"), filepath.Join(homedir, "snap.old")), check.IsNil) 1274 // but poison the well 1275 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap"), 0755), check.IsNil) 1276 c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", "too-snap"), 0), check.IsNil) 1277 1278 found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user"}) 1279 c.Assert(err, check.IsNil) 1280 sort.Strings(found) 1281 c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"}) 1282 1283 change := st.NewChange("restore-snapshot", "...") 1284 change.AddAll(taskset) 1285 1286 st.Unlock() 1287 c.Assert(o.Settle(5*time.Second), check.IsNil) 1288 st.Lock() 1289 c.Check(change.Err(), check.NotNil) 1290 defer st.Unlock() 1291 1292 tasks := change.Tasks() 1293 c.Check(tasks, check.HasLen, 3) 1294 for _, task := range tasks { 1295 if strings.Contains(task.Summary(), `"too-snap"`) { 1296 // too-snap was set up to fail, should always fail with 1297 // 'permission denied' (see the mkdirall w/mode 0 above) 1298 c.Check(task.Status(), check.Equals, state.ErrorStatus) 1299 c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR mkdir \S+: permission denied`) 1300 } else { 1301 // the other two might fail (ErrorStatus) if they're 1302 // still running when too-snap fails, or they might have 1303 // finished and needed to be undone (UndoneStatus); it's 1304 // a race, but either is fine. 1305 if task.Status() == state.ErrorStatus { 1306 c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR.* context canceled`) 1307 } else { 1308 c.Check(task.Status(), check.Equals, state.UndoneStatus) 1309 } 1310 } 1311 } 1312 1313 // remove the poison 1314 c.Assert(os.Remove(filepath.Join(homedir, "snap", "too-snap")), check.IsNil) 1315 1316 // check that nothing else was put there 1317 out, err := exec.Command("find", filepath.Join(homedir, "snap")).CombinedOutput() 1318 c.Assert(err, check.IsNil) 1319 c.Check(strings.TrimSpace(string(out)), check.Equals, filepath.Join(homedir, "snap")) 1320 } 1321 1322 func (snapshotSuite) TestCheckChecksIterError(c *check.C) { 1323 defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error { 1324 return errors.New("bzzt") 1325 })() 1326 1327 st := state.New(nil) 1328 st.Lock() 1329 defer st.Unlock() 1330 1331 _, _, err := snapshotstate.Check(st, 42, nil, nil) 1332 c.Assert(err, check.ErrorMatches, "bzzt") 1333 } 1334 1335 func (s snapshotSuite) TestCheckDoesNotTriggerSnapstateConflict(c *check.C) { 1336 st, restore := s.createConflictingChange(c) 1337 defer restore() 1338 1339 _, _, err := snapshotstate.Check(st, 42, nil, nil) 1340 c.Assert(err, check.IsNil) 1341 } 1342 1343 func (snapshotSuite) TestCheckChecksForgetConflicts(c *check.C) { 1344 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1345 c.Assert(err, check.IsNil) 1346 defer shotfile.Close() 1347 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1348 c.Assert(f(&backend.Reader{ 1349 // not wanted 1350 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1351 File: shotfile, 1352 }), check.IsNil) 1353 1354 return nil 1355 } 1356 defer snapshotstate.MockBackendIter(fakeIter)() 1357 1358 st := state.New(nil) 1359 st.Lock() 1360 defer st.Unlock() 1361 chg := st.NewChange("forget-snapshot-change", "...") 1362 tsk := st.NewTask("forget-snapshot", "...") 1363 tsk.SetStatus(state.DoingStatus) 1364 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1365 chg.AddTask(tsk) 1366 1367 _, _, err = snapshotstate.Check(st, 42, nil, nil) 1368 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 1369 } 1370 1371 func (snapshotSuite) TestCheck(c *check.C) { 1372 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1373 c.Assert(err, check.IsNil) 1374 defer shotfile.Close() 1375 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1376 c.Assert(f(&backend.Reader{ 1377 // not wanted 1378 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1379 File: shotfile, 1380 }), check.IsNil) 1381 1382 return nil 1383 } 1384 defer snapshotstate.MockBackendIter(fakeIter)() 1385 1386 st := state.New(nil) 1387 st.Lock() 1388 defer st.Unlock() 1389 1390 found, taskset, err := snapshotstate.Check(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"}) 1391 c.Assert(err, check.IsNil) 1392 c.Check(found, check.DeepEquals, []string{"a-snap"}) 1393 tasks := taskset.Tasks() 1394 c.Assert(tasks, check.HasLen, 1) 1395 c.Check(tasks[0].Kind(), check.Equals, "check-snapshot") 1396 c.Check(tasks[0].Summary(), check.Equals, `Check data of snap "a-snap" in snapshot set #42`) 1397 var snapshot map[string]interface{} 1398 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1399 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1400 "set-id": 42., 1401 "snap": "a-snap", 1402 "filename": shotfile.Name(), 1403 "users": []interface{}{"a-user"}, 1404 "current": "unset", 1405 }) 1406 } 1407 1408 func (snapshotSuite) TestForgetChecksIterError(c *check.C) { 1409 defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error { 1410 return errors.New("bzzt") 1411 })() 1412 1413 st := state.New(nil) 1414 st.Lock() 1415 defer st.Unlock() 1416 1417 _, _, err := snapshotstate.Forget(st, 42, nil) 1418 c.Assert(err, check.ErrorMatches, "bzzt") 1419 } 1420 1421 func (s snapshotSuite) TestForgetDoesNotTriggerSnapstateConflict(c *check.C) { 1422 st, restore := s.createConflictingChange(c) 1423 defer restore() 1424 1425 _, _, err := snapshotstate.Forget(st, 42, nil) 1426 c.Assert(err, check.IsNil) 1427 } 1428 1429 func (snapshotSuite) TestForgetChecksCheckConflicts(c *check.C) { 1430 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1431 c.Assert(err, check.IsNil) 1432 defer shotfile.Close() 1433 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1434 c.Assert(f(&backend.Reader{ 1435 // not wanted 1436 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1437 File: shotfile, 1438 }), check.IsNil) 1439 1440 return nil 1441 } 1442 defer snapshotstate.MockBackendIter(fakeIter)() 1443 1444 st := state.New(nil) 1445 st.Lock() 1446 defer st.Unlock() 1447 chg := st.NewChange("check-snapshot-change", "...") 1448 tsk := st.NewTask("check-snapshot", "...") 1449 tsk.SetStatus(state.DoingStatus) 1450 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1451 chg.AddTask(tsk) 1452 1453 _, _, err = snapshotstate.Forget(st, 42, nil) 1454 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 1455 } 1456 1457 func (snapshotSuite) TestForgetChecksExportConflicts(c *check.C) { 1458 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1459 c.Assert(err, check.IsNil) 1460 defer shotfile.Close() 1461 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1462 c.Assert(f(&backend.Reader{ 1463 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1464 File: shotfile, 1465 }), check.IsNil) 1466 1467 return nil 1468 } 1469 defer snapshotstate.MockBackendIter(fakeIter)() 1470 1471 st := state.New(nil) 1472 st.Lock() 1473 defer st.Unlock() 1474 1475 snapshotstate.SetSnapshotOpInProgress(st, 42, "export-snapshot") 1476 1477 _, _, err = snapshotstate.Forget(st, 42, nil) 1478 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while operation export-snapshot is in progress`) 1479 } 1480 1481 func (snapshotSuite) TestForgetChecksRestoreConflicts(c *check.C) { 1482 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1483 c.Assert(err, check.IsNil) 1484 defer shotfile.Close() 1485 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1486 c.Assert(f(&backend.Reader{ 1487 // not wanted 1488 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1489 File: shotfile, 1490 }), check.IsNil) 1491 1492 return nil 1493 } 1494 defer snapshotstate.MockBackendIter(fakeIter)() 1495 1496 st := state.New(nil) 1497 st.Lock() 1498 defer st.Unlock() 1499 chg := st.NewChange("restore-snapshot-change", "...") 1500 tsk := st.NewTask("restore-snapshot", "...") 1501 tsk.SetStatus(state.DoingStatus) 1502 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1503 chg.AddTask(tsk) 1504 1505 _, _, err = snapshotstate.Forget(st, 42, nil) 1506 c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`) 1507 } 1508 1509 func (snapshotSuite) TestForget(c *check.C) { 1510 shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip")) 1511 c.Assert(err, check.IsNil) 1512 defer shotfile.Close() 1513 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 1514 c.Assert(f(&backend.Reader{ 1515 // not wanted 1516 Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"}, 1517 File: shotfile, 1518 }), check.IsNil) 1519 1520 return nil 1521 } 1522 defer snapshotstate.MockBackendIter(fakeIter)() 1523 1524 st := state.New(nil) 1525 st.Lock() 1526 defer st.Unlock() 1527 1528 found, taskset, err := snapshotstate.Forget(st, 42, []string{"a-snap", "b-snap"}) 1529 c.Assert(err, check.IsNil) 1530 c.Check(found, check.DeepEquals, []string{"a-snap"}) 1531 tasks := taskset.Tasks() 1532 c.Assert(tasks, check.HasLen, 1) 1533 c.Check(tasks[0].Kind(), check.Equals, "forget-snapshot") 1534 c.Check(tasks[0].Summary(), check.Equals, `Drop data of snap "a-snap" from snapshot set #42`) 1535 var snapshot map[string]interface{} 1536 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1537 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1538 "set-id": 42., 1539 "snap": "a-snap", 1540 "filename": shotfile.Name(), 1541 "current": "unset", 1542 }) 1543 } 1544 1545 func (snapshotSuite) TestSaveExpiration(c *check.C) { 1546 st := state.New(nil) 1547 st.Lock() 1548 defer st.Unlock() 1549 1550 var expirations map[uint64]interface{} 1551 tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z") 1552 c.Assert(err, check.IsNil) 1553 c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil) 1554 1555 tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z") 1556 c.Assert(err, check.IsNil) 1557 c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil) 1558 1559 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 1560 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 1561 12: map[string]interface{}{"expiry-time": "2019-03-11T11:24:00Z"}, 1562 13: map[string]interface{}{"expiry-time": "2019-02-12T12:50:00Z"}, 1563 }) 1564 } 1565 1566 func (snapshotSuite) TestRemoveSnapshotState(c *check.C) { 1567 st := state.New(nil) 1568 st.Lock() 1569 defer st.Unlock() 1570 1571 st.Set("snapshots", map[uint64]interface{}{ 1572 12: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"}, 1573 13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"}, 1574 14: map[string]interface{}{"expiry-time": "2019-03-12T13:11:00Z"}, 1575 }) 1576 1577 snapshotstate.RemoveSnapshotState(st, 12, 14) 1578 1579 var snapshots map[uint64]interface{} 1580 c.Assert(st.Get("snapshots", &snapshots), check.IsNil) 1581 c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{ 1582 13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"}, 1583 }) 1584 } 1585 1586 func (snapshotSuite) TestExpiredSnapshotSets(c *check.C) { 1587 st := state.New(nil) 1588 st.Lock() 1589 defer st.Unlock() 1590 1591 tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z") 1592 c.Assert(err, check.IsNil) 1593 c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil) 1594 1595 tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z") 1596 c.Assert(err, check.IsNil) 1597 c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil) 1598 1599 tm, err = time.Parse(time.RFC3339, "2020-03-11T11:24:00Z") 1600 c.Assert(err, check.IsNil) 1601 expired, err := snapshotstate.ExpiredSnapshotSets(st, tm) 1602 c.Assert(err, check.IsNil) 1603 c.Check(expired, check.DeepEquals, map[uint64]bool{12: true, 13: true}) 1604 1605 tm, err = time.Parse(time.RFC3339, "2019-03-01T11:24:00Z") 1606 c.Assert(err, check.IsNil) 1607 expired, err = snapshotstate.ExpiredSnapshotSets(st, tm) 1608 c.Assert(err, check.IsNil) 1609 c.Check(expired, check.DeepEquals, map[uint64]bool{13: true}) 1610 } 1611 1612 func (snapshotSuite) TestAutomaticSnapshotDisabled(c *check.C) { 1613 st := state.New(nil) 1614 st.Lock() 1615 defer st.Unlock() 1616 1617 tr := config.NewTransaction(st) 1618 tr.Set("core", "snapshots.automatic.retention", "no") 1619 tr.Commit() 1620 1621 _, err := snapshotstate.AutomaticSnapshot(st, "foo") 1622 c.Assert(err, check.Equals, snapstate.ErrNothingToDo) 1623 } 1624 1625 func (snapshotSuite) TestAutomaticSnapshot(c *check.C) { 1626 st := state.New(nil) 1627 st.Lock() 1628 defer st.Unlock() 1629 1630 tr := config.NewTransaction(st) 1631 tr.Set("core", "snapshots.automatic.retention", "24h") 1632 tr.Commit() 1633 1634 ts, err := snapshotstate.AutomaticSnapshot(st, "foo") 1635 c.Assert(err, check.IsNil) 1636 1637 tasks := ts.Tasks() 1638 c.Assert(tasks, check.HasLen, 1) 1639 c.Check(tasks[0].Kind(), check.Equals, "save-snapshot") 1640 c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "foo" in automatic snapshot set #1`) 1641 var snapshot map[string]interface{} 1642 c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil) 1643 c.Check(snapshot, check.DeepEquals, map[string]interface{}{ 1644 "set-id": 1., 1645 "snap": "foo", 1646 "current": "unset", 1647 "auto": true, 1648 }) 1649 } 1650 1651 func (snapshotSuite) TestAutomaticSnapshotDefaultClassic(c *check.C) { 1652 release.MockOnClassic(true) 1653 1654 st := state.New(nil) 1655 st.Lock() 1656 defer st.Unlock() 1657 1658 du, err := snapshotstate.AutomaticSnapshotExpiration(st) 1659 c.Assert(err, check.IsNil) 1660 c.Assert(du, check.Equals, snapshotstate.DefaultAutomaticSnapshotExpiration) 1661 } 1662 1663 func (snapshotSuite) TestAutomaticSnapshotDefaultUbuntuCore(c *check.C) { 1664 release.MockOnClassic(false) 1665 1666 st := state.New(nil) 1667 st.Lock() 1668 defer st.Unlock() 1669 1670 du, err := snapshotstate.AutomaticSnapshotExpiration(st) 1671 c.Assert(err, check.IsNil) 1672 c.Assert(du, check.Equals, time.Duration(0)) 1673 } 1674 1675 func (snapshotSuite) TestListError(c *check.C) { 1676 restore := snapshotstate.MockBackendList(func(context.Context, uint64, []string) ([]client.SnapshotSet, error) { 1677 return nil, fmt.Errorf("boom") 1678 }) 1679 defer restore() 1680 1681 st := state.New(nil) 1682 st.Lock() 1683 defer st.Unlock() 1684 1685 _, err := snapshotstate.List(context.TODO(), st, 0, nil) 1686 c.Assert(err, check.ErrorMatches, "boom") 1687 } 1688 1689 func (snapshotSuite) TestListSetsAutoFlag(c *check.C) { 1690 st := state.New(nil) 1691 st.Lock() 1692 defer st.Unlock() 1693 1694 st.Set("snapshots", map[uint64]interface{}{ 1695 1: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"}, 1696 2: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"}, 1697 }) 1698 1699 restore := snapshotstate.MockBackendList(func(ctx context.Context, setID uint64, snapNames []string) ([]client.SnapshotSet, error) { 1700 // three sets, first two are automatic (implied by expiration times in the state), the third isn't. 1701 return []client.SnapshotSet{ 1702 { 1703 ID: 1, 1704 Snapshots: []*client.Snapshot{ 1705 { 1706 Snap: "foo", 1707 SetID: 1, 1708 }, 1709 { 1710 Snap: "bar", 1711 SetID: 1, 1712 }, 1713 }, 1714 }, 1715 { 1716 ID: 2, 1717 Snapshots: []*client.Snapshot{ 1718 { 1719 Snap: "baz", 1720 SetID: 2, 1721 }, 1722 }, 1723 }, 1724 { 1725 ID: 3, 1726 Snapshots: []*client.Snapshot{ 1727 { 1728 Snap: "baz", 1729 SetID: 3, 1730 }, 1731 }, 1732 }, 1733 }, nil 1734 }) 1735 defer restore() 1736 1737 sets, err := snapshotstate.List(context.TODO(), st, 0, nil) 1738 c.Assert(err, check.IsNil) 1739 c.Assert(sets, check.HasLen, 3) 1740 1741 for _, sset := range sets { 1742 switch sset.ID { 1743 case 1: 1744 c.Check(sset.Snapshots, check.HasLen, 2, check.Commentf("set #%d", sset.ID)) 1745 default: 1746 c.Check(sset.Snapshots, check.HasLen, 1, check.Commentf("set #%d", sset.ID)) 1747 } 1748 1749 switch sset.ID { 1750 case 1, 2: 1751 for _, snapshot := range sset.Snapshots { 1752 c.Check(snapshot.Auto, check.Equals, true) 1753 } 1754 default: 1755 for _, snapshot := range sset.Snapshots { 1756 c.Check(snapshot.Auto, check.Equals, false) 1757 } 1758 } 1759 } 1760 } 1761 1762 func (snapshotSuite) TestImportSnapshotHappy(c *check.C) { 1763 st := state.New(nil) 1764 1765 fakeSnapNames := []string{"baz", "bar", "foo"} 1766 fakeSnapshotData := "fake-import-data" 1767 1768 buf := bytes.NewBufferString(fakeSnapshotData) 1769 restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) { 1770 d, err := ioutil.ReadAll(r) 1771 c.Assert(err, check.IsNil) 1772 c.Check(fakeSnapshotData, check.Equals, string(d)) 1773 return fakeSnapNames, nil 1774 }) 1775 defer restore() 1776 1777 sid, names, err := snapshotstate.Import(context.TODO(), st, buf) 1778 c.Assert(err, check.IsNil) 1779 c.Check(sid, check.Equals, uint64(1)) 1780 c.Check(names, check.DeepEquals, fakeSnapNames) 1781 } 1782 1783 func (snapshotSuite) TestImportSnapshotImportError(c *check.C) { 1784 st := state.New(nil) 1785 1786 restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) { 1787 return nil, errors.New("some-error") 1788 }) 1789 defer restore() 1790 1791 r := bytes.NewBufferString("faked-import-data") 1792 sid, _, err := snapshotstate.Import(context.TODO(), st, r) 1793 c.Assert(err, check.NotNil) 1794 c.Assert(err.Error(), check.Equals, "some-error") 1795 c.Check(sid, check.Equals, uint64(0)) 1796 } 1797 1798 func (snapshotSuite) TestImportSnapshotDuplicate(c *check.C) { 1799 st := state.New(nil) 1800 1801 restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) { 1802 return nil, backend.DuplicatedSnapshotImportError{SetID: 3, SnapNames: []string{"foo-snap"}} 1803 }) 1804 defer restore() 1805 1806 st.Lock() 1807 st.Set("snapshots", map[uint64]interface{}{ 1808 2: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"}, 1809 3: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"}, 1810 }) 1811 st.Unlock() 1812 1813 sid, snapNames, err := snapshotstate.Import(context.TODO(), st, bytes.NewBufferString("")) 1814 c.Assert(err, check.IsNil) 1815 c.Check(sid, check.Equals, uint64(3)) 1816 c.Check(snapNames, check.DeepEquals, []string{"foo-snap"}) 1817 1818 st.Lock() 1819 defer st.Unlock() 1820 // expiry-time has been removed for snapshot set 3 1821 var snapshots map[uint64]interface{} 1822 c.Assert(st.Get("snapshots", &snapshots), check.IsNil) 1823 c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{ 1824 2: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"}, 1825 }) 1826 } 1827 1828 func (snapshotSuite) TestEstimateSnapshotSize(c *check.C) { 1829 st := state.New(nil) 1830 st.Lock() 1831 defer st.Unlock() 1832 1833 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1834 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1835 Active: true, 1836 Sequence: []*snap.SideInfo{sideInfo}, 1837 Current: sideInfo.Revision, 1838 }) 1839 1840 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1841 return 123, nil 1842 })() 1843 1844 sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil) 1845 c.Assert(err, check.IsNil) 1846 c.Check(sz, check.Equals, uint64(123)) 1847 } 1848 1849 func (snapshotSuite) TestEstimateSnapshotSizeWithConfig(c *check.C) { 1850 st := state.New(nil) 1851 st.Lock() 1852 defer st.Unlock() 1853 1854 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1855 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1856 Active: true, 1857 Sequence: []*snap.SideInfo{sideInfo}, 1858 Current: sideInfo.Revision, 1859 }) 1860 1861 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1862 return 100, nil 1863 })() 1864 1865 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 1866 c.Check(snapname, check.Equals, "some-snap") 1867 buf := json.RawMessage(`{"hello": "there"}`) 1868 return &buf, nil 1869 })() 1870 1871 sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil) 1872 c.Assert(err, check.IsNil) 1873 // size is 100 + 18 1874 c.Check(sz, check.Equals, uint64(118)) 1875 } 1876 1877 func (snapshotSuite) TestEstimateSnapshotSizeError(c *check.C) { 1878 st := state.New(nil) 1879 st.Lock() 1880 defer st.Unlock() 1881 1882 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1883 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1884 Active: true, 1885 Sequence: []*snap.SideInfo{sideInfo}, 1886 Current: sideInfo.Revision, 1887 }) 1888 1889 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1890 return 0, fmt.Errorf("an error") 1891 })() 1892 1893 _, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil) 1894 c.Assert(err, check.ErrorMatches, `an error`) 1895 } 1896 1897 func (snapshotSuite) TestEstimateSnapshotSizeWithUsers(c *check.C) { 1898 st := state.New(nil) 1899 st.Lock() 1900 defer st.Unlock() 1901 1902 sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)} 1903 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1904 Active: true, 1905 Sequence: []*snap.SideInfo{sideInfo}, 1906 Current: sideInfo.Revision, 1907 }) 1908 1909 var gotUsers []string 1910 defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) { 1911 gotUsers = users 1912 return 0, nil 1913 })() 1914 1915 _, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", []string{"user1", "user2"}) 1916 c.Assert(err, check.IsNil) 1917 c.Check(gotUsers, check.DeepEquals, []string{"user1", "user2"}) 1918 } 1919 1920 func (snapshotSuite) TestExportSnapshotConflictsWithForget(c *check.C) { 1921 st := state.New(nil) 1922 st.Lock() 1923 defer st.Unlock() 1924 1925 chg := st.NewChange("forget-snapshot-change", "...") 1926 tsk := st.NewTask("forget-snapshot", "...") 1927 tsk.SetStatus(state.DoingStatus) 1928 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1929 chg.AddTask(tsk) 1930 1931 _, err := snapshotstate.Export(context.TODO(), st, 42) 1932 c.Assert(err, check.NotNil) 1933 c.Assert(err.Error(), check.Equals, `cannot operate on snapshot set #42 while change "1" is in progress`) 1934 } 1935 1936 func (snapshotSuite) TestImportSnapshotDuplicatedNoConflict(c *check.C) { 1937 buf := &bytes.Buffer{} 1938 var importCalls int 1939 restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) { 1940 importCalls++ 1941 c.Check(id, check.Equals, uint64(1)) 1942 return nil, backend.DuplicatedSnapshotImportError{SetID: 42, SnapNames: []string{"foo-snap"}} 1943 }) 1944 defer restore() 1945 1946 st := state.New(nil) 1947 setID, snaps, err := snapshotstate.Import(context.TODO(), st, buf) 1948 c.Check(importCalls, check.Equals, 1) 1949 c.Assert(err, check.IsNil) 1950 c.Check(setID, check.Equals, uint64(42)) 1951 c.Check(snaps, check.DeepEquals, []string{"foo-snap"}) 1952 } 1953 1954 func (snapshotSuite) TestImportSnapshotConflictsWithForget(c *check.C) { 1955 buf := &bytes.Buffer{} 1956 var importCalls int 1957 restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) { 1958 importCalls++ 1959 switch importCalls { 1960 case 1: 1961 c.Assert(flags, check.IsNil) 1962 case 2: 1963 c.Assert(flags, check.NotNil) 1964 c.Assert(flags.NoDuplicatedImportCheck, check.Equals, true) 1965 return []string{"foo"}, nil 1966 default: 1967 c.Fatal("unexpected number call to Import") 1968 } 1969 // DuplicatedSnapshotImportError is the only case where we can encounter 1970 // conflict on import (trying to reuse existing snapshot). 1971 return nil, backend.DuplicatedSnapshotImportError{SetID: 42, SnapNames: []string{"not-relevant-because-of-retry"}} 1972 }) 1973 defer restore() 1974 1975 st := state.New(nil) 1976 st.Lock() 1977 defer st.Unlock() 1978 1979 // conflicting change 1980 chg := st.NewChange("forget-snapshot-change", "...") 1981 tsk := st.NewTask("forget-snapshot", "...") 1982 tsk.SetStatus(state.DoingStatus) 1983 tsk.Set("snapshot-setup", map[string]int{"set-id": 42}) 1984 chg.AddTask(tsk) 1985 1986 st.Unlock() 1987 setID, snaps, err := snapshotstate.Import(context.TODO(), st, buf) 1988 st.Lock() 1989 c.Check(importCalls, check.Equals, 2) 1990 c.Assert(err, check.IsNil) 1991 c.Check(setID, check.Equals, uint64(1)) 1992 c.Check(snaps, check.DeepEquals, []string{"foo"}) 1993 } 1994 1995 func (snapshotSuite) TestExportSnapshotSetsOpInProgress(c *check.C) { 1996 restore := snapshotstate.MockBackendNewSnapshotExport(func(ctx context.Context, setID uint64) (se *backend.SnapshotExport, err error) { 1997 return nil, nil 1998 }) 1999 defer restore() 2000 2001 st := state.New(nil) 2002 st.Lock() 2003 defer st.Unlock() 2004 2005 _, err := snapshotstate.Export(context.TODO(), st, 42) 2006 c.Assert(err, check.IsNil) 2007 2008 ops := st.Cached("snapshot-ops") 2009 c.Assert(ops, check.DeepEquals, map[uint64]string{ 2010 uint64(42): "export-snapshot", 2011 }) 2012 } 2013 2014 func (snapshotSuite) TestSetSnapshotOpInProgress(c *check.C) { 2015 st := state.New(nil) 2016 st.Lock() 2017 defer st.Unlock() 2018 2019 c.Assert(snapshotstate.UnsetSnapshotOpInProgress(st, 9999), check.Equals, "") 2020 2021 snapshotstate.SetSnapshotOpInProgress(st, 1, "foo-op") 2022 snapshotstate.SetSnapshotOpInProgress(st, 2, "bar-op") 2023 2024 val := st.Cached("snapshot-ops") 2025 c.Check(val, check.DeepEquals, map[uint64]string{ 2026 uint64(1): "foo-op", 2027 uint64(2): "bar-op", 2028 }) 2029 2030 c.Check(snapshotstate.UnsetSnapshotOpInProgress(st, 1), check.Equals, "foo-op") 2031 2032 val = st.Cached("snapshot-ops") 2033 c.Check(val, check.DeepEquals, map[uint64]string{ 2034 uint64(2): "bar-op", 2035 }) 2036 2037 c.Check(snapshotstate.UnsetSnapshotOpInProgress(st, 2), check.Equals, "bar-op") 2038 2039 val = st.Cached("snapshot-ops") 2040 c.Check(val, check.HasLen, 0) 2041 }