gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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, snapshotOp 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 var tsk *state.Task 194 195 switch snapshotOp { 196 case "export-snapshot": 197 snapshotstate.SetSnapshotOpInProgress(st, 1, snapshotOp) 198 default: 199 chg := st.NewChange("snapshot-change", "...") 200 tsk = st.NewTask(snapshotOp, "...") 201 tsk.SetStatus(state.DoingStatus) 202 tsk.Set("snapshot-setup", map[string]int{"set-id": 1}) 203 chg.AddTask(tsk) 204 } 205 206 st.Unlock() 207 c.Assert(mgr.Ensure(), check.IsNil) 208 st.Lock() 209 210 var expirations map[uint64]interface{} 211 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 212 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 213 1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"}, 214 }) 215 c.Check(removeCalled, check.Equals, 0) 216 217 if tsk != nil { 218 // sanity check of the test setup: snapshot gets removed once conflict goes away 219 tsk.SetStatus(state.DoneStatus) 220 } else { 221 c.Check(snapshotstate.UnsetSnapshotOpInProgress(st, 1), check.Equals, snapshotOp) 222 } 223 224 // pretend we haven't run for a while 225 t, err := time.Parse(time.RFC3339, "2002-03-11T11:24:00Z") 226 c.Assert(err, check.IsNil) 227 snapshotstate.SetLastForgetExpiredSnapshotTime(mgr, t) 228 229 st.Unlock() 230 c.Assert(mgr.Ensure(), check.IsNil) 231 st.Lock() 232 233 expirations = nil 234 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 235 c.Check(removeCalled, check.Equals, 1) 236 c.Check(expirations, check.HasLen, 0) 237 } 238 239 func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithCheckSnapshot(c *check.C) { 240 s.testEnsureForgetSnapshotsConflict(c, "check-snapshot") 241 } 242 243 func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithRestoreSnapshot(c *check.C) { 244 s.testEnsureForgetSnapshotsConflict(c, "restore-snapshot") 245 } 246 247 func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithExportSnapshot(c *check.C) { 248 s.testEnsureForgetSnapshotsConflict(c, "export-snapshot") 249 } 250 251 func (snapshotSuite) TestFilename(c *check.C) { 252 si := &snap.Info{ 253 SideInfo: snap.SideInfo{ 254 RealName: "a-snap", 255 Revision: snap.R(-1), 256 }, 257 Version: "1.33", 258 } 259 filename := snapshotstate.Filename(42, si) 260 c.Check(filepath.Dir(filename), check.Equals, dirs.SnapshotsDir) 261 c.Check(filepath.Base(filename), check.Equals, "42_a-snap_1.33_x1.zip") 262 } 263 264 func (snapshotSuite) TestDoSave(c *check.C) { 265 snapInfo := snap.Info{ 266 SideInfo: snap.SideInfo{ 267 RealName: "a-snap", 268 Revision: snap.R(-1), 269 }, 270 Version: "1.33", 271 } 272 defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) { 273 c.Check(snapname, check.Equals, "a-snap") 274 return &snapInfo, nil 275 })() 276 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 277 c.Check(snapname, check.Equals, "a-snap") 278 buf := json.RawMessage(`{"hello": "there"}`) 279 return &buf, nil 280 })() 281 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 282 c.Check(id, check.Equals, uint64(42)) 283 c.Check(si, check.DeepEquals, &snapInfo) 284 c.Check(cfg, check.DeepEquals, map[string]interface{}{"hello": "there"}) 285 c.Check(usernames, check.DeepEquals, []string{"a-user", "b-user"}) 286 return nil, nil 287 })() 288 289 st := state.New(nil) 290 st.Lock() 291 task := st.NewTask("save-snapshot", "...") 292 task.Set("snapshot-setup", map[string]interface{}{ 293 "set-id": 42, 294 "snap": "a-snap", 295 "users": []string{"a-user", "b-user"}, 296 }) 297 st.Unlock() 298 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 299 c.Assert(err, check.IsNil) 300 } 301 302 func (snapshotSuite) TestDoSaveFailsWithNoSnap(c *check.C) { 303 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { 304 return nil, errors.New("bzzt") 305 })() 306 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })() 307 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 308 return nil, nil 309 })() 310 311 st := state.New(nil) 312 st.Lock() 313 task := st.NewTask("save-snapshot", "...") 314 task.Set("snapshot-setup", map[string]interface{}{ 315 "set-id": 42, 316 "snap": "a-snap", 317 "users": []string{"a-user", "b-user"}, 318 }) 319 st.Unlock() 320 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 321 c.Assert(err, check.ErrorMatches, "bzzt") 322 } 323 324 func (snapshotSuite) TestDoSaveFailsWithNoSnapshot(c *check.C) { 325 snapInfo := snap.Info{ 326 SideInfo: snap.SideInfo{ 327 RealName: "a-snap", 328 Revision: snap.R(-1), 329 }, 330 Version: "1.33", 331 } 332 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 333 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })() 334 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 335 return nil, nil 336 })() 337 338 st := state.New(nil) 339 st.Lock() 340 task := st.NewTask("save-snapshot", "...") 341 // NOTE no task.Set("snapshot-setup", ...) 342 st.Unlock() 343 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 344 c.Assert(err, check.NotNil) 345 c.Assert(err.Error(), check.Equals, "internal error: task 1 (save-snapshot) is missing snapshot information") 346 } 347 348 func (snapshotSuite) TestDoSaveFailsBackendError(c *check.C) { 349 snapInfo := snap.Info{ 350 SideInfo: snap.SideInfo{ 351 RealName: "a-snap", 352 Revision: snap.R(-1), 353 }, 354 Version: "1.33", 355 } 356 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 357 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })() 358 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 359 return nil, errors.New("bzzt") 360 })() 361 362 st := state.New(nil) 363 st.Lock() 364 task := st.NewTask("save-snapshot", "...") 365 task.Set("snapshot-setup", map[string]interface{}{ 366 "set-id": 42, 367 "snap": "a-snap", 368 "users": []string{"a-user", "b-user"}, 369 }) 370 st.Unlock() 371 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 372 c.Assert(err, check.ErrorMatches, "bzzt") 373 } 374 375 func (snapshotSuite) TestDoSaveFailsConfigError(c *check.C) { 376 snapInfo := snap.Info{ 377 SideInfo: snap.SideInfo{ 378 RealName: "a-snap", 379 Revision: snap.R(-1), 380 }, 381 Version: "1.33", 382 } 383 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 384 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 385 return nil, errors.New("bzzt") 386 })() 387 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 388 return nil, nil 389 })() 390 391 st := state.New(nil) 392 st.Lock() 393 task := st.NewTask("save-snapshot", "...") 394 task.Set("snapshot-setup", map[string]interface{}{ 395 "set-id": 42, 396 "snap": "a-snap", 397 "users": []string{"a-user", "b-user"}, 398 }) 399 st.Unlock() 400 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 401 c.Assert(err, check.ErrorMatches, "internal error: cannot obtain current snap config: bzzt") 402 } 403 404 func (snapshotSuite) TestDoSaveFailsBadConfig(c *check.C) { 405 snapInfo := snap.Info{ 406 SideInfo: snap.SideInfo{ 407 RealName: "a-snap", 408 Revision: snap.R(-1), 409 }, 410 Version: "1.33", 411 } 412 defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })() 413 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 414 // returns something that's not a JSON object 415 buf := json.RawMessage(`"hello-there"`) 416 return &buf, nil 417 })() 418 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 419 return nil, nil 420 })() 421 422 st := state.New(nil) 423 st.Lock() 424 task := st.NewTask("save-snapshot", "...") 425 task.Set("snapshot-setup", map[string]interface{}{ 426 "set-id": 42, 427 "snap": "a-snap", 428 "users": []string{"a-user", "b-user"}, 429 }) 430 st.Unlock() 431 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 432 c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*") 433 } 434 435 func (snapshotSuite) TestDoSaveFailureRemovesStateEntry(c *check.C) { 436 st := state.New(nil) 437 438 snapInfo := snap.Info{ 439 SideInfo: snap.SideInfo{ 440 RealName: "a-snap", 441 Revision: snap.R(-1), 442 }, 443 Version: "1.33", 444 } 445 defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) { 446 return &snapInfo, nil 447 })() 448 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 449 return nil, nil 450 })() 451 defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 452 var expirations map[uint64]interface{} 453 st.Lock() 454 defer st.Unlock() 455 // verify that prepareSave stored expiration in the state 456 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 457 c.Assert(expirations, check.HasLen, 1) 458 c.Check(expirations[42], check.NotNil) 459 return nil, errors.New("error") 460 })() 461 462 st.Lock() 463 464 task := st.NewTask("save-snapshot", "...") 465 task.Set("snapshot-setup", map[string]interface{}{ 466 "set-id": 42, 467 "snap": "a-snap", 468 "auto": true, 469 }) 470 st.Unlock() 471 err := snapshotstate.DoSave(task, &tomb.Tomb{}) 472 c.Assert(err, check.ErrorMatches, "error") 473 474 st.Lock() 475 defer st.Unlock() 476 477 // verify that after backend.Save failure expiration was removed from the state 478 var expirations map[uint64]interface{} 479 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 480 c.Check(expirations, check.HasLen, 0) 481 } 482 483 type readerSuite struct { 484 task *state.Task 485 calls []string 486 restores []func() 487 } 488 489 var _ = check.Suite(&readerSuite{}) 490 491 func (rs *readerSuite) SetUpTest(c *check.C) { 492 st := state.New(nil) 493 st.Lock() 494 rs.task = st.NewTask("restore-snapshot", "...") 495 rs.task.Set("snapshot-setup", map[string]interface{}{ 496 // interestingly restore doesn't use the set-id 497 "snap": "a-snap", 498 "filename": "/some/1_file.zip", 499 "users": []string{"a-user", "b-user"}, 500 }) 501 st.Unlock() 502 503 rs.calls = nil 504 rs.restores = []func(){ 505 snapshotstate.MockOsRemove(func(string) error { 506 rs.calls = append(rs.calls, "remove") 507 return nil 508 }), 509 snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 510 rs.calls = append(rs.calls, "get config") 511 return nil, nil 512 }), 513 snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error { 514 rs.calls = append(rs.calls, "set config") 515 return nil 516 }), 517 snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) { 518 rs.calls = append(rs.calls, "open") 519 return &backend.Reader{}, nil 520 }), 521 snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) { 522 rs.calls = append(rs.calls, "restore") 523 return &backend.RestoreState{}, nil 524 }), 525 snapshotstate.MockBackendCheck(func(*backend.Reader, context.Context, []string) error { 526 rs.calls = append(rs.calls, "check") 527 return nil 528 }), 529 snapshotstate.MockBackendRevert(func(*backend.RestoreState) { 530 rs.calls = append(rs.calls, "revert") 531 }), 532 snapshotstate.MockBackendCleanup(func(*backend.RestoreState) { 533 rs.calls = append(rs.calls, "cleanup") 534 }), 535 } 536 } 537 538 func (rs *readerSuite) TearDownTest(c *check.C) { 539 for _, restore := range rs.restores { 540 restore() 541 } 542 } 543 544 func (rs *readerSuite) TestDoRestore(c *check.C) { 545 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 546 rs.calls = append(rs.calls, "get config") 547 c.Check(snapname, check.Equals, "a-snap") 548 buf := json.RawMessage(`{"old": "conf"}`) 549 return &buf, nil 550 })() 551 defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) { 552 rs.calls = append(rs.calls, "open") 553 // set id 0 tells backend.Open to use set id from the filename 554 c.Check(setID, check.Equals, uint64(0)) 555 c.Check(filename, check.Equals, "/some/1_file.zip") 556 return &backend.Reader{ 557 Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}}, 558 }, nil 559 })() 560 defer snapshotstate.MockBackendRestore(func(_ *backend.Reader, _ context.Context, _ snap.Revision, users []string, _ backend.Logf) (*backend.RestoreState, error) { 561 rs.calls = append(rs.calls, "restore") 562 c.Check(users, check.DeepEquals, []string{"a-user", "b-user"}) 563 return &backend.RestoreState{}, nil 564 })() 565 defer snapshotstate.MockConfigSetSnapConfig(func(_ *state.State, snapname string, conf *json.RawMessage) error { 566 rs.calls = append(rs.calls, "set config") 567 c.Check(snapname, check.Equals, "a-snap") 568 c.Check(string(*conf), check.Equals, `{"hello":"there"}`) 569 return nil 570 })() 571 572 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 573 c.Assert(err, check.IsNil) 574 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config"}) 575 576 st := rs.task.State() 577 st.Lock() 578 var v map[string]interface{} 579 rs.task.Get("restore-state", &v) 580 st.Unlock() 581 c.Check(v, check.DeepEquals, map[string]interface{}{"config": map[string]interface{}{"old": "conf"}}) 582 } 583 584 func (rs *readerSuite) TestDoRestoreNoConfig(c *check.C) { 585 defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) { 586 rs.calls = append(rs.calls, "get config") 587 c.Check(snapname, check.Equals, "a-snap") 588 // simulate old config 589 raw := json.RawMessage(`{"foo": "bar"}`) 590 return &raw, nil 591 })() 592 defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) { 593 rs.calls = append(rs.calls, "open") 594 // set id 0 tells backend.Open to use set id from the filename 595 c.Check(setID, check.Equals, uint64(0)) 596 c.Check(filename, check.Equals, "/some/1_file.zip") 597 return &backend.Reader{ 598 // snapshot has no configuration to restore 599 Snapshot: client.Snapshot{Snap: "a-snap", Conf: nil}, 600 }, nil 601 })() 602 defer snapshotstate.MockBackendRestore(func(_ *backend.Reader, _ context.Context, _ snap.Revision, users []string, _ backend.Logf) (*backend.RestoreState, error) { 603 rs.calls = append(rs.calls, "restore") 604 c.Check(users, check.DeepEquals, []string{"a-user", "b-user"}) 605 return &backend.RestoreState{}, nil 606 })() 607 defer snapshotstate.MockConfigSetSnapConfig(func(_ *state.State, snapname string, conf *json.RawMessage) error { 608 rs.calls = append(rs.calls, "set config") 609 c.Check(snapname, check.Equals, "a-snap") 610 c.Check(conf, check.IsNil) 611 return nil 612 })() 613 614 st := rs.task.State() 615 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 616 c.Assert(err, check.IsNil) 617 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config"}) 618 619 st.Lock() 620 defer st.Unlock() 621 var v map[string]interface{} 622 rs.task.Get("restore-state", &v) 623 c.Check(v, check.DeepEquals, map[string]interface{}{"config": map[string]interface{}{"foo": "bar"}}) 624 } 625 626 func (rs *readerSuite) TestDoRestoreFailsNoTaskSnapshot(c *check.C) { 627 rs.task.State().Lock() 628 rs.task.Clear("snapshot-setup") 629 rs.task.State().Unlock() 630 631 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 632 c.Assert(err, check.NotNil) 633 c.Assert(err.Error(), check.Equals, "internal error: task 1 (restore-snapshot) is missing snapshot information") 634 c.Check(rs.calls, check.HasLen, 0) 635 } 636 637 func (rs *readerSuite) TestDoRestoreFailsOnGetConfigError(c *check.C) { 638 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 639 rs.calls = append(rs.calls, "get config") 640 return nil, errors.New("bzzt") 641 })() 642 643 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 644 c.Assert(err, check.ErrorMatches, "internal error: cannot obtain current snap config: bzzt") 645 c.Check(rs.calls, check.DeepEquals, []string{"get config"}) 646 } 647 648 func (rs *readerSuite) TestDoRestoreFailsOnBadConfig(c *check.C) { 649 defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { 650 rs.calls = append(rs.calls, "get config") 651 buf := json.RawMessage(`42`) 652 return &buf, nil 653 })() 654 655 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 656 c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*") 657 c.Check(rs.calls, check.DeepEquals, []string{"get config"}) 658 } 659 660 func (rs *readerSuite) TestDoRestoreFailsOpenError(c *check.C) { 661 defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) { 662 rs.calls = append(rs.calls, "open") 663 return nil, errors.New("bzzt") 664 })() 665 666 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 667 c.Assert(err, check.ErrorMatches, "cannot open snapshot: bzzt") 668 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open"}) 669 } 670 671 func (rs *readerSuite) TestDoRestoreFailsUnserialisableSnapshotConfigError(c *check.C) { 672 defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) { 673 rs.calls = append(rs.calls, "open") 674 return &backend.Reader{ 675 Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": func() {}}}, 676 }, nil 677 })() 678 679 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 680 c.Assert(err, check.ErrorMatches, "cannot marshal saved config: json.*") 681 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "revert"}) 682 } 683 684 func (rs *readerSuite) TestDoRestoreFailsOnRestoreError(c *check.C) { 685 defer snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) { 686 rs.calls = append(rs.calls, "restore") 687 return nil, errors.New("bzzt") 688 })() 689 690 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 691 c.Assert(err, check.ErrorMatches, "bzzt") 692 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore"}) 693 } 694 695 func (rs *readerSuite) TestDoRestoreFailsAndRevertsOnSetConfigError(c *check.C) { 696 defer snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error { 697 rs.calls = append(rs.calls, "set config") 698 return errors.New("bzzt") 699 })() 700 701 err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{}) 702 c.Assert(err, check.ErrorMatches, "cannot set snap config: bzzt") 703 c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config", "revert"}) 704 } 705 706 func (rs *readerSuite) TestUndoRestore(c *check.C) { 707 defer snapshotstate.MockConfigSetSnapConfig(func(st *state.State, snapName string, raw *json.RawMessage) error { 708 rs.calls = append(rs.calls, "set config") 709 c.Check(string(*raw), check.Equals, `{"foo":"bar"}`) 710 return nil 711 })() 712 713 st := rs.task.State() 714 st.Lock() 715 v := map[string]interface{}{"config": map[string]interface{}{"foo": "bar"}} 716 rs.task.Set("restore-state", &v) 717 st.Unlock() 718 719 err := snapshotstate.UndoRestore(rs.task, &tomb.Tomb{}) 720 c.Assert(err, check.IsNil) 721 c.Check(rs.calls, check.DeepEquals, []string{"set config", "revert"}) 722 } 723 724 func (rs *readerSuite) TestUndoRestoreNoConfig(c *check.C) { 725 defer snapshotstate.MockConfigSetSnapConfig(func(st *state.State, snapName string, raw *json.RawMessage) error { 726 rs.calls = append(rs.calls, "set config") 727 c.Check(raw, check.IsNil) 728 return nil 729 })() 730 731 st := rs.task.State() 732 st.Lock() 733 var v map[string]interface{} 734 rs.task.Set("restore-state", &v) 735 st.Unlock() 736 737 err := snapshotstate.UndoRestore(rs.task, &tomb.Tomb{}) 738 c.Assert(err, check.IsNil) 739 c.Check(rs.calls, check.DeepEquals, []string{"set config", "revert"}) 740 } 741 742 func (rs *readerSuite) TestCleanupRestore(c *check.C) { 743 st := rs.task.State() 744 st.Lock() 745 var v map[string]interface{} 746 rs.task.Set("restore-state", &v) 747 st.Unlock() 748 749 err := snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{}) 750 c.Assert(err, check.IsNil) 751 c.Check(rs.calls, check.HasLen, 0) 752 753 st.Lock() 754 rs.task.SetStatus(state.DoneStatus) 755 st.Unlock() 756 757 err = snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{}) 758 c.Assert(err, check.IsNil) 759 c.Check(rs.calls, check.DeepEquals, []string{"cleanup"}) 760 } 761 762 func (rs *readerSuite) TestDoCheck(c *check.C) { 763 defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) { 764 rs.calls = append(rs.calls, "open") 765 c.Check(filename, check.Equals, "/some/1_file.zip") 766 // set id 0 tells backend.Open to use set id from the filename 767 c.Check(setID, check.Equals, uint64(0)) 768 return &backend.Reader{ 769 Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}}, 770 }, nil 771 })() 772 defer snapshotstate.MockBackendCheck(func(_ *backend.Reader, _ context.Context, users []string) error { 773 rs.calls = append(rs.calls, "check") 774 c.Check(users, check.DeepEquals, []string{"a-user", "b-user"}) 775 return nil 776 })() 777 778 err := snapshotstate.DoCheck(rs.task, &tomb.Tomb{}) 779 c.Assert(err, check.IsNil) 780 c.Check(rs.calls, check.DeepEquals, []string{"open", "check"}) 781 } 782 783 func (rs *readerSuite) TestDoRemove(c *check.C) { 784 defer snapshotstate.MockOsRemove(func(filename string) error { 785 c.Check(filename, check.Equals, "/some/1_file.zip") 786 rs.calls = append(rs.calls, "remove") 787 return nil 788 })() 789 err := snapshotstate.DoForget(rs.task, &tomb.Tomb{}) 790 c.Assert(err, check.IsNil) 791 c.Check(rs.calls, check.DeepEquals, []string{"remove"}) 792 } 793 794 func (rs *readerSuite) TestDoForgetRemovesAutomaticSnapshotExpiry(c *check.C) { 795 defer snapshotstate.MockOsRemove(func(filename string) error { 796 return nil 797 })() 798 799 st := state.New(nil) 800 st.Lock() 801 defer st.Unlock() 802 803 task := st.NewTask("forget-snapshot", "...") 804 task.Set("snapshot-setup", map[string]interface{}{ 805 "set-id": 1, 806 "filename": "a-file", 807 "snap": "a-snap", 808 }) 809 810 st.Set("snapshots", map[uint64]interface{}{ 811 1: map[string]interface{}{ 812 "expiry-time": "2001-03-11T11:24:00Z", 813 }, 814 2: map[string]interface{}{ 815 "expiry-time": "2037-02-12T12:50:00Z", 816 }, 817 }) 818 819 st.Unlock() 820 c.Assert(snapshotstate.DoForget(task, &tomb.Tomb{}), check.IsNil) 821 822 st.Lock() 823 var expirations map[uint64]interface{} 824 c.Assert(st.Get("snapshots", &expirations), check.IsNil) 825 c.Check(expirations, check.DeepEquals, map[uint64]interface{}{ 826 2: map[string]interface{}{ 827 "expiry-time": "2037-02-12T12:50:00Z", 828 }}) 829 } 830 831 func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartup(c *check.C) { 832 n := 0 833 restore := snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) { 834 n++ 835 return 0, nil 836 }) 837 defer restore() 838 839 o := overlord.Mock() 840 st := o.State() 841 mgr := snapshotstate.Manager(st, state.NewTaskRunner(st)) 842 c.Assert(mgr, check.NotNil) 843 o.AddManager(mgr) 844 err := o.Settle(100 * time.Millisecond) 845 c.Assert(err, check.IsNil) 846 847 c.Check(n, check.Equals, 1) 848 } 849 850 func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartupErrorLogged(c *check.C) { 851 logbuf, restore := logger.MockLogger() 852 defer restore() 853 854 n := 0 855 restore = snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) { 856 n++ 857 return 0, errors.New("some error") 858 }) 859 defer restore() 860 861 o := overlord.Mock() 862 st := o.State() 863 mgr := snapshotstate.Manager(st, state.NewTaskRunner(st)) 864 c.Assert(mgr, check.NotNil) 865 o.AddManager(mgr) 866 err := o.Settle(100 * time.Millisecond) 867 c.Assert(err, check.IsNil) 868 869 c.Check(n, check.Equals, 1) 870 c.Check(logbuf.String(), testutil.Contains, "cannot cleanup incomplete imports: some error\n") 871 }