github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/overlord/devicestate/devicestate_remodel_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2021 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 devicestate_test 21 22 import ( 23 "context" 24 "errors" 25 "fmt" 26 "io/ioutil" 27 "path/filepath" 28 "reflect" 29 "time" 30 31 . "gopkg.in/check.v1" 32 "gopkg.in/tomb.v2" 33 34 "github.com/snapcore/snapd/asserts" 35 "github.com/snapcore/snapd/asserts/assertstest" 36 "github.com/snapcore/snapd/boot" 37 "github.com/snapcore/snapd/gadget" 38 "github.com/snapcore/snapd/gadget/quantity" 39 "github.com/snapcore/snapd/overlord/assertstate" 40 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 41 "github.com/snapcore/snapd/overlord/auth" 42 "github.com/snapcore/snapd/overlord/devicestate" 43 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 44 "github.com/snapcore/snapd/overlord/snapstate" 45 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 46 "github.com/snapcore/snapd/overlord/state" 47 "github.com/snapcore/snapd/overlord/storecontext" 48 "github.com/snapcore/snapd/release" 49 "github.com/snapcore/snapd/snap" 50 "github.com/snapcore/snapd/snap/snapfile" 51 "github.com/snapcore/snapd/snap/snaptest" 52 "github.com/snapcore/snapd/store/storetest" 53 ) 54 55 type deviceMgrRemodelSuite struct { 56 deviceMgrBaseSuite 57 } 58 59 var _ = Suite(&deviceMgrRemodelSuite{}) 60 61 func (s *deviceMgrRemodelSuite) TestRemodelUnhappyNotSeeded(c *C) { 62 s.state.Lock() 63 defer s.state.Unlock() 64 s.state.Set("seeded", false) 65 66 newModel := s.brands.Model("canonical", "pc", map[string]interface{}{ 67 "architecture": "amd64", 68 "kernel": "pc-kernel", 69 "gadget": "pc", 70 }) 71 _, err := devicestate.Remodel(s.state, newModel) 72 c.Assert(err, ErrorMatches, "cannot remodel until fully seeded") 73 } 74 75 var mockCore20ModelHeaders = map[string]interface{}{ 76 "brand": "canonical", 77 "model": "pc-model-20", 78 "architecture": "amd64", 79 "grade": "dangerous", 80 "base": "core20", 81 "snaps": mockCore20ModelSnaps, 82 } 83 84 var mockCore20ModelSnaps = []interface{}{ 85 map[string]interface{}{ 86 "name": "pc-kernel", 87 "id": "pckernelidididididididididididid", 88 "type": "kernel", 89 "default-channel": "20", 90 }, 91 map[string]interface{}{ 92 "name": "pc", 93 "id": "pcididididididididididididididid", 94 "type": "gadget", 95 "default-channel": "20", 96 }, 97 } 98 99 // copy current model unless new model test data is different 100 // and delete nil keys in new model 101 func mergeMockModelHeaders(cur, new map[string]interface{}) { 102 for k, v := range cur { 103 if v, ok := new[k]; ok { 104 if v == nil { 105 delete(new, k) 106 } 107 continue 108 } 109 new[k] = v 110 } 111 } 112 113 func (s *deviceMgrRemodelSuite) TestRemodelUnhappy(c *C) { 114 s.state.Lock() 115 defer s.state.Unlock() 116 s.state.Set("seeded", true) 117 118 // set a model assertion 119 cur := map[string]interface{}{ 120 "brand": "canonical", 121 "model": "pc-model", 122 "architecture": "amd64", 123 "kernel": "pc-kernel", 124 "gadget": "pc", 125 } 126 s.makeModelAssertionInState(c, cur["brand"].(string), cur["model"].(string), map[string]interface{}{ 127 "architecture": cur["architecture"], 128 "kernel": cur["kernel"], 129 "gadget": cur["gadget"], 130 }) 131 s.makeSerialAssertionInState(c, cur["brand"].(string), cur["model"].(string), "orig-serial") 132 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 133 Brand: cur["brand"].(string), 134 Model: cur["model"].(string), 135 Serial: "orig-serial", 136 }) 137 138 restore := devicestate.AllowUC20RemodelTesting(false) 139 defer restore() 140 // ensure all error cases are checked 141 for _, t := range []struct { 142 new map[string]interface{} 143 errStr string 144 }{ 145 {map[string]interface{}{"architecture": "pdp-7"}, "cannot remodel to different architectures yet"}, 146 {map[string]interface{}{"base": "core18"}, "cannot remodel from core to bases yet"}, 147 {map[string]interface{}{"base": "core20", "kernel": nil, "gadget": nil, "snaps": mockCore20ModelSnaps}, "cannot remodel to Ubuntu Core 20 models yet"}, 148 } { 149 mergeMockModelHeaders(cur, t.new) 150 new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new) 151 chg, err := devicestate.Remodel(s.state, new) 152 c.Check(chg, IsNil) 153 c.Check(err, ErrorMatches, t.errStr) 154 } 155 156 restore = devicestate.AllowUC20RemodelTesting(true) 157 defer restore() 158 159 for _, t := range []struct { 160 new map[string]interface{} 161 errStr string 162 }{ 163 // pre-UC20 to UC20 164 {map[string]interface{}{"base": "core20", "kernel": nil, "gadget": nil, "snaps": mockCore20ModelSnaps}, "cannot remodel from grade unset to grade signed"}, 165 } { 166 mergeMockModelHeaders(cur, t.new) 167 new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new) 168 chg, err := devicestate.Remodel(s.state, new) 169 c.Check(chg, IsNil) 170 c.Check(err, ErrorMatches, t.errStr) 171 } 172 173 } 174 175 func (s *deviceMgrRemodelSuite) TestRemodelCheckGrade(c *C) { 176 s.state.Lock() 177 defer s.state.Unlock() 178 s.state.Set("seeded", true) 179 180 restore := devicestate.AllowUC20RemodelTesting(true) 181 defer restore() 182 183 // set a model assertion 184 cur := mockCore20ModelHeaders 185 s.makeModelAssertionInState(c, cur["brand"].(string), cur["model"].(string), map[string]interface{}{ 186 "architecture": cur["architecture"], 187 "base": cur["base"], 188 "grade": cur["grade"], 189 "snaps": cur["snaps"], 190 }) 191 s.makeSerialAssertionInState(c, cur["brand"].(string), cur["model"].(string), "orig-serial") 192 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 193 Brand: cur["brand"].(string), 194 Model: cur["model"].(string), 195 Serial: "orig-serial", 196 }) 197 198 // ensure all error cases are checked 199 for idx, t := range []struct { 200 new map[string]interface{} 201 errStr string 202 }{ 203 // uc20 model 204 {map[string]interface{}{"grade": "signed"}, "cannot remodel from grade dangerous to grade signed"}, 205 {map[string]interface{}{"grade": "secured"}, "cannot remodel from grade dangerous to grade secured"}, 206 // non-uc20 model 207 {map[string]interface{}{"snaps": nil, "grade": nil, "base": "core", "gadget": "pc", "kernel": "pc-kernel"}, "cannot remodel from grade dangerous to grade unset"}, 208 } { 209 c.Logf("tc: %v", idx) 210 mergeMockModelHeaders(cur, t.new) 211 new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new) 212 chg, err := devicestate.Remodel(s.state, new) 213 c.Check(chg, IsNil) 214 c.Check(err, ErrorMatches, t.errStr) 215 } 216 } 217 218 func (s *deviceMgrRemodelSuite) TestRemodelRequiresSerial(c *C) { 219 s.state.Lock() 220 defer s.state.Unlock() 221 s.state.Set("seeded", true) 222 223 // set a model assertion 224 cur := map[string]interface{}{ 225 "brand": "canonical", 226 "model": "pc-model", 227 "architecture": "amd64", 228 "kernel": "pc-kernel", 229 "gadget": "pc", 230 } 231 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 232 "architecture": "amd64", 233 "kernel": "pc-kernel", 234 "gadget": "pc", 235 }) 236 // no serial assertion, no serial in state 237 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 238 Brand: "canonical", 239 Model: "pc-model", 240 }) 241 242 newModelHdrs := map[string]interface{}{ 243 "revision": "2", 244 } 245 mergeMockModelHeaders(cur, newModelHdrs) 246 new := s.brands.Model("canonical", "pc-model", newModelHdrs) 247 chg, err := devicestate.Remodel(s.state, new) 248 c.Check(chg, IsNil) 249 c.Check(err, ErrorMatches, "cannot remodel without a serial") 250 } 251 252 func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchGadgetTrack(c *C) { 253 s.testRemodelTasksSwitchTrack(c, "pc", map[string]interface{}{ 254 "gadget": "pc=18", 255 }) 256 } 257 258 func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchKernelTrack(c *C) { 259 s.testRemodelTasksSwitchTrack(c, "pc-kernel", map[string]interface{}{ 260 "kernel": "pc-kernel=18", 261 }) 262 } 263 264 func (s *deviceMgrRemodelSuite) testRemodelTasksSwitchTrack(c *C, whatRefreshes string, newModelOverrides map[string]interface{}) { 265 s.state.Lock() 266 defer s.state.Unlock() 267 s.state.Set("seeded", true) 268 s.state.Set("refresh-privacy-key", "some-privacy-key") 269 270 var testDeviceCtx snapstate.DeviceContext 271 272 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 273 c.Check(flags.Required, Equals, true) 274 c.Check(deviceCtx, Equals, testDeviceCtx) 275 c.Check(fromChange, Equals, "99") 276 277 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 278 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 279 tValidate.WaitFor(tDownload) 280 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 281 tInstall.WaitFor(tValidate) 282 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 283 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 284 return ts, nil 285 }) 286 defer restore() 287 288 restore = devicestate.MockSnapstateUpdateWithDeviceContext(func(st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 289 c.Check(flags.Required, Equals, false) 290 c.Check(flags.NoReRefresh, Equals, true) 291 c.Check(deviceCtx, Equals, testDeviceCtx) 292 c.Check(fromChange, Equals, "99") 293 c.Check(name, Equals, whatRefreshes) 294 c.Check(opts.Channel, Equals, "18") 295 296 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s to track %s", name, opts.Channel)) 297 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 298 tValidate.WaitFor(tDownload) 299 tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel)) 300 tUpdate.WaitFor(tValidate) 301 ts := state.NewTaskSet(tDownload, tValidate, tUpdate) 302 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 303 return ts, nil 304 }) 305 defer restore() 306 307 // set a model assertion 308 current := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 309 "architecture": "amd64", 310 "kernel": "pc-kernel", 311 "gadget": "pc", 312 "base": "core18", 313 }) 314 err := assertstate.Add(s.state, current) 315 c.Assert(err, IsNil) 316 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 317 Brand: "canonical", 318 Model: "pc-model", 319 }) 320 321 headers := map[string]interface{}{ 322 "architecture": "amd64", 323 "kernel": "pc-kernel", 324 "gadget": "pc", 325 "base": "core18", 326 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 327 "revision": "1", 328 } 329 for k, v := range newModelOverrides { 330 headers[k] = v 331 } 332 new := s.brands.Model("canonical", "pc-model", headers) 333 334 testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true} 335 336 tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99") 337 c.Assert(err, IsNil) 338 // 2 snaps, plus one track switch plus the remodel task, the 339 // wait chain is tested in TestRemodel* 340 c.Assert(tss, HasLen, 4) 341 } 342 343 func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchGadget(c *C) { 344 s.testRemodelSwitchTasks(c, "other-gadget", "18", map[string]interface{}{ 345 "gadget": "other-gadget=18", 346 }) 347 } 348 349 func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchKernel(c *C) { 350 s.testRemodelSwitchTasks(c, "other-kernel", "18", map[string]interface{}{ 351 "kernel": "other-kernel=18", 352 }) 353 } 354 355 func (s *deviceMgrRemodelSuite) testRemodelSwitchTasks(c *C, whatsNew, whatNewTrack string, newModelOverrides map[string]interface{}) { 356 c.Check(newModelOverrides, HasLen, 1, Commentf("test expects a single model property to change")) 357 s.state.Lock() 358 defer s.state.Unlock() 359 s.state.Set("seeded", true) 360 s.state.Set("refresh-privacy-key", "some-privacy-key") 361 362 var testDeviceCtx snapstate.DeviceContext 363 364 var snapstateInstallWithDeviceContextCalled int 365 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 366 snapstateInstallWithDeviceContextCalled++ 367 c.Check(name, Equals, whatsNew) 368 if whatNewTrack != "" { 369 c.Check(opts.Channel, Equals, whatNewTrack) 370 } 371 372 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 373 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 374 tValidate.WaitFor(tDownload) 375 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 376 tInstall.WaitFor(tValidate) 377 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 378 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 379 return ts, nil 380 }) 381 defer restore() 382 383 // set a model assertion 384 current := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 385 "architecture": "amd64", 386 "kernel": "pc-kernel", 387 "gadget": "pc", 388 "base": "core18", 389 }) 390 err := assertstate.Add(s.state, current) 391 c.Assert(err, IsNil) 392 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 393 Brand: "canonical", 394 Model: "pc-model", 395 }) 396 397 headers := map[string]interface{}{ 398 "architecture": "amd64", 399 "kernel": "pc-kernel", 400 "gadget": "pc", 401 "base": "core18", 402 "revision": "1", 403 } 404 for k, v := range newModelOverrides { 405 headers[k] = v 406 } 407 new := s.brands.Model("canonical", "pc-model", headers) 408 409 testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true} 410 411 tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99") 412 c.Assert(err, IsNil) 413 // 1 of switch-kernel/base/gadget plus the remodel task 414 c.Assert(tss, HasLen, 2) 415 // API was hit 416 c.Assert(snapstateInstallWithDeviceContextCalled, Equals, 1) 417 } 418 419 func (s *deviceMgrRemodelSuite) TestRemodelRequiredSnaps(c *C) { 420 s.state.Lock() 421 defer s.state.Unlock() 422 s.state.Set("seeded", true) 423 s.state.Set("refresh-privacy-key", "some-privacy-key") 424 425 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 426 c.Check(flags.Required, Equals, true) 427 c.Check(deviceCtx, NotNil) 428 c.Check(deviceCtx.ForRemodeling(), Equals, true) 429 430 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 431 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 432 tValidate.WaitFor(tDownload) 433 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 434 tInstall.WaitFor(tValidate) 435 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 436 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 437 return ts, nil 438 }) 439 defer restore() 440 441 // set a model assertion 442 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 443 "architecture": "amd64", 444 "kernel": "pc-kernel", 445 "gadget": "pc", 446 "base": "core18", 447 }) 448 s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234") 449 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 450 Brand: "canonical", 451 Model: "pc-model", 452 Serial: "1234", 453 }) 454 455 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 456 "architecture": "amd64", 457 "kernel": "pc-kernel", 458 "gadget": "pc", 459 "base": "core18", 460 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 461 "revision": "1", 462 }) 463 chg, err := devicestate.Remodel(s.state, new) 464 c.Assert(err, IsNil) 465 c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") 466 467 tl := chg.Tasks() 468 // 2 snaps, 469 c.Assert(tl, HasLen, 2*3+1) 470 471 deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil) 472 c.Assert(err, IsNil) 473 // deviceCtx is actually a remodelContext here 474 remodCtx, ok := deviceCtx.(devicestate.RemodelContext) 475 c.Assert(ok, Equals, true) 476 c.Check(remodCtx.ForRemodeling(), Equals, true) 477 c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel) 478 c.Check(remodCtx.Model(), DeepEquals, new) 479 c.Check(remodCtx.Store(), IsNil) 480 481 // check the tasks 482 tDownloadSnap1 := tl[0] 483 tValidateSnap1 := tl[1] 484 tInstallSnap1 := tl[2] 485 tDownloadSnap2 := tl[3] 486 tValidateSnap2 := tl[4] 487 tInstallSnap2 := tl[5] 488 tSetModel := tl[6] 489 490 // check the tasks 491 c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download") 492 c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1") 493 c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0) 494 c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap") 495 c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1") 496 c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0) 497 c.Assert(tDownloadSnap2.Kind(), Equals, "fake-download") 498 c.Assert(tDownloadSnap2.Summary(), Equals, "Download new-required-snap-2") 499 // check the ordering, download/validate everything first, then install 500 501 // snap2 downloads wait for the downloads of snap1 502 c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0) 503 c.Assert(tValidateSnap1.WaitTasks(), DeepEquals, []*state.Task{ 504 tDownloadSnap1, 505 }) 506 c.Assert(tDownloadSnap2.WaitTasks(), DeepEquals, []*state.Task{ 507 tValidateSnap1, 508 }) 509 c.Assert(tValidateSnap2.WaitTasks(), DeepEquals, []*state.Task{ 510 tDownloadSnap2, 511 }) 512 c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{ 513 // wait for own check-snap 514 tValidateSnap1, 515 // and also the last check-snap of the download chain 516 tValidateSnap2, 517 }) 518 c.Assert(tInstallSnap2.WaitTasks(), DeepEquals, []*state.Task{ 519 // last snap of the download chain 520 tValidateSnap2, 521 // previous install chain 522 tInstallSnap1, 523 }) 524 525 c.Assert(tSetModel.Kind(), Equals, "set-model") 526 c.Assert(tSetModel.Summary(), Equals, "Set new model assertion") 527 // setModel waits for everything in the change 528 c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{tDownloadSnap1, tValidateSnap1, tInstallSnap1, tDownloadSnap2, tValidateSnap2, tInstallSnap2}) 529 } 530 531 func (s *deviceMgrRemodelSuite) TestRemodelSwitchKernelTrack(c *C) { 532 s.state.Lock() 533 defer s.state.Unlock() 534 s.state.Set("seeded", true) 535 s.state.Set("refresh-privacy-key", "some-privacy-key") 536 537 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 538 c.Check(flags.Required, Equals, true) 539 c.Check(deviceCtx, NotNil) 540 c.Check(deviceCtx.ForRemodeling(), Equals, true) 541 542 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 543 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 544 tValidate.WaitFor(tDownload) 545 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 546 tInstall.WaitFor(tValidate) 547 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 548 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 549 return ts, nil 550 }) 551 defer restore() 552 553 restore = devicestate.MockSnapstateUpdateWithDeviceContext(func(st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 554 c.Check(flags.Required, Equals, false) 555 c.Check(flags.NoReRefresh, Equals, true) 556 c.Check(deviceCtx, NotNil) 557 c.Check(deviceCtx.ForRemodeling(), Equals, true) 558 559 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s to track %s", name, opts.Channel)) 560 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 561 tValidate.WaitFor(tDownload) 562 tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel)) 563 tUpdate.WaitFor(tValidate) 564 ts := state.NewTaskSet(tDownload, tValidate, tUpdate) 565 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 566 return ts, nil 567 }) 568 defer restore() 569 570 // set a model assertion 571 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 572 "architecture": "amd64", 573 "kernel": "pc-kernel", 574 "gadget": "pc", 575 "base": "core18", 576 }) 577 s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234") 578 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 579 Brand: "canonical", 580 Model: "pc-model", 581 Serial: "1234", 582 }) 583 584 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 585 "architecture": "amd64", 586 "kernel": "pc-kernel=18", 587 "gadget": "pc", 588 "base": "core18", 589 "required-snaps": []interface{}{"new-required-snap-1"}, 590 "revision": "1", 591 }) 592 chg, err := devicestate.Remodel(s.state, new) 593 c.Assert(err, IsNil) 594 c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") 595 596 tl := chg.Tasks() 597 c.Assert(tl, HasLen, 2*3+1) 598 599 tDownloadKernel := tl[0] 600 tValidateKernel := tl[1] 601 tUpdateKernel := tl[2] 602 tDownloadSnap1 := tl[3] 603 tValidateSnap1 := tl[4] 604 tInstallSnap1 := tl[5] 605 tSetModel := tl[6] 606 607 c.Assert(tDownloadKernel.Kind(), Equals, "fake-download") 608 c.Assert(tDownloadKernel.Summary(), Equals, "Download pc-kernel to track 18") 609 c.Assert(tValidateKernel.Kind(), Equals, "validate-snap") 610 c.Assert(tValidateKernel.Summary(), Equals, "Validate pc-kernel") 611 c.Assert(tUpdateKernel.Kind(), Equals, "fake-update") 612 c.Assert(tUpdateKernel.Summary(), Equals, "Update pc-kernel to track 18") 613 c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download") 614 c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1") 615 c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap") 616 c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1") 617 c.Assert(tInstallSnap1.Kind(), Equals, "fake-install") 618 c.Assert(tInstallSnap1.Summary(), Equals, "Install new-required-snap-1") 619 620 c.Assert(tSetModel.Kind(), Equals, "set-model") 621 c.Assert(tSetModel.Summary(), Equals, "Set new model assertion") 622 623 // check the ordering 624 c.Assert(tDownloadSnap1.WaitTasks(), DeepEquals, []*state.Task{ 625 // previous download finished 626 tValidateKernel, 627 }) 628 c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{ 629 // last download in the chain finished 630 tValidateSnap1, 631 // and kernel got updated 632 tUpdateKernel, 633 }) 634 c.Assert(tUpdateKernel.WaitTasks(), DeepEquals, []*state.Task{ 635 // kernel is valid 636 tValidateKernel, 637 // and last download in the chain finished 638 tValidateSnap1, 639 }) 640 } 641 642 func (s *deviceMgrRemodelSuite) TestRemodelLessRequiredSnaps(c *C) { 643 s.state.Lock() 644 defer s.state.Unlock() 645 s.state.Set("seeded", true) 646 s.state.Set("refresh-privacy-key", "some-privacy-key") 647 648 // set a model assertion 649 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 650 "architecture": "amd64", 651 "kernel": "pc-kernel", 652 "gadget": "pc", 653 "base": "core18", 654 "required-snaps": []interface{}{"some-required-snap"}, 655 }) 656 s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234") 657 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 658 Brand: "canonical", 659 Model: "pc-model", 660 Serial: "1234", 661 }) 662 663 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 664 "architecture": "amd64", 665 "kernel": "pc-kernel", 666 "gadget": "pc", 667 "base": "core18", 668 "revision": "1", 669 }) 670 chg, err := devicestate.Remodel(s.state, new) 671 c.Assert(err, IsNil) 672 c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") 673 674 tl := chg.Tasks() 675 c.Assert(tl, HasLen, 1) 676 tSetModel := tl[0] 677 c.Assert(tSetModel.Kind(), Equals, "set-model") 678 c.Assert(tSetModel.Summary(), Equals, "Set new model assertion") 679 } 680 681 type freshSessionStore struct { 682 storetest.Store 683 684 ensureDeviceSession int 685 } 686 687 func (sto *freshSessionStore) EnsureDeviceSession() (*auth.DeviceState, error) { 688 sto.ensureDeviceSession += 1 689 return nil, nil 690 } 691 692 func (s *deviceMgrRemodelSuite) TestRemodelStoreSwitch(c *C) { 693 s.state.Lock() 694 defer s.state.Unlock() 695 s.state.Set("seeded", true) 696 s.state.Set("refresh-privacy-key", "some-privacy-key") 697 698 var testStore snapstate.StoreService 699 700 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 701 c.Check(flags.Required, Equals, true) 702 c.Check(deviceCtx, NotNil) 703 c.Check(deviceCtx.ForRemodeling(), Equals, true) 704 705 c.Check(deviceCtx.Store(), Equals, testStore) 706 707 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 708 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 709 tValidate.WaitFor(tDownload) 710 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 711 tInstall.WaitFor(tValidate) 712 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 713 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 714 return ts, nil 715 }) 716 defer restore() 717 718 // set a model assertion 719 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 720 "architecture": "amd64", 721 "kernel": "pc-kernel", 722 "gadget": "pc", 723 "base": "core18", 724 }) 725 s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234") 726 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 727 Brand: "canonical", 728 Model: "pc-model", 729 Serial: "1234", 730 }) 731 732 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 733 "architecture": "amd64", 734 "kernel": "pc-kernel", 735 "gadget": "pc", 736 "base": "core18", 737 "store": "switched-store", 738 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 739 "revision": "1", 740 }) 741 742 freshStore := &freshSessionStore{} 743 testStore = freshStore 744 745 s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService { 746 mod, err := devBE.Model() 747 c.Check(err, IsNil) 748 if err == nil { 749 c.Check(mod, DeepEquals, new) 750 } 751 return testStore 752 } 753 754 chg, err := devicestate.Remodel(s.state, new) 755 c.Assert(err, IsNil) 756 c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") 757 758 c.Check(freshStore.ensureDeviceSession, Equals, 1) 759 760 tl := chg.Tasks() 761 // 2 snaps * 3 tasks (from the mock install above) + 762 // 1 "set-model" task at the end 763 c.Assert(tl, HasLen, 2*3+1) 764 765 deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil) 766 c.Assert(err, IsNil) 767 // deviceCtx is actually a remodelContext here 768 remodCtx, ok := deviceCtx.(devicestate.RemodelContext) 769 c.Assert(ok, Equals, true) 770 c.Check(remodCtx.ForRemodeling(), Equals, true) 771 c.Check(remodCtx.Kind(), Equals, devicestate.StoreSwitchRemodel) 772 c.Check(remodCtx.Model(), DeepEquals, new) 773 c.Check(remodCtx.Store(), Equals, testStore) 774 } 775 776 func (s *deviceMgrRemodelSuite) TestRemodelRereg(c *C) { 777 s.state.Lock() 778 defer s.state.Unlock() 779 s.state.Set("seeded", true) 780 781 // set a model assertion 782 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 783 "architecture": "amd64", 784 "kernel": "pc-kernel", 785 "gadget": "pc", 786 "base": "core18", 787 }) 788 s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial") 789 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 790 Brand: "canonical", 791 Model: "pc-model", 792 Serial: "orig-serial", 793 SessionMacaroon: "old-session", 794 }) 795 796 new := s.brands.Model("canonical", "rereg-model", map[string]interface{}{ 797 "architecture": "amd64", 798 "kernel": "pc-kernel", 799 "gadget": "pc", 800 "base": "core18", 801 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 802 }) 803 804 s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService { 805 mod, err := devBE.Model() 806 c.Check(err, IsNil) 807 if err == nil { 808 c.Check(mod, DeepEquals, new) 809 } 810 return nil 811 } 812 813 chg, err := devicestate.Remodel(s.state, new) 814 c.Assert(err, IsNil) 815 816 c.Assert(chg.Summary(), Equals, "Remodel device to canonical/rereg-model (0)") 817 818 tl := chg.Tasks() 819 c.Assert(tl, HasLen, 2) 820 821 // check the tasks 822 tRequestSerial := tl[0] 823 tPrepareRemodeling := tl[1] 824 825 // check the tasks 826 c.Assert(tRequestSerial.Kind(), Equals, "request-serial") 827 c.Assert(tRequestSerial.Summary(), Equals, "Request new device serial") 828 c.Assert(tRequestSerial.WaitTasks(), HasLen, 0) 829 830 c.Assert(tPrepareRemodeling.Kind(), Equals, "prepare-remodeling") 831 c.Assert(tPrepareRemodeling.Summary(), Equals, "Prepare remodeling") 832 c.Assert(tPrepareRemodeling.WaitTasks(), DeepEquals, []*state.Task{tRequestSerial}) 833 } 834 835 func (s *deviceMgrRemodelSuite) TestRemodelClash(c *C) { 836 s.state.Lock() 837 defer s.state.Unlock() 838 s.state.Set("seeded", true) 839 s.state.Set("refresh-privacy-key", "some-privacy-key") 840 841 var clashing *asserts.Model 842 843 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 844 // simulate things changing under our feet 845 assertstatetest.AddMany(st, clashing) 846 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 847 Brand: "canonical", 848 Model: clashing.Model(), 849 }) 850 851 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 852 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 853 tValidate.WaitFor(tDownload) 854 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 855 tInstall.WaitFor(tValidate) 856 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 857 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 858 return ts, nil 859 }) 860 defer restore() 861 862 // set a model assertion 863 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 864 "architecture": "amd64", 865 "kernel": "pc-kernel", 866 "gadget": "pc", 867 "base": "core18", 868 }) 869 s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234") 870 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 871 Brand: "canonical", 872 Model: "pc-model", 873 Serial: "1234", 874 }) 875 876 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 877 "architecture": "amd64", 878 "kernel": "pc-kernel", 879 "gadget": "pc", 880 "base": "core18", 881 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 882 "revision": "1", 883 }) 884 other := s.brands.Model("canonical", "pc-model-other", map[string]interface{}{ 885 "architecture": "amd64", 886 "kernel": "pc-kernel", 887 "gadget": "pc", 888 "base": "core18", 889 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 890 }) 891 892 clashing = other 893 _, err := devicestate.Remodel(s.state, new) 894 c.Check(err, DeepEquals, &snapstate.ChangeConflictError{ 895 Message: "cannot start remodel, clashing with concurrent remodel to canonical/pc-model-other (0)", 896 }) 897 898 // reset 899 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 900 Brand: "canonical", 901 Model: "pc-model", 902 Serial: "1234", 903 }) 904 clashing = new 905 _, err = devicestate.Remodel(s.state, new) 906 c.Check(err, DeepEquals, &snapstate.ChangeConflictError{ 907 Message: "cannot start remodel, clashing with concurrent remodel to canonical/pc-model (1)", 908 }) 909 } 910 911 func (s *deviceMgrRemodelSuite) TestRemodelClashInProgress(c *C) { 912 s.state.Lock() 913 defer s.state.Unlock() 914 s.state.Set("seeded", true) 915 s.state.Set("refresh-privacy-key", "some-privacy-key") 916 917 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 918 // simulate another started remodeling 919 st.NewChange("remodel", "other remodel") 920 921 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 922 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 923 tValidate.WaitFor(tDownload) 924 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 925 tInstall.WaitFor(tValidate) 926 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 927 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 928 return ts, nil 929 }) 930 defer restore() 931 932 // set a model assertion 933 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 934 "architecture": "amd64", 935 "kernel": "pc-kernel", 936 "gadget": "pc", 937 "base": "core18", 938 }) 939 s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234") 940 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 941 Brand: "canonical", 942 Model: "pc-model", 943 Serial: "1234", 944 }) 945 946 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 947 "architecture": "amd64", 948 "kernel": "pc-kernel", 949 "gadget": "pc", 950 "base": "core18", 951 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 952 "revision": "1", 953 }) 954 955 _, err := devicestate.Remodel(s.state, new) 956 c.Check(err, DeepEquals, &snapstate.ChangeConflictError{ 957 Message: "cannot start remodel, clashing with concurrent one", 958 }) 959 } 960 961 func (s *deviceMgrRemodelSuite) TestReregRemodelClashAnyChange(c *C) { 962 s.state.Lock() 963 defer s.state.Unlock() 964 s.state.Set("seeded", true) 965 966 // set a model assertion 967 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 968 "architecture": "amd64", 969 "kernel": "pc-kernel", 970 "gadget": "pc", 971 "base": "core18", 972 }) 973 s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial") 974 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 975 Brand: "canonical", 976 Model: "pc-model", 977 Serial: "orig-serial", 978 SessionMacaroon: "old-session", 979 }) 980 981 new := s.brands.Model("canonical", "pc-model-2", map[string]interface{}{ 982 "architecture": "amd64", 983 "kernel": "pc-kernel", 984 "gadget": "pc", 985 "base": "core18", 986 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 987 "revision": "1", 988 }) 989 s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService { 990 // we never reach the place where this gets called 991 c.Fatalf("unexpected call") 992 return nil 993 } 994 995 // simulate any other change 996 chg := s.state.NewChange("chg", "other change") 997 chg.SetStatus(state.DoingStatus) 998 999 _, err := devicestate.Remodel(s.state, new) 1000 c.Assert(err, NotNil) 1001 c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{ 1002 ChangeKind: "chg", 1003 Message: `other changes in progress (conflicting change "chg"), change "remodel" not allowed until they are done`, 1004 }) 1005 } 1006 1007 func (s *deviceMgrRemodelSuite) TestRemodeling(c *C) { 1008 s.state.Lock() 1009 defer s.state.Unlock() 1010 1011 // no changes 1012 c.Check(devicestate.Remodeling(s.state), Equals, false) 1013 1014 // other change 1015 s.state.NewChange("other", "...") 1016 c.Check(devicestate.Remodeling(s.state), Equals, false) 1017 1018 // remodel change 1019 chg := s.state.NewChange("remodel", "...") 1020 c.Check(devicestate.Remodeling(s.state), Equals, true) 1021 1022 // done 1023 chg.SetStatus(state.DoneStatus) 1024 c.Check(devicestate.Remodeling(s.state), Equals, false) 1025 } 1026 1027 func (s *deviceMgrRemodelSuite) TestDeviceCtxNoTask(c *C) { 1028 s.state.Lock() 1029 defer s.state.Unlock() 1030 // nothing in the state 1031 1032 _, err := devicestate.DeviceCtx(s.state, nil, nil) 1033 c.Check(err, Equals, state.ErrNoState) 1034 1035 // have a model assertion 1036 model := s.brands.Model("canonical", "pc", map[string]interface{}{ 1037 "gadget": "pc", 1038 "kernel": "kernel", 1039 "architecture": "amd64", 1040 }) 1041 assertstatetest.AddMany(s.state, model) 1042 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1043 Brand: "canonical", 1044 Model: "pc", 1045 }) 1046 1047 deviceCtx, err := devicestate.DeviceCtx(s.state, nil, nil) 1048 c.Assert(err, IsNil) 1049 c.Assert(deviceCtx.Model().BrandID(), Equals, "canonical") 1050 1051 c.Check(deviceCtx.Classic(), Equals, false) 1052 c.Check(deviceCtx.Kernel(), Equals, "kernel") 1053 c.Check(deviceCtx.Base(), Equals, "") 1054 c.Check(deviceCtx.RunMode(), Equals, true) 1055 // not a uc20 model, so no modeenv 1056 c.Check(deviceCtx.HasModeenv(), Equals, false) 1057 } 1058 1059 func (s *deviceMgrRemodelSuite) TestDeviceCtxGroundContext(c *C) { 1060 s.state.Lock() 1061 defer s.state.Unlock() 1062 1063 // have a model assertion 1064 model := s.brands.Model("canonical", "pc", map[string]interface{}{ 1065 "gadget": "pc", 1066 "kernel": "kernel", 1067 "architecture": "amd64", 1068 }) 1069 assertstatetest.AddMany(s.state, model) 1070 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1071 Brand: "canonical", 1072 Model: "pc", 1073 }) 1074 1075 deviceCtx, err := devicestate.DeviceCtx(s.state, nil, nil) 1076 c.Assert(err, IsNil) 1077 c.Assert(deviceCtx.Model().BrandID(), Equals, "canonical") 1078 groundCtx := deviceCtx.GroundContext() 1079 c.Check(groundCtx.ForRemodeling(), Equals, false) 1080 c.Check(groundCtx.Model().Model(), Equals, "pc") 1081 c.Check(groundCtx.Store, PanicMatches, `retrieved ground context is not intended to drive store operations`) 1082 } 1083 1084 func (s *deviceMgrRemodelSuite) TestDeviceCtxProvided(c *C) { 1085 s.state.Lock() 1086 defer s.state.Unlock() 1087 1088 model := assertstest.FakeAssertion(map[string]interface{}{ 1089 "type": "model", 1090 "authority-id": "canonical", 1091 "series": "16", 1092 "brand-id": "canonical", 1093 "model": "pc", 1094 "gadget": "pc", 1095 "kernel": "kernel", 1096 "architecture": "amd64", 1097 }).(*asserts.Model) 1098 1099 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model} 1100 1101 deviceCtx1, err := devicestate.DeviceCtx(s.state, nil, deviceCtx) 1102 c.Assert(err, IsNil) 1103 c.Assert(deviceCtx1, Equals, deviceCtx) 1104 } 1105 1106 func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatible(c *C) { 1107 s.state.Lock() 1108 defer s.state.Unlock() 1109 1110 currentSnapYaml := ` 1111 name: gadget 1112 type: gadget 1113 version: 123 1114 ` 1115 remodelSnapYaml := ` 1116 name: new-gadget 1117 type: gadget 1118 version: 123 1119 ` 1120 mockGadget := ` 1121 type: gadget 1122 name: gadget 1123 volumes: 1124 volume: 1125 schema: gpt 1126 bootloader: grub 1127 ` 1128 siCurrent := &snap.SideInfo{Revision: snap.R(123), RealName: "gadget"} 1129 // so that we get a directory 1130 currInfo := snaptest.MockSnapWithFiles(c, currentSnapYaml, siCurrent, nil) 1131 info := snaptest.MockSnapWithFiles(c, remodelSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, nil) 1132 snapf, err := snapfile.Open(info.MountDir()) 1133 c.Assert(err, IsNil) 1134 1135 s.setupBrands(c) 1136 1137 oldModel := fakeMyModel(map[string]interface{}{ 1138 "architecture": "amd64", 1139 "gadget": "gadget", 1140 "kernel": "kernel", 1141 }) 1142 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: oldModel} 1143 1144 // model assertion in device context 1145 newModel := fakeMyModel(map[string]interface{}{ 1146 "architecture": "amd64", 1147 "gadget": "new-gadget", 1148 "kernel": "kernel", 1149 }) 1150 remodelCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: newModel, Remodeling: true, OldDeviceModel: oldModel} 1151 1152 restore := devicestate.MockGadgetIsCompatible(func(current, update *gadget.Info) error { 1153 c.Assert(current.Volumes, HasLen, 1) 1154 c.Assert(update.Volumes, HasLen, 1) 1155 return errors.New("fail") 1156 }) 1157 defer restore() 1158 1159 // not on classic 1160 release.OnClassic = true 1161 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx) 1162 c.Check(err, IsNil) 1163 release.OnClassic = false 1164 1165 // nothing if not remodeling 1166 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, deviceCtx) 1167 c.Check(err, IsNil) 1168 1169 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx) 1170 c.Check(err, ErrorMatches, "cannot read new gadget metadata: .*/new-gadget/1/meta/gadget.yaml: no such file or directory") 1171 1172 // drop gadget.yaml to the new gadget 1173 err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(mockGadget), 0644) 1174 c.Assert(err, IsNil) 1175 1176 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx) 1177 c.Check(err, ErrorMatches, "cannot read current gadget metadata: .*/gadget/123/meta/gadget.yaml: no such file or directory") 1178 1179 // drop gadget.yaml to the current gadget 1180 err = ioutil.WriteFile(filepath.Join(currInfo.MountDir(), "meta/gadget.yaml"), []byte(mockGadget), 0644) 1181 c.Assert(err, IsNil) 1182 1183 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx) 1184 c.Check(err, ErrorMatches, "cannot remodel to an incompatible gadget: fail") 1185 1186 restore = devicestate.MockGadgetIsCompatible(func(current, update *gadget.Info) error { 1187 c.Assert(current.Volumes, HasLen, 1) 1188 c.Assert(update.Volumes, HasLen, 1) 1189 return nil 1190 }) 1191 defer restore() 1192 1193 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx) 1194 c.Check(err, IsNil) 1195 1196 // when remodeling to completely new gadget snap, there is no current 1197 // snap passed to the check callback 1198 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx) 1199 c.Check(err, ErrorMatches, "cannot identify the current gadget snap") 1200 1201 // mock data to obtain current gadget info 1202 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1203 Brand: "canonical", 1204 Model: "gadget", 1205 }) 1206 s.makeModelAssertionInState(c, "canonical", "gadget", map[string]interface{}{ 1207 "architecture": "amd64", 1208 "kernel": "kernel", 1209 "gadget": "gadget", 1210 }) 1211 1212 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx) 1213 c.Check(err, ErrorMatches, "cannot identify the current gadget snap") 1214 1215 snapstate.Set(s.state, "gadget", &snapstate.SnapState{ 1216 SnapType: "gadget", 1217 Sequence: []*snap.SideInfo{siCurrent}, 1218 Current: siCurrent.Revision, 1219 Active: true, 1220 }) 1221 1222 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx) 1223 c.Check(err, IsNil) 1224 } 1225 1226 var ( 1227 compatibleTestMockOkGadget = ` 1228 type: gadget 1229 name: gadget 1230 volumes: 1231 volume: 1232 schema: gpt 1233 bootloader: grub 1234 structure: 1235 - name: foo 1236 size: 10M 1237 type: 00000000-0000-0000-0000-0000deadbeef 1238 ` 1239 ) 1240 1241 func (s *deviceMgrRemodelSuite) testCheckGadgetRemodelCompatibleWithYaml(c *C, currentGadgetYaml, newGadgetYaml string, expErr string) { 1242 s.state.Lock() 1243 defer s.state.Unlock() 1244 1245 currentSnapYaml := ` 1246 name: gadget 1247 type: gadget 1248 version: 123 1249 ` 1250 remodelSnapYaml := ` 1251 name: new-gadget 1252 type: gadget 1253 version: 123 1254 ` 1255 1256 currInfo := snaptest.MockSnapWithFiles(c, currentSnapYaml, &snap.SideInfo{Revision: snap.R(123)}, [][]string{ 1257 {"meta/gadget.yaml", currentGadgetYaml}, 1258 }) 1259 // gadget we're remodeling to is identical 1260 info := snaptest.MockSnapWithFiles(c, remodelSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, [][]string{ 1261 {"meta/gadget.yaml", newGadgetYaml}, 1262 }) 1263 snapf, err := snapfile.Open(info.MountDir()) 1264 c.Assert(err, IsNil) 1265 1266 s.setupBrands(c) 1267 // model assertion in device context 1268 oldModel := fakeMyModel(map[string]interface{}{ 1269 "architecture": "amd64", 1270 "gadget": "new-gadget", 1271 "kernel": "krnl", 1272 }) 1273 model := fakeMyModel(map[string]interface{}{ 1274 "architecture": "amd64", 1275 "gadget": "new-gadget", 1276 "kernel": "krnl", 1277 }) 1278 remodelCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model, Remodeling: true, OldDeviceModel: oldModel} 1279 1280 err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx) 1281 if expErr == "" { 1282 c.Check(err, IsNil) 1283 } else { 1284 c.Check(err, ErrorMatches, expErr) 1285 } 1286 1287 } 1288 1289 func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatibleWithYamlHappy(c *C) { 1290 s.testCheckGadgetRemodelCompatibleWithYaml(c, compatibleTestMockOkGadget, compatibleTestMockOkGadget, "") 1291 } 1292 1293 func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatibleWithYamlBad(c *C) { 1294 mockBadGadgetYaml := ` 1295 type: gadget 1296 name: gadget 1297 volumes: 1298 volume: 1299 schema: gpt 1300 bootloader: grub 1301 structure: 1302 - name: foo 1303 size: 20M 1304 type: 00000000-0000-0000-0000-0000deadbeef 1305 ` 1306 1307 errMatch := `cannot remodel to an incompatible gadget: incompatible layout change: incompatible structure #0 \("foo"\) change: cannot change structure size from 10485760 to 20971520` 1308 s.testCheckGadgetRemodelCompatibleWithYaml(c, compatibleTestMockOkGadget, mockBadGadgetYaml, errMatch) 1309 } 1310 1311 func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsUpdate(c *C) { 1312 var currentGadgetYaml = ` 1313 volumes: 1314 pc: 1315 bootloader: grub 1316 structure: 1317 - name: foo 1318 type: 00000000-0000-0000-0000-0000deadcafe 1319 filesystem: ext4 1320 size: 10M 1321 content: 1322 - source: foo-content 1323 target: / 1324 - name: bare-one 1325 type: bare 1326 size: 1M 1327 content: 1328 - image: bare.img 1329 ` 1330 1331 var remodelGadgetYaml = ` 1332 volumes: 1333 pc: 1334 bootloader: grub 1335 structure: 1336 - name: foo 1337 type: 00000000-0000-0000-0000-0000deadcafe 1338 filesystem: ext4 1339 size: 10M 1340 content: 1341 - source: new-foo-content 1342 target: / 1343 - name: bare-one 1344 type: bare 1345 size: 1M 1346 content: 1347 - image: new-bare-content.img 1348 ` 1349 1350 s.state.Lock() 1351 s.state.Set("seeded", true) 1352 s.state.Set("refresh-privacy-key", "some-privacy-key") 1353 1354 nopHandler := func(task *state.Task, _ *tomb.Tomb) error { 1355 return nil 1356 } 1357 s.o.TaskRunner().AddHandler("fake-download", nopHandler, nil) 1358 s.o.TaskRunner().AddHandler("validate-snap", nopHandler, nil) 1359 s.o.TaskRunner().AddHandler("set-model", nopHandler, nil) 1360 1361 // set a model assertion we remodel from 1362 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 1363 "architecture": "amd64", 1364 "kernel": "pc-kernel", 1365 "gadget": "pc", 1366 "base": "core18", 1367 }) 1368 s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial") 1369 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1370 Brand: "canonical", 1371 Model: "pc-model", 1372 Serial: "serial", 1373 }) 1374 1375 // the target model 1376 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 1377 "architecture": "amd64", 1378 "kernel": "pc-kernel", 1379 "base": "core18", 1380 "revision": "1", 1381 // remodel to new gadget 1382 "gadget": "new-gadget", 1383 }) 1384 1385 // current gadget 1386 siModelGadget := &snap.SideInfo{ 1387 RealName: "pc", 1388 Revision: snap.R(33), 1389 SnapID: "foo-id", 1390 } 1391 currentGadgetInfo := snaptest.MockSnapWithFiles(c, snapYaml, siModelGadget, [][]string{ 1392 {"meta/gadget.yaml", currentGadgetYaml}, 1393 }) 1394 snapstate.Set(s.state, "pc", &snapstate.SnapState{ 1395 SnapType: "gadget", 1396 Sequence: []*snap.SideInfo{siModelGadget}, 1397 Current: siModelGadget.Revision, 1398 Active: true, 1399 }) 1400 1401 // new gadget snap 1402 siNewModelGadget := &snap.SideInfo{ 1403 RealName: "new-gadget", 1404 Revision: snap.R(34), 1405 } 1406 newGadgetInfo := snaptest.MockSnapWithFiles(c, snapYaml, siNewModelGadget, [][]string{ 1407 {"meta/gadget.yaml", remodelGadgetYaml}, 1408 }) 1409 1410 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 1411 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 1412 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 1413 tValidate.WaitFor(tDownload) 1414 tGadgetUpdate := s.state.NewTask("update-gadget-assets", fmt.Sprintf("Update gadget %s", name)) 1415 tGadgetUpdate.Set("snap-setup", &snapstate.SnapSetup{ 1416 SideInfo: siNewModelGadget, 1417 Type: snap.TypeGadget, 1418 }) 1419 tGadgetUpdate.WaitFor(tValidate) 1420 ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate) 1421 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 1422 return ts, nil 1423 }) 1424 defer restore() 1425 restore = release.MockOnClassic(false) 1426 defer restore() 1427 1428 gadgetUpdateCalled := false 1429 restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 1430 gadgetUpdateCalled = true 1431 c.Check(policy, NotNil) 1432 c.Check(reflect.ValueOf(policy).Pointer(), Equals, reflect.ValueOf(gadget.RemodelUpdatePolicy).Pointer()) 1433 c.Check(current, DeepEquals, gadget.GadgetData{ 1434 Info: &gadget.Info{ 1435 Volumes: map[string]*gadget.Volume{ 1436 "pc": { 1437 Bootloader: "grub", 1438 Schema: "gpt", 1439 Structure: []gadget.VolumeStructure{{ 1440 Name: "foo", 1441 Type: "00000000-0000-0000-0000-0000deadcafe", 1442 Size: 10 * quantity.SizeMiB, 1443 Filesystem: "ext4", 1444 Content: []gadget.VolumeContent{ 1445 {UnresolvedSource: "foo-content", Target: "/"}, 1446 }, 1447 }, { 1448 Name: "bare-one", 1449 Type: "bare", 1450 Size: quantity.SizeMiB, 1451 Content: []gadget.VolumeContent{ 1452 {Image: "bare.img"}, 1453 }, 1454 }}, 1455 }, 1456 }, 1457 }, 1458 RootDir: currentGadgetInfo.MountDir(), 1459 }) 1460 c.Check(update, DeepEquals, gadget.GadgetData{ 1461 Info: &gadget.Info{ 1462 Volumes: map[string]*gadget.Volume{ 1463 "pc": { 1464 Bootloader: "grub", 1465 Schema: "gpt", 1466 Structure: []gadget.VolumeStructure{{ 1467 Name: "foo", 1468 Type: "00000000-0000-0000-0000-0000deadcafe", 1469 Size: 10 * quantity.SizeMiB, 1470 Filesystem: "ext4", 1471 Content: []gadget.VolumeContent{ 1472 {UnresolvedSource: "new-foo-content", Target: "/"}, 1473 }, 1474 }, { 1475 Name: "bare-one", 1476 Type: "bare", 1477 Size: quantity.SizeMiB, 1478 Content: []gadget.VolumeContent{ 1479 {Image: "new-bare-content.img"}, 1480 }, 1481 }}, 1482 }, 1483 }, 1484 }, 1485 RootDir: newGadgetInfo.MountDir(), 1486 }) 1487 return nil 1488 }) 1489 defer restore() 1490 1491 chg, err := devicestate.Remodel(s.state, new) 1492 c.Assert(err, IsNil) 1493 s.state.Unlock() 1494 1495 s.settle(c) 1496 1497 s.state.Lock() 1498 defer s.state.Unlock() 1499 c.Check(chg.IsReady(), Equals, true) 1500 c.Check(chg.Err(), IsNil) 1501 c.Check(gadgetUpdateCalled, Equals, true) 1502 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem}) 1503 } 1504 1505 func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsParanoidCheck(c *C) { 1506 s.state.Lock() 1507 s.state.Set("seeded", true) 1508 s.state.Set("refresh-privacy-key", "some-privacy-key") 1509 1510 nopHandler := func(task *state.Task, _ *tomb.Tomb) error { 1511 return nil 1512 } 1513 s.o.TaskRunner().AddHandler("fake-download", nopHandler, nil) 1514 s.o.TaskRunner().AddHandler("validate-snap", nopHandler, nil) 1515 s.o.TaskRunner().AddHandler("set-model", nopHandler, nil) 1516 1517 // set a model assertion we remodel from 1518 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 1519 "architecture": "amd64", 1520 "kernel": "pc-kernel", 1521 "gadget": "pc", 1522 "base": "core18", 1523 }) 1524 s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial") 1525 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1526 Brand: "canonical", 1527 Model: "pc-model", 1528 Serial: "serial", 1529 }) 1530 1531 // the target model 1532 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 1533 "architecture": "amd64", 1534 "kernel": "pc-kernel", 1535 "base": "core18", 1536 "revision": "1", 1537 // remodel to new gadget 1538 "gadget": "new-gadget", 1539 }) 1540 1541 // current gadget 1542 siModelGadget := &snap.SideInfo{ 1543 RealName: "pc", 1544 Revision: snap.R(33), 1545 SnapID: "foo-id", 1546 } 1547 snapstate.Set(s.state, "pc", &snapstate.SnapState{ 1548 SnapType: "gadget", 1549 Sequence: []*snap.SideInfo{siModelGadget}, 1550 Current: siModelGadget.Revision, 1551 Active: true, 1552 }) 1553 1554 // new gadget snap, name does not match the new model 1555 siUnexpectedModelGadget := &snap.SideInfo{ 1556 RealName: "new-gadget-unexpected", 1557 Revision: snap.R(34), 1558 } 1559 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 1560 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 1561 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 1562 tValidate.WaitFor(tDownload) 1563 tGadgetUpdate := s.state.NewTask("update-gadget-assets", fmt.Sprintf("Update gadget %s", name)) 1564 tGadgetUpdate.Set("snap-setup", &snapstate.SnapSetup{ 1565 SideInfo: siUnexpectedModelGadget, 1566 Type: snap.TypeGadget, 1567 }) 1568 tGadgetUpdate.WaitFor(tValidate) 1569 ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate) 1570 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 1571 return ts, nil 1572 }) 1573 defer restore() 1574 restore = release.MockOnClassic(false) 1575 defer restore() 1576 1577 gadgetUpdateCalled := false 1578 restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 1579 return errors.New("unexpected call") 1580 }) 1581 defer restore() 1582 1583 chg, err := devicestate.Remodel(s.state, new) 1584 c.Assert(err, IsNil) 1585 s.state.Unlock() 1586 1587 s.settle(c) 1588 1589 s.state.Lock() 1590 defer s.state.Unlock() 1591 c.Check(chg.IsReady(), Equals, true) 1592 c.Assert(chg.Err(), ErrorMatches, `(?s).*\(cannot apply gadget assets update from non-model gadget snap "new-gadget-unexpected", expected "new-gadget" snap\)`) 1593 c.Check(gadgetUpdateCalled, Equals, false) 1594 c.Check(s.restartRequests, HasLen, 0) 1595 } 1596 1597 func (s *deviceMgrSuite) TestRemodelSwitchBase(c *C) { 1598 s.state.Lock() 1599 defer s.state.Unlock() 1600 s.state.Set("seeded", true) 1601 s.state.Set("refresh-privacy-key", "some-privacy-key") 1602 1603 var testDeviceCtx snapstate.DeviceContext 1604 1605 var snapstateInstallWithDeviceContextCalled int 1606 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 1607 snapstateInstallWithDeviceContextCalled++ 1608 c.Check(name, Equals, "core20") 1609 1610 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 1611 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 1612 tValidate.WaitFor(tDownload) 1613 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 1614 tInstall.WaitFor(tValidate) 1615 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 1616 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 1617 return ts, nil 1618 }) 1619 defer restore() 1620 1621 // set a model assertion 1622 current := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 1623 "architecture": "amd64", 1624 "kernel": "pc-kernel", 1625 "gadget": "pc", 1626 "base": "core18", 1627 }) 1628 err := assertstate.Add(s.state, current) 1629 c.Assert(err, IsNil) 1630 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1631 Brand: "canonical", 1632 Model: "pc-model", 1633 }) 1634 1635 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 1636 "architecture": "amd64", 1637 "kernel": "pc-kernel", 1638 "gadget": "pc", 1639 "base": "core20", 1640 "revision": "1", 1641 }) 1642 1643 testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true} 1644 1645 tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99") 1646 c.Assert(err, IsNil) 1647 // 1 switch to a new base plus the remodel task 1648 c.Assert(tss, HasLen, 2) 1649 // API was hit 1650 c.Assert(snapstateInstallWithDeviceContextCalled, Equals, 1) 1651 } 1652 1653 func (s *deviceMgrRemodelSuite) TestRemodelUC20RequiredSnapsAndRecoverySystem(c *C) { 1654 s.state.Lock() 1655 defer s.state.Unlock() 1656 s.state.Set("seeded", true) 1657 s.state.Set("refresh-privacy-key", "some-privacy-key") 1658 1659 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 1660 c.Check(flags.Required, Equals, true) 1661 c.Check(deviceCtx, NotNil) 1662 c.Check(deviceCtx.ForRemodeling(), Equals, true) 1663 1664 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 1665 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 1666 tValidate.WaitFor(tDownload) 1667 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 1668 tInstall.WaitFor(tValidate) 1669 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 1670 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 1671 return ts, nil 1672 }) 1673 defer restore() 1674 1675 now := time.Now() 1676 restore = devicestate.MockTimeNow(func() time.Time { return now }) 1677 defer restore() 1678 1679 restore = devicestate.AllowUC20RemodelTesting(true) 1680 defer restore() 1681 1682 // set a model assertion 1683 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 1684 "architecture": "amd64", 1685 "base": "core20", 1686 "grade": "dangerous", 1687 "snaps": []interface{}{ 1688 map[string]interface{}{ 1689 "name": "pc-kernel", 1690 "id": snaptest.AssertedSnapID("pc-kernel"), 1691 "type": "kernel", 1692 "default-channel": "20", 1693 }, 1694 map[string]interface{}{ 1695 "name": "pc", 1696 "id": snaptest.AssertedSnapID("pc"), 1697 "type": "gadget", 1698 "default-channel": "20", 1699 }, 1700 }, 1701 }) 1702 s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial") 1703 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1704 Brand: "canonical", 1705 Model: "pc-model", 1706 Serial: "serial", 1707 }) 1708 1709 new := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 1710 "architecture": "amd64", 1711 "base": "core20", 1712 "grade": "dangerous", 1713 "revision": "1", 1714 "snaps": []interface{}{ 1715 map[string]interface{}{ 1716 "name": "pc-kernel", 1717 "id": snaptest.AssertedSnapID("pc-kernel"), 1718 "type": "kernel", 1719 "default-channel": "20", 1720 }, 1721 map[string]interface{}{ 1722 "name": "pc", 1723 "id": snaptest.AssertedSnapID("pc"), 1724 "type": "gadget", 1725 "default-channel": "20", 1726 }, 1727 map[string]interface{}{ 1728 "name": "new-required-snap-1", 1729 "id": snaptest.AssertedSnapID("new-required-snap-1"), 1730 "presence": "required", 1731 }, 1732 map[string]interface{}{ 1733 "name": "new-required-snap-2", 1734 "id": snaptest.AssertedSnapID("new-required-snap-2"), 1735 "presence": "required", 1736 }, 1737 }, 1738 }) 1739 chg, err := devicestate.Remodel(s.state, new) 1740 c.Assert(err, IsNil) 1741 c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") 1742 1743 tl := chg.Tasks() 1744 // 2 snaps (3 tasks for each) + recovery system (2 tasks) + set-model 1745 c.Assert(tl, HasLen, 2*3+2+1) 1746 1747 deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil) 1748 c.Assert(err, IsNil) 1749 // deviceCtx is actually a remodelContext here 1750 remodCtx, ok := deviceCtx.(devicestate.RemodelContext) 1751 c.Assert(ok, Equals, true) 1752 c.Check(remodCtx.ForRemodeling(), Equals, true) 1753 c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel) 1754 c.Check(remodCtx.Model(), DeepEquals, new) 1755 c.Check(remodCtx.Store(), IsNil) 1756 1757 // check the tasks 1758 tDownloadSnap1 := tl[0] 1759 tValidateSnap1 := tl[1] 1760 tInstallSnap1 := tl[2] 1761 tDownloadSnap2 := tl[3] 1762 tValidateSnap2 := tl[4] 1763 tInstallSnap2 := tl[5] 1764 tCreateRecovery := tl[6] 1765 tFinalizeRecovery := tl[7] 1766 tSetModel := tl[8] 1767 1768 // check the tasks 1769 c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download") 1770 c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1") 1771 c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0) 1772 c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap") 1773 c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1") 1774 expectedLabel := now.Format("20060102") 1775 c.Assert(tCreateRecovery.Kind(), Equals, "create-recovery-system") 1776 c.Assert(tCreateRecovery.Summary(), Equals, fmt.Sprintf("Create recovery system with label %q", expectedLabel)) 1777 c.Assert(tFinalizeRecovery.Kind(), Equals, "finalize-recovery-system") 1778 c.Assert(tFinalizeRecovery.Summary(), Equals, fmt.Sprintf("Finalize recovery system with label %q", expectedLabel)) 1779 // check the ordering, download/validate everything first, then install 1780 1781 // snap2 downloads wait for the downloads of snap1 1782 c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0) 1783 c.Assert(tValidateSnap1.WaitTasks(), DeepEquals, []*state.Task{ 1784 tDownloadSnap1, 1785 }) 1786 c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{ 1787 tValidateSnap1, 1788 tValidateSnap2, 1789 // wait for recovery system to be created 1790 tCreateRecovery, 1791 // and then finalized 1792 tFinalizeRecovery, 1793 }) 1794 c.Assert(tInstallSnap2.WaitTasks(), DeepEquals, []*state.Task{ 1795 tValidateSnap2, 1796 // previous install chain 1797 tInstallSnap1, 1798 }) 1799 c.Assert(tCreateRecovery.WaitTasks(), DeepEquals, []*state.Task{ 1800 // last snap of the download chain 1801 tValidateSnap2, 1802 }) 1803 c.Assert(tFinalizeRecovery.WaitTasks(), DeepEquals, []*state.Task{ 1804 // recovery system being created 1805 tCreateRecovery, 1806 // last snap of the download chain (added later) 1807 tValidateSnap2, 1808 }) 1809 1810 c.Assert(tSetModel.Kind(), Equals, "set-model") 1811 c.Assert(tSetModel.Summary(), Equals, "Set new model assertion") 1812 // setModel waits for everything in the change 1813 c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{ 1814 tDownloadSnap1, tValidateSnap1, tInstallSnap1, 1815 tDownloadSnap2, tValidateSnap2, tInstallSnap2, 1816 tCreateRecovery, tFinalizeRecovery, 1817 }) 1818 1819 // verify recovery system setup data on appropriate tasks 1820 var systemSetupData map[string]interface{} 1821 err = tCreateRecovery.Get("recovery-system-setup", &systemSetupData) 1822 c.Assert(err, IsNil) 1823 c.Assert(systemSetupData, DeepEquals, map[string]interface{}{ 1824 "label": expectedLabel, 1825 "directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", expectedLabel), 1826 "snap-setup-tasks": []interface{}{tDownloadSnap1.ID(), tDownloadSnap2.ID()}, 1827 }) 1828 // cross references of to recovery system setup data 1829 for _, tsk := range []*state.Task{tFinalizeRecovery, tSetModel} { 1830 var otherTaskID string 1831 // finalize-recovery-system points to create-recovery-system 1832 err = tsk.Get("recovery-system-setup-task", &otherTaskID) 1833 c.Assert(err, IsNil, Commentf("recovery system setup task ID missing in %s", tsk.Kind())) 1834 c.Assert(otherTaskID, Equals, tCreateRecovery.ID()) 1835 } 1836 }