github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/snapshotstate/snapshotmgr_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package snapshotstate_test 21 22 import ( 23 "context" 24 "encoding/json" 25 "errors" 26 "os" 27 "path/filepath" 28 "sort" 29 "time" 30 31 "gopkg.in/check.v1" 32 "gopkg.in/tomb.v2" 33 34 "github.com/snapcore/snapd/client" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/logger" 37 "github.com/snapcore/snapd/overlord" 38 "github.com/snapcore/snapd/overlord/snapshotstate" 39 "github.com/snapcore/snapd/overlord/snapshotstate/backend" 40 "github.com/snapcore/snapd/overlord/state" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/testutil" 43 ) 44 45 func (snapshotSuite) TestManager(c *check.C) { 46 st := state.New(nil) 47 st.Lock() 48 defer st.Unlock() 49 runner := state.NewTaskRunner(st) 50 mgr := snapshotstate.Manager(st, runner) 51 c.Assert(mgr, check.NotNil) 52 kinds := runner.KnownTaskKinds() 53 sort.Strings(kinds) 54 c.Check(kinds, check.DeepEquals, []string{ 55 "check-snapshot", 56 "forget-snapshot", 57 "restore-snapshot", 58 "save-snapshot", 59 }) 60 } 61 62 func mockDummySnapshot(c *check.C) (restore func()) { 63 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 64 c.Assert(err, check.IsNil) 65 66 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 67 c.Assert(f(&backend.Reader{ 68 Snapshot: client.Snapshot{SetID: 1, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}}, 69 File: shotfile, 70 }), check.IsNil) 71 return nil 72 } 73 74 restoreBackendIter := snapshotstate.MockBackendIter(fakeIter) 75 76 return func() { 77 shotfile.Close() 78 restoreBackendIter() 79 } 80 } 81 82 func (snapshotSuite) TestEnsureForgetsSnapshots(c *check.C) { 83 var removedSnapshot string 84 restoreOsRemove := snapshotstate.MockOsRemove(func(fileName string) error { 85 removedSnapshot = fileName 86 return nil 87 }) 88 defer restoreOsRemove() 89 90 restore := mockDummySnapshot(c) 91 defer restore() 92 93 st := state.New(nil) 94 runner := state.NewTaskRunner(st) 95 mgr := snapshotstate.Manager(st, runner) 96 c.Assert(mgr, check.NotNil) 97 98 st.Lock() 99 defer st.Unlock() 100 101 st.Set("snapshots", map[uint64]interface{}{ 102 1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"}, 103 2: map[string]interface{}{"expiry-time": "2037-02-12T12:50:00Z"}, 104 }) 105 106 st.Unlock() 107 c.Assert(mgr.Ensure(), check.IsNil) 108 st.Lock() 109 110 // verify expired snapshots were removed 111 var expirations map[uint64]interface{} 112 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 113 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 114 2: map[string]interface{}{"expiry-time": "2037-02-12T12:50:00Z"}}) 115 c.Check(removedSnapshot, check.Matches, ".*/foo.zip") 116 } 117 118 func (snapshotSuite) TestEnsureForgetsSnapshotsRunsRegularly(c *check.C) { 119 var backendIterCalls int 120 shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip")) 121 c.Assert(err, check.IsNil) 122 fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { 123 c.Assert(f(&backend.Reader{ 124 Snapshot: client.Snapshot{SetID: 1, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}}, 125 File: shotfile, 126 }), check.IsNil) 127 backendIterCalls++ 128 return nil 129 } 130 restoreBackendIter := snapshotstate.MockBackendIter(fakeIter) 131 defer restoreBackendIter() 132 133 restoreOsRemove := snapshotstate.MockOsRemove(func(fileName string) error { 134 return nil 135 }) 136 defer restoreOsRemove() 137 138 st := state.New(nil) 139 runner := state.NewTaskRunner(st) 140 mgr := snapshotstate.Manager(st, runner) 141 c.Assert(mgr, check.NotNil) 142 143 storeExpiredSnapshot := func() { 144 st.Lock() 145 // we need at least one snapshot set in the state for forgetExpiredSnapshots to do any work 146 st.Set("snapshots", map[uint64]interface{}{ 147 1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"}, 148 }) 149 st.Unlock() 150 } 151 152 // consecutive runs of Ensure call the backend just once because of the snapshotExpirationLoopInterval 153 for i := 0; i < 3; i++ { 154 storeExpiredSnapshot() 155 c.Assert(mgr.Ensure(), check.IsNil) 156 c.Check(backendIterCalls, check.Equals, 1) 157 } 158 159 // pretend we haven't run for a while 160 t, err := time.Parse(time.RFC3339, "2002-03-11T11:24:00Z") 161 c.Assert(err, check.IsNil) 162 snapshotstate.SetLastForgetExpiredSnapshotTime(mgr, t) 163 c.Assert(mgr.Ensure(), check.IsNil) 164 c.Check(backendIterCalls, check.Equals, 2) 165 166 c.Assert(mgr.Ensure(), check.IsNil) 167 c.Check(backendIterCalls, check.Equals, 2) 168 } 169 170 func (snapshotSuite) testEnsureForgetSnapshotsConflict(c *check.C, snapshotTaskKind string) { 171 removeCalled := 0 172 restoreOsRemove := snapshotstate.MockOsRemove(func(string) error { 173 removeCalled++ 174 return nil 175 }) 176 defer restoreOsRemove() 177 178 restore := mockDummySnapshot(c) 179 defer restore() 180 181 st := state.New(nil) 182 runner := state.NewTaskRunner(st) 183 mgr := snapshotstate.Manager(st, runner) 184 c.Assert(mgr, check.NotNil) 185 186 st.Lock() 187 defer st.Unlock() 188 189 st.Set("snapshots", map[uint64]interface{}{ 190 1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"}, 191 }) 192 193 chg := st.NewChange("snapshot-change", "...") 194 tsk := st.NewTask(snapshotTaskKind, "...") 195 tsk.SetStatus(state.DoingStatus) 196 tsk.Set("snapshot-setup", map[string]int{"set-id": 1}) 197 chg.AddTask(tsk) 198 199 st.Unlock() 200 c.Assert(mgr.Ensure(), check.IsNil) 201 st.Lock() 202 203 var expirations map[uint64]interface{} 204 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 205 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 206 1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"}, 207 }) 208 c.Check(removeCalled, check.Equals, 0) 209 210 // sanity check of the test setup: snapshot gets removed once conflict goes away 211 tsk.SetStatus(state.DoneStatus) 212 213 // pretend we haven't run for a while 214 t, err := time.Parse(time.RFC3339, "2002-03-11T11:24:00Z") 215 c.Assert(err, check.IsNil) 216 snapshotstate.SetLastForgetExpiredSnapshotTime(mgr, t) 217 218 st.Unlock() 219 c.Assert(mgr.Ensure(), check.IsNil) 220 st.Lock() 221 222 expirations = nil 223 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 224 c.Check(removeCalled, check.Equals, 1) 225 c.Check(expirations, check.HasLen, 0) 226 } 227 228 func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithCheckSnapshot(c *check.C) { 229 s.testEnsureForgetSnapshotsConflict(c, "check-snapshot") 230 } 231 232 func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithRestoreSnapshot(c *check.C) { 233 s.testEnsureForgetSnapshotsConflict(c, "restore-snapshot") 234 } 235 236 func (snapshotSuite) TestFilename(c *check.C) { 237 si := &snap.Info{ 238 SideInfo: snap.SideInfo{ 239 RealName: "a-snap", 240 Revision: snap.R(-1), 241 }, 242 Version: "1.33", 243 } 244 filename := snapshotstate.Filename(42, si) 245 c.Check(filepath.Dir(filename), check.Equals, dirs.SnapshotsDir) 246 c.Check(filepath.Base(filename), check.Equals, "42_a-snap_1.33_x1.zip") 247 } 248 249 func (snapshotSuite) TestDoSave(c *check.C) { 250 snapInfo := snap.Info{ 251 SideInfo: snap.SideInfo{ 252 RealName: "a-snap", 253 Revision: snap.R(-1), 254 }, 255 Version: "1.33", 256 } 257 defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) { 258 c.Check(snapname, check.Equals, "a-snap") 259 return &snapInfo, nil 260 })() 261 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 262 c.Check(snapname, check.Equals, "a-snap") 263 buf := json.RawMessage(`{"hello": "there"}`) 264 return &buf, nil 265 })() 266 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 267 c.Check(id, check.Equals, uint64(42)) 268 c.Check(si, check.DeepEquals, &snapInfo) 269 c.Check(cfg, check.DeepEquals, map[string]interface{}{"hello": "there"}) 270 c.Check(usernames, check.DeepEquals, []string{"a-user", "b-user"}) 271 return nil, nil 272 })() 273 274 st := state.New(nil) 275 st.Lock() 276 task := st.NewTask("save-snapshot", "...") 277 task.Set("snapshot-setup", map[string]interface{}{ 278 "set-id": 42, 279 "snap": "a-snap", 280 "users": []string{"a-user", "b-user"}, 281 }) 282 st.Unlock() 283 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 284 c.Assert(err, check.IsNil) 285 } 286 287 func (snapshotSuite) TestDoSaveFailsWithNoSnap(c *check.C) { 288 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { 289 return nil, errors.New("bzzt") 290 })() 291 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })() 292 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 293 return nil, nil 294 })() 295 296 st := state.New(nil) 297 st.Lock() 298 task := st.NewTask("save-snapshot", "...") 299 task.Set("snapshot-setup", map[string]interface{}{ 300 "set-id": 42, 301 "snap": "a-snap", 302 "users": []string{"a-user", "b-user"}, 303 }) 304 st.Unlock() 305 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 306 c.Assert(err, check.ErrorMatches, "bzzt") 307 } 308 309 func (snapshotSuite) TestDoSaveFailsWithNoSnapshot(c *check.C) { 310 snapInfo := snap.Info{ 311 SideInfo: snap.SideInfo{ 312 RealName: "a-snap", 313 Revision: snap.R(-1), 314 }, 315 Version: "1.33", 316 } 317 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 318 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })() 319 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 320 return nil, nil 321 })() 322 323 st := state.New(nil) 324 st.Lock() 325 task := st.NewTask("save-snapshot", "...") 326 // NOTE no task.Set("snapshot-setup", ...) 327 st.Unlock() 328 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 329 c.Assert(err, check.NotNil) 330 c.Assert(err.Error(), check.Equals, "internal error: task 1 (save-snapshot) is missing snapshot information") 331 } 332 333 func (snapshotSuite) TestDoSaveFailsBackendError(c *check.C) { 334 snapInfo := snap.Info{ 335 SideInfo: snap.SideInfo{ 336 RealName: "a-snap", 337 Revision: snap.R(-1), 338 }, 339 Version: "1.33", 340 } 341 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 342 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })() 343 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 344 return nil, errors.New("bzzt") 345 })() 346 347 st := state.New(nil) 348 st.Lock() 349 task := st.NewTask("save-snapshot", "...") 350 task.Set("snapshot-setup", map[string]interface{}{ 351 "set-id": 42, 352 "snap": "a-snap", 353 "users": []string{"a-user", "b-user"}, 354 }) 355 st.Unlock() 356 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 357 c.Assert(err, check.ErrorMatches, "bzzt") 358 } 359 360 func (snapshotSuite) TestDoSaveFailsConfigError(c *check.C) { 361 snapInfo := snap.Info{ 362 SideInfo: snap.SideInfo{ 363 RealName: "a-snap", 364 Revision: snap.R(-1), 365 }, 366 Version: "1.33", 367 } 368 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 369 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 370 return nil, errors.New("bzzt") 371 })() 372 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 373 return nil, nil 374 })() 375 376 st := state.New(nil) 377 st.Lock() 378 task := st.NewTask("save-snapshot", "...") 379 task.Set("snapshot-setup", map[string]interface{}{ 380 "set-id": 42, 381 "snap": "a-snap", 382 "users": []string{"a-user", "b-user"}, 383 }) 384 st.Unlock() 385 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 386 c.Assert(err, check.ErrorMatches, "bzzt") 387 } 388 389 func (snapshotSuite) TestDoSaveFailsBadConfig(c *check.C) { 390 snapInfo := snap.Info{ 391 SideInfo: snap.SideInfo{ 392 RealName: "a-snap", 393 Revision: snap.R(-1), 394 }, 395 Version: "1.33", 396 } 397 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 398 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 399 // returns something that's not a JSON object 400 buf := json.RawMessage(`"hello-there"`) 401 return &buf, nil 402 })() 403 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 404 return nil, nil 405 })() 406 407 st := state.New(nil) 408 st.Lock() 409 task := st.NewTask("save-snapshot", "...") 410 task.Set("snapshot-setup", map[string]interface{}{ 411 "set-id": 42, 412 "snap": "a-snap", 413 "users": []string{"a-user", "b-user"}, 414 }) 415 st.Unlock() 416 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 417 c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*") 418 } 419 420 func (snapshotSuite) TestDoSaveFailureRemovesStateEntry(c *check.C) { 421 st := state.New(nil) 422 423 snapInfo := snap.Info{ 424 SideInfo: snap.SideInfo{ 425 RealName: "a-snap", 426 Revision: snap.R(-1), 427 }, 428 Version: "1.33", 429 } 430 defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) { 431 return &snapInfo, nil 432 })() 433 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 434 return nil, nil 435 })() 436 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 437 var expirations map[uint64]interface{} 438 st.Lock() 439 defer st.Unlock() 440 // verify that prepareSave stored expiration in the state 441 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 442 c.Assert(expirations, check.HasLen, 1) 443 c.Check(expirations[42], check.NotNil) 444 return nil, errors.New("error") 445 })() 446 447 st.Lock() 448 449 task := st.NewTask("save-snapshot", "...") 450 task.Set("snapshot-setup", map[string]interface{}{ 451 "set-id": 42, 452 "snap": "a-snap", 453 "auto": true, 454 }) 455 st.Unlock() 456 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 457 c.Assert(err, check.ErrorMatches, "error") 458 459 st.Lock() 460 defer st.Unlock() 461 462 // verify that after backend.Save failure expiration was removed from the state 463 var expirations map[uint64]interface{} 464 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 465 c.Check(expirations, check.HasLen, 0) 466 } 467 468 type readerSuite struct { 469 task *state.Task 470 calls []string 471 restores []func() 472 } 473 474 var _ = check.Suite(&readerSuite{}) 475 476 func (rs *readerSuite) SetUpTest(c *check.C) { 477 st := state.New(nil) 478 st.Lock() 479 rs.task = st.NewTask("restore-snapshot", "...") 480 rs.task.Set("snapshot-setup", map[string]interface{}{ 481 // interestingly restore doesn't use the set-id 482 "snap": "a-snap", 483 "filename": "/some/1_file.zip", 484 "users": []string{"a-user", "b-user"}, 485 }) 486 st.Unlock() 487 488 rs.calls = nil 489 rs.restores = []func(){ 490 snapshotstate.MockOsRemove(func(string) error { 491 rs.calls = append(rs.calls, "remove") 492 return nil 493 }), 494 snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 495 rs.calls = append(rs.calls, "get config") 496 return nil, nil 497 }), 498 snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error { 499 rs.calls = append(rs.calls, "set config") 500 return nil 501 }), 502 snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) { 503 rs.calls = append(rs.calls, "open") 504 return &backend.Reader{}, nil 505 }), 506 snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) { 507 rs.calls = append(rs.calls, "restore") 508 return &backend.RestoreState{}, nil 509 }), 510 snapshotstate.MockBackendCheck(func(*backend.Reader, context.Context, []string) error { 511 rs.calls = append(rs.calls, "check") 512 return nil 513 }), 514 snapshotstate.MockBackendRevert(func(*backend.RestoreState) { 515 rs.calls = append(rs.calls, "revert") 516 }), 517 snapshotstate.MockBackendCleanup(func(*backend.RestoreState) { 518 rs.calls = append(rs.calls, "cleanup") 519 }), 520 } 521 } 522 523 func (rs *readerSuite) TearDownTest(c *check.C) { 524 for _, restore := range rs.restores { 525 restore() 526 } 527 } 528 529 func (rs *readerSuite) TestDoRestore(c *check.C) { 530 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 531 rs.calls = append(rs.calls, "get config") 532 c.Check(snapname, check.Equals, "a-snap") 533 buf := json.RawMessage(`{"old": "conf"}`) 534 return &buf, nil 535 })() 536 defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) { 537 rs.calls = append(rs.calls, "open") 538 // set id 0 tells backend.Open to use set id from the filename 539 c.Check(setID, check.Equals, uint64(0)) 540 c.Check(filename, check.Equals, "/some/1_file.zip") 541 return &backend.Reader{ 542 Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}}, 543 }, nil 544 })() 545 defer snapshotstate.MockBackendRestore(func(_ *backend.Reader, _ context.Context, _ snap.Revision, users []string, _ backend.Logf) (*backend.RestoreState, error) { 546 rs.calls = append(rs.calls, "restore") 547 c.Check(users, check.DeepEquals, []string{"a-user", "b-user"}) 548 return &backend.RestoreState{}, nil 549 })() 550 defer snapshotstate.MockConfigSetSnapConfig(func(_ *state.State, snapname string, conf *json.RawMessage) error { 551 rs.calls = append(rs.calls, "set config") 552 c.Check(snapname, check.Equals, "a-snap") 553 c.Check(string(*conf), check.Equals, `{"hello":"there"}`) 554 return nil 555 })() 556 557 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 558 c.Assert(err, check.IsNil) 559 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config"}) 560 561 st := rs.task.State() 562 st.Lock() 563 var v map[string]interface{} 564 rs.task.Get("restore-state", &v) 565 st.Unlock() 566 c.Check(v, check.DeepEquals, map[string]interface{}{"config": map[string]interface{}{"old": "conf"}}) 567 } 568 569 func (rs *readerSuite) TestDoRestoreFailsNoTaskSnapshot(c *check.C) { 570 rs.task.State().Lock() 571 rs.task.Clear("snapshot-setup") 572 rs.task.State().Unlock() 573 574 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 575 c.Assert(err, check.NotNil) 576 c.Assert(err.Error(), check.Equals, "internal error: task 1 (restore-snapshot) is missing snapshot information") 577 c.Check(rs.calls, check.HasLen, 0) 578 } 579 580 func (rs *readerSuite) TestDoRestoreFailsOnGetConfigError(c *check.C) { 581 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 582 rs.calls = append(rs.calls, "get config") 583 return nil, errors.New("bzzt") 584 })() 585 586 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 587 c.Assert(err, check.ErrorMatches, "internal error: cannot obtain current snap config for snapshot restore: bzzt") 588 c.Check(rs.calls, check.DeepEquals, []string{"get config"}) 589 } 590 591 func (rs *readerSuite) TestDoRestoreFailsOnBadConfig(c *check.C) { 592 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 593 rs.calls = append(rs.calls, "get config") 594 buf := json.RawMessage(`42`) 595 return &buf, nil 596 })() 597 598 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 599 c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*") 600 c.Check(rs.calls, check.DeepEquals, []string{"get config"}) 601 } 602 603 func (rs *readerSuite) TestDoRestoreFailsOpenError(c *check.C) { 604 defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) { 605 rs.calls = append(rs.calls, "open") 606 return nil, errors.New("bzzt") 607 })() 608 609 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 610 c.Assert(err, check.ErrorMatches, "cannot open snapshot: bzzt") 611 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open"}) 612 } 613 614 func (rs *readerSuite) TestDoRestoreFailsUnserialisableSnapshotConfigError(c *check.C) { 615 defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) { 616 rs.calls = append(rs.calls, "open") 617 return &backend.Reader{ 618 Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": func() {}}}, 619 }, nil 620 })() 621 622 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 623 c.Assert(err, check.ErrorMatches, "cannot marshal saved config: json.*") 624 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "revert"}) 625 } 626 627 func (rs *readerSuite) TestDoRestoreFailsOnRestoreError(c *check.C) { 628 defer snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) { 629 rs.calls = append(rs.calls, "restore") 630 return nil, errors.New("bzzt") 631 })() 632 633 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 634 c.Assert(err, check.ErrorMatches, "bzzt") 635 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore"}) 636 } 637 638 func (rs *readerSuite) TestDoRestoreFailsAndRevertsOnSetConfigError(c *check.C) { 639 defer snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error { 640 rs.calls = append(rs.calls, "set config") 641 return errors.New("bzzt") 642 })() 643 644 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 645 c.Assert(err, check.ErrorMatches, "cannot set snap config: bzzt") 646 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config", "revert"}) 647 } 648 649 func (rs *readerSuite) TestUndoRestore(c *check.C) { 650 st := rs.task.State() 651 st.Lock() 652 var v map[string]interface{} 653 rs.task.Set("restore-state", &v) 654 st.Unlock() 655 656 err := snapshotstate.UndoRestore(rs.task, &tomb.Tomb{}) 657 c.Assert(err, check.IsNil) 658 c.Check(rs.calls, check.DeepEquals, []string{"set config", "revert"}) 659 } 660 661 func (rs *readerSuite) TestCleanupRestore(c *check.C) { 662 st := rs.task.State() 663 st.Lock() 664 var v map[string]interface{} 665 rs.task.Set("restore-state", &v) 666 st.Unlock() 667 668 err := snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{}) 669 c.Assert(err, check.IsNil) 670 c.Check(rs.calls, check.HasLen, 0) 671 672 st.Lock() 673 rs.task.SetStatus(state.DoneStatus) 674 st.Unlock() 675 676 err = snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{}) 677 c.Assert(err, check.IsNil) 678 c.Check(rs.calls, check.DeepEquals, []string{"cleanup"}) 679 } 680 681 func (rs *readerSuite) TestDoCheck(c *check.C) { 682 defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) { 683 rs.calls = append(rs.calls, "open") 684 c.Check(filename, check.Equals, "/some/1_file.zip") 685 // set id 0 tells backend.Open to use set id from the filename 686 c.Check(setID, check.Equals, uint64(0)) 687 return &backend.Reader{ 688 Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}}, 689 }, nil 690 })() 691 defer snapshotstate.MockBackendCheck(func(_ *backend.Reader, _ context.Context, users []string) error { 692 rs.calls = append(rs.calls, "check") 693 c.Check(users, check.DeepEquals, []string{"a-user", "b-user"}) 694 return nil 695 })() 696 697 err := snapshotstate.DoCheck(rs.task, &tomb.Tomb{}) 698 c.Assert(err, check.IsNil) 699 c.Check(rs.calls, check.DeepEquals, []string{"open", "check"}) 700 } 701 702 func (rs *readerSuite) TestDoRemove(c *check.C) { 703 defer snapshotstate.MockOsRemove(func(filename string) error { 704 c.Check(filename, check.Equals, "/some/1_file.zip") 705 rs.calls = append(rs.calls, "remove") 706 return nil 707 })() 708 err := snapshotstate.DoForget(rs.task, &tomb.Tomb{}) 709 c.Assert(err, check.IsNil) 710 c.Check(rs.calls, check.DeepEquals, []string{"remove"}) 711 } 712 713 func (rs *readerSuite) TestDoForgetRemovesAutomaticSnapshotExpiry(c *check.C) { 714 defer snapshotstate.MockOsRemove(func(filename string) error { 715 return nil 716 })() 717 718 st := state.New(nil) 719 st.Lock() 720 defer st.Unlock() 721 722 task := st.NewTask("forget-snapshot", "...") 723 task.Set("snapshot-setup", map[string]interface{}{ 724 "set-id": 1, 725 "filename": "a-file", 726 "snap": "a-snap", 727 }) 728 729 st.Set("snapshots", map[uint64]interface{}{ 730 1: map[string]interface{}{ 731 "expiry-time": "2001-03-11T11:24:00Z", 732 }, 733 2: map[string]interface{}{ 734 "expiry-time": "2037-02-12T12:50:00Z", 735 }, 736 }) 737 738 st.Unlock() 739 c.Assert(snapshotstate.DoForget(task, &tomb.Tomb{}), check.IsNil) 740 741 st.Lock() 742 var expirations map[uint64]interface{} 743 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 744 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 745 2: map[string]interface{}{ 746 "expiry-time": "2037-02-12T12:50:00Z", 747 }}) 748 } 749 750 func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartup(c *check.C) { 751 n := 0 752 restore := snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) { 753 n++ 754 return 0, nil 755 }) 756 defer restore() 757 758 o := overlord.Mock() 759 st := o.State() 760 mgr := snapshotstate.Manager(st, state.NewTaskRunner(st)) 761 c.Assert(mgr, check.NotNil) 762 o.AddManager(mgr) 763 err := o.Settle(100 * time.Millisecond) 764 c.Assert(err, check.IsNil) 765 766 c.Check(n, check.Equals, 1) 767 } 768 769 func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartupErrorLogged(c *check.C) { 770 logbuf, restore := logger.MockLogger() 771 defer restore() 772 773 n := 0 774 restore = snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) { 775 n++ 776 return 0, errors.New("some error") 777 }) 778 defer restore() 779 780 o := overlord.Mock() 781 st := o.State() 782 mgr := snapshotstate.Manager(st, state.NewTaskRunner(st)) 783 c.Assert(mgr, check.NotNil) 784 o.AddManager(mgr) 785 err := o.Settle(100 * time.Millisecond) 786 c.Assert(err, check.IsNil) 787 788 c.Check(n, check.Equals, 1) 789 c.Check(logbuf.String(), testutil.Contains, "cannot cleanup incomplete imports: some error\n") 790 }