github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/overlord/managers_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 overlord_test 21 22 // test the various managers and their operation together through overlord 23 24 import ( 25 "bytes" 26 "context" 27 "encoding/json" 28 "errors" 29 "fmt" 30 "io" 31 "io/ioutil" 32 "net/http" 33 "net/http/httptest" 34 "net/url" 35 "os" 36 "path/filepath" 37 "sort" 38 "strings" 39 "time" 40 41 . "gopkg.in/check.v1" 42 "gopkg.in/tomb.v2" 43 "gopkg.in/yaml.v2" 44 45 "github.com/snapcore/snapd/asserts" 46 "github.com/snapcore/snapd/asserts/assertstest" 47 "github.com/snapcore/snapd/asserts/sysdb" 48 "github.com/snapcore/snapd/boot" 49 "github.com/snapcore/snapd/boot/boottest" 50 "github.com/snapcore/snapd/bootloader" 51 "github.com/snapcore/snapd/bootloader/bootloadertest" 52 "github.com/snapcore/snapd/bootloader/grubenv" 53 "github.com/snapcore/snapd/client" 54 "github.com/snapcore/snapd/dirs" 55 "github.com/snapcore/snapd/gadget" 56 "github.com/snapcore/snapd/gadget/quantity" 57 "github.com/snapcore/snapd/interfaces" 58 "github.com/snapcore/snapd/logger" 59 "github.com/snapcore/snapd/osutil" 60 "github.com/snapcore/snapd/overlord" 61 "github.com/snapcore/snapd/overlord/assertstate" 62 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 63 "github.com/snapcore/snapd/overlord/auth" 64 "github.com/snapcore/snapd/overlord/configstate/config" 65 "github.com/snapcore/snapd/overlord/devicestate" 66 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 67 "github.com/snapcore/snapd/overlord/hookstate" 68 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 69 "github.com/snapcore/snapd/overlord/ifacestate" 70 "github.com/snapcore/snapd/overlord/servicestate" 71 "github.com/snapcore/snapd/overlord/servicestate/servicestatetest" 72 "github.com/snapcore/snapd/overlord/snapshotstate" 73 "github.com/snapcore/snapd/overlord/snapstate" 74 "github.com/snapcore/snapd/overlord/state" 75 "github.com/snapcore/snapd/release" 76 "github.com/snapcore/snapd/secboot" 77 "github.com/snapcore/snapd/seed" 78 "github.com/snapcore/snapd/seed/seedtest" 79 "github.com/snapcore/snapd/seed/seedwriter" 80 "github.com/snapcore/snapd/snap" 81 "github.com/snapcore/snapd/snap/naming" 82 "github.com/snapcore/snapd/snap/snapfile" 83 "github.com/snapcore/snapd/snap/snaptest" 84 "github.com/snapcore/snapd/store" 85 "github.com/snapcore/snapd/systemd" 86 "github.com/snapcore/snapd/systemd/systemdtest" 87 "github.com/snapcore/snapd/testutil" 88 "github.com/snapcore/snapd/wrappers" 89 ) 90 91 var ( 92 settleTimeout = testutil.HostScaledTimeout(45 * time.Second) 93 aggressiveSettleTimeout = testutil.HostScaledTimeout(50 * time.Millisecond) 94 connectRetryTimeout = testutil.HostScaledTimeout(70 * time.Millisecond) 95 ) 96 97 type automaticSnapshotCall struct { 98 InstanceName string 99 SnapConfig map[string]interface{} 100 Usernames []string 101 } 102 103 type baseMgrsSuite struct { 104 testutil.BaseTest 105 106 tempdir string 107 108 storeSigning *assertstest.StoreStack 109 brands *assertstest.SigningAccounts 110 111 devAcct *asserts.Account 112 113 serveIDtoName map[string]string 114 serveSnapPath map[string]string 115 serveRevision map[string]string 116 serveOldPaths map[string][]string 117 serveOldRevs map[string][]string 118 119 hijackServeSnap func(http.ResponseWriter) 120 121 checkDeviceAndAuthContext func(store.DeviceAndAuthContext) 122 expectedSerial string 123 expectedStore string 124 sessionMacaroon string 125 126 o *overlord.Overlord 127 128 failNextDownload string 129 130 automaticSnapshots []automaticSnapshotCall 131 132 logbuf *bytes.Buffer 133 } 134 135 var ( 136 _ = Suite(&mgrsSuite{}) 137 _ = Suite(&storeCtxSetupSuite{}) 138 ) 139 140 var ( 141 brandPrivKey, _ = assertstest.GenerateKey(752) 142 143 develPrivKey, _ = assertstest.GenerateKey(752) 144 145 deviceKey, _ = assertstest.GenerateKey(752) 146 ) 147 148 func verifyLastTasksetIsRerefresh(c *C, tts []*state.TaskSet) { 149 ts := tts[len(tts)-1] 150 c.Assert(ts.Tasks(), HasLen, 1) 151 c.Check(ts.Tasks()[0].Kind(), Equals, "check-rerefresh") 152 } 153 154 func (s *baseMgrsSuite) SetUpTest(c *C) { 155 s.BaseTest.SetUpTest(c) 156 157 s.tempdir = c.MkDir() 158 dirs.SetRootDir(s.tempdir) 159 s.AddCleanup(func() { dirs.SetRootDir("") }) 160 161 // needed for system key generation 162 s.AddCleanup(osutil.MockMountInfo("")) 163 164 err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) 165 c.Assert(err, IsNil) 166 167 // needed by hooks 168 s.AddCleanup(testutil.MockCommand(c, "snap", "").Restore) 169 170 restoreCheckFreeSpace := snapstate.MockOsutilCheckFreeSpace(func(string, uint64) error { return nil }) 171 s.AddCleanup(restoreCheckFreeSpace) 172 173 oldSetupInstallHook := snapstate.SetupInstallHook 174 oldSetupRemoveHook := snapstate.SetupRemoveHook 175 snapstate.SetupRemoveHook = hookstate.SetupRemoveHook 176 snapstate.SetupInstallHook = hookstate.SetupInstallHook 177 s.AddCleanup(func() { 178 snapstate.SetupRemoveHook = oldSetupRemoveHook 179 snapstate.SetupInstallHook = oldSetupInstallHook 180 }) 181 182 s.automaticSnapshots = nil 183 r := snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) { 184 s.automaticSnapshots = append(s.automaticSnapshots, automaticSnapshotCall{InstanceName: si.InstanceName(), SnapConfig: cfg, Usernames: usernames}) 185 return nil, nil 186 }) 187 s.AddCleanup(r) 188 189 s.AddCleanup(ifacestate.MockConnectRetryTimeout(connectRetryTimeout)) 190 191 os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1") 192 s.AddCleanup(func() { os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS") }) 193 194 // create a fake systemd environment 195 os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755) 196 197 r = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 198 if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, nil); out != nil { 199 return out, nil 200 } 201 return []byte("ActiveState=inactive\n"), nil 202 }) 203 s.AddCleanup(r) 204 205 s.storeSigning = assertstest.NewStoreStack("can0nical", nil) 206 s.brands = assertstest.NewSigningAccounts(s.storeSigning) 207 s.brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 208 "validation": "verified", 209 }) 210 s.AddCleanup(sysdb.InjectTrusted(s.storeSigning.Trusted)) 211 212 s.devAcct = assertstest.NewAccount(s.storeSigning, "devdevdev", map[string]interface{}{ 213 "account-id": "devdevdev", 214 }, "") 215 err = s.storeSigning.Add(s.devAcct) 216 c.Assert(err, IsNil) 217 218 s.serveIDtoName = make(map[string]string) 219 s.serveSnapPath = make(map[string]string) 220 s.serveRevision = make(map[string]string) 221 s.serveOldPaths = make(map[string][]string) 222 s.serveOldRevs = make(map[string][]string) 223 s.hijackServeSnap = nil 224 225 s.checkDeviceAndAuthContext = nil 226 s.expectedSerial = "" 227 s.expectedStore = "" 228 s.sessionMacaroon = "" 229 230 s.AddCleanup(ifacestate.MockSecurityBackends(nil)) 231 232 o, err := overlord.New(nil) 233 c.Assert(err, IsNil) 234 st := o.State() 235 st.Lock() 236 st.Set("seeded", true) 237 st.Unlock() 238 err = o.StartUp() 239 c.Assert(err, IsNil) 240 o.InterfaceManager().DisableUDevMonitor() 241 s.o = o 242 243 st.Lock() 244 defer st.Unlock() 245 // registered 246 err = assertstate.Add(st, sysdb.GenericClassicModel()) 247 c.Assert(err, IsNil) 248 devicestatetest.SetDevice(st, &auth.DeviceState{ 249 Brand: "generic", 250 Model: "generic-classic", 251 Serial: "serialserial", 252 }) 253 254 // add "core" snap declaration 255 headers := map[string]interface{}{ 256 "series": "16", 257 "snap-name": "core", 258 "publisher-id": "can0nical", 259 "timestamp": time.Now().Format(time.RFC3339), 260 } 261 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 262 err = assertstate.Add(st, s.storeSigning.StoreAccountKey("")) 263 c.Assert(err, IsNil) 264 a, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 265 c.Assert(err, IsNil) 266 err = assertstate.Add(st, a) 267 c.Assert(err, IsNil) 268 s.serveRevision["core"] = "1" 269 s.serveIDtoName[fakeSnapID("core")] = "core" 270 err = s.storeSigning.Add(a) 271 c.Assert(err, IsNil) 272 273 // add "snap1" snap declaration 274 headers = map[string]interface{}{ 275 "series": "16", 276 "snap-name": "snap1", 277 "publisher-id": "can0nical", 278 "timestamp": time.Now().Format(time.RFC3339), 279 } 280 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 281 a2, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 282 c.Assert(err, IsNil) 283 c.Assert(assertstate.Add(st, a2), IsNil) 284 c.Assert(s.storeSigning.Add(a2), IsNil) 285 286 // add "snap2" snap declaration 287 headers = map[string]interface{}{ 288 "series": "16", 289 "snap-name": "snap2", 290 "publisher-id": "can0nical", 291 "timestamp": time.Now().Format(time.RFC3339), 292 } 293 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 294 a3, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 295 c.Assert(err, IsNil) 296 c.Assert(assertstate.Add(st, a3), IsNil) 297 c.Assert(s.storeSigning.Add(a3), IsNil) 298 299 // add "some-snap" snap declaration 300 headers = map[string]interface{}{ 301 "series": "16", 302 "snap-name": "some-snap", 303 "publisher-id": "can0nical", 304 "timestamp": time.Now().Format(time.RFC3339), 305 } 306 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 307 a4, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 308 c.Assert(err, IsNil) 309 c.Assert(assertstate.Add(st, a4), IsNil) 310 c.Assert(s.storeSigning.Add(a4), IsNil) 311 312 // add "other-snap" snap declaration 313 headers = map[string]interface{}{ 314 "series": "16", 315 "snap-name": "other-snap", 316 "publisher-id": "can0nical", 317 "timestamp": time.Now().Format(time.RFC3339), 318 } 319 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 320 a5, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 321 c.Assert(err, IsNil) 322 c.Assert(assertstate.Add(st, a5), IsNil) 323 c.Assert(s.storeSigning.Add(a5), IsNil) 324 325 // add pc-kernel snap declaration 326 headers = map[string]interface{}{ 327 "series": "16", 328 "snap-name": "pc-kernel", 329 "publisher-id": "can0nical", 330 "timestamp": time.Now().Format(time.RFC3339), 331 } 332 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 333 a6, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 334 c.Assert(err, IsNil) 335 c.Assert(assertstate.Add(st, a6), IsNil) 336 c.Assert(s.storeSigning.Add(a6), IsNil) 337 338 // add pc snap declaration 339 headers = map[string]interface{}{ 340 "series": "16", 341 "snap-name": "pc", 342 "publisher-id": "can0nical", 343 "timestamp": time.Now().Format(time.RFC3339), 344 } 345 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 346 a7, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 347 c.Assert(err, IsNil) 348 c.Assert(assertstate.Add(st, a7), IsNil) 349 c.Assert(s.storeSigning.Add(a7), IsNil) 350 351 // add pi snap declaration 352 headers = map[string]interface{}{ 353 "series": "16", 354 "snap-name": "pi", 355 "publisher-id": "can0nical", 356 "timestamp": time.Now().Format(time.RFC3339), 357 } 358 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 359 a8, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 360 c.Assert(err, IsNil) 361 c.Assert(assertstate.Add(st, a8), IsNil) 362 c.Assert(s.storeSigning.Add(a8), IsNil) 363 364 // add pi-kernel snap declaration 365 headers = map[string]interface{}{ 366 "series": "16", 367 "snap-name": "pi-kernel", 368 "publisher-id": "can0nical", 369 "timestamp": time.Now().Format(time.RFC3339), 370 } 371 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 372 a9, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 373 c.Assert(err, IsNil) 374 c.Assert(assertstate.Add(st, a9), IsNil) 375 c.Assert(s.storeSigning.Add(a9), IsNil) 376 377 // add core18 snap declaration 378 headers = map[string]interface{}{ 379 "series": "16", 380 "snap-name": "core18", 381 "publisher-id": "can0nical", 382 "timestamp": time.Now().Format(time.RFC3339), 383 } 384 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 385 a10, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 386 c.Assert(err, IsNil) 387 c.Assert(assertstate.Add(st, a10), IsNil) 388 c.Assert(s.storeSigning.Add(a10), IsNil) 389 390 // add core20 snap declaration 391 headers = map[string]interface{}{ 392 "series": "16", 393 "snap-name": "core20", 394 "publisher-id": "can0nical", 395 "timestamp": time.Now().Format(time.RFC3339), 396 } 397 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 398 a11, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 399 c.Assert(err, IsNil) 400 c.Assert(assertstate.Add(st, a11), IsNil) 401 c.Assert(s.storeSigning.Add(a11), IsNil) 402 403 // add snapd snap declaration 404 headers = map[string]interface{}{ 405 "series": "16", 406 "snap-name": "snapd", 407 "publisher-id": "can0nical", 408 "timestamp": time.Now().Format(time.RFC3339), 409 } 410 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 411 a12, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 412 c.Assert(err, IsNil) 413 c.Assert(assertstate.Add(st, a12), IsNil) 414 c.Assert(s.storeSigning.Add(a12), IsNil) 415 416 // add core itself 417 snapstate.Set(st, "core", &snapstate.SnapState{ 418 Active: true, 419 Sequence: []*snap.SideInfo{ 420 {RealName: "core", SnapID: fakeSnapID("core"), Revision: snap.R(1)}, 421 }, 422 Current: snap.R(1), 423 SnapType: "os", 424 Flags: snapstate.Flags{ 425 Required: true, 426 }, 427 }) 428 429 // don't actually try to talk to the store on snapstate.Ensure 430 // needs doing after the call to devicestate.Manager (which happens in overlord.New) 431 snapstate.CanAutoRefresh = nil 432 433 st.Set("refresh-privacy-key", "privacy-key") 434 435 // For triggering errors 436 erroringHandler := func(task *state.Task, _ *tomb.Tomb) error { 437 return errors.New("error out") 438 } 439 s.o.TaskRunner().AddHandler("error-trigger", erroringHandler, nil) 440 441 // setup cloud-init as restricted so that tests by default don't run the 442 // full EnsureCloudInitRestricted logic in the devicestate mgr 443 snapdCloudInitRestrictedFile := filepath.Join(dirs.GlobalRootDir, "etc/cloud/cloud.cfg.d/zzzz_snapd.cfg") 444 err = os.MkdirAll(filepath.Dir(snapdCloudInitRestrictedFile), 0755) 445 c.Assert(err, IsNil) 446 err = ioutil.WriteFile(snapdCloudInitRestrictedFile, nil, 0644) 447 c.Assert(err, IsNil) 448 449 logbuf, restore := logger.MockLogger() 450 s.AddCleanup(restore) 451 s.logbuf = logbuf 452 } 453 454 func (s *baseMgrsSuite) makeSerialAssertionInState(c *C, st *state.State, brandID, model, serialN string) *asserts.Serial { 455 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 456 c.Assert(err, IsNil) 457 serial, err := s.brands.Signing(brandID).Sign(asserts.SerialType, map[string]interface{}{ 458 "brand-id": brandID, 459 "model": model, 460 "serial": serialN, 461 "device-key": string(encDevKey), 462 "device-key-sha3-384": deviceKey.PublicKey().ID(), 463 "timestamp": time.Now().Format(time.RFC3339), 464 }, nil, "") 465 c.Assert(err, IsNil) 466 err = assertstate.Add(st, serial) 467 c.Assert(err, IsNil) 468 return serial.(*asserts.Serial) 469 } 470 471 // XXX: We have some very similar code in hookstate/ctlcmd/is_connected_test.go 472 // should this be moved to overlord/snapstate/snapstatetest as a common 473 // helper 474 func (ms *baseMgrsSuite) mockInstalledSnapWithFiles(c *C, snapYaml string, files [][]string) *snap.Info { 475 return ms.mockInstalledSnapWithRevAndFiles(c, snapYaml, snap.R(1), files) 476 } 477 478 func (ms *baseMgrsSuite) mockInstalledSnapWithRevAndFiles(c *C, snapYaml string, rev snap.Revision, files [][]string) *snap.Info { 479 st := ms.o.State() 480 481 info := snaptest.MockSnapWithFiles(c, snapYaml, &snap.SideInfo{Revision: snap.R(1)}, files) 482 si := &snap.SideInfo{ 483 RealName: info.SnapName(), 484 SnapID: fakeSnapID(info.SnapName()), 485 Revision: info.Revision, 486 } 487 snapstate.Set(st, info.InstanceName(), &snapstate.SnapState{ 488 Active: true, 489 Sequence: []*snap.SideInfo{si}, 490 Current: info.Revision, 491 SnapType: string(info.Type()), 492 }) 493 return info 494 } 495 496 type mgrsSuite struct { 497 baseMgrsSuite 498 } 499 500 func makeTestSnapWithFiles(c *C, snapYamlContent string, files [][]string) string { 501 info, err := snap.InfoFromSnapYaml([]byte(snapYamlContent)) 502 c.Assert(err, IsNil) 503 504 for _, app := range info.Apps { 505 // files is a list of (filename, content) 506 files = append(files, []string{app.Command, ""}) 507 } 508 509 return snaptest.MakeTestSnapWithFiles(c, snapYamlContent, files) 510 } 511 512 func makeTestSnap(c *C, snapYamlContent string) string { 513 return makeTestSnapWithFiles(c, snapYamlContent, nil) 514 } 515 516 func (s *mgrsSuite) TestHappyLocalInstall(c *C) { 517 snapYamlContent := `name: foo 518 apps: 519 bar: 520 command: bin/bar 521 ` 522 snapPath := makeTestSnap(c, snapYamlContent+"version: 1.0") 523 524 st := s.o.State() 525 st.Lock() 526 defer st.Unlock() 527 528 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "foo"}, snapPath, "", "", snapstate.Flags{DevMode: true}) 529 c.Assert(err, IsNil) 530 chg := st.NewChange("install-snap", "...") 531 chg.AddAll(ts) 532 533 st.Unlock() 534 err = s.o.Settle(settleTimeout) 535 st.Lock() 536 c.Assert(err, IsNil) 537 538 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 539 540 snap, err := snapstate.CurrentInfo(st, "foo") 541 c.Assert(err, IsNil) 542 543 // ensure that the binary wrapper file got generated with the right 544 // name 545 binaryWrapper := filepath.Join(dirs.SnapBinariesDir, "foo.bar") 546 c.Assert(osutil.IsSymlink(binaryWrapper), Equals, true) 547 548 // data dirs 549 c.Assert(osutil.IsDirectory(snap.DataDir()), Equals, true) 550 c.Assert(osutil.IsDirectory(snap.CommonDataDir()), Equals, true) 551 552 // snap file and its mounting 553 554 // after install the snap file is in the right dir 555 c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "foo_x1.snap")), Equals, true) 556 557 // ensure the right unit is created 558 mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "foo/x1")) 559 c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s/foo/x1", dirs.StripRootDir(dirs.SnapMountDir))) 560 c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/foo_x1.snap") 561 } 562 563 func (s *mgrsSuite) TestLocalInstallUndo(c *C) { 564 snapYamlContent := `name: foo 565 apps: 566 bar: 567 command: bin/bar 568 hooks: 569 install: 570 configure: 571 ` 572 snapPath := makeTestSnap(c, snapYamlContent+"version: 1.0") 573 574 installHook := false 575 defer hookstate.MockRunHook(func(ctx *hookstate.Context, _ *tomb.Tomb) ([]byte, error) { 576 switch ctx.HookName() { 577 case "install": 578 installHook = true 579 _, _, err := ctlcmd.Run(ctx, []string{"set", "installed=true"}, 0) 580 c.Assert(err, IsNil) 581 return nil, nil 582 case "configure": 583 return nil, errors.New("configure failed") 584 } 585 return nil, nil 586 })() 587 588 st := s.o.State() 589 st.Lock() 590 defer st.Unlock() 591 592 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "foo"}, snapPath, "", "", snapstate.Flags{DevMode: true}) 593 c.Assert(err, IsNil) 594 chg := st.NewChange("install-snap", "...") 595 chg.AddAll(ts) 596 597 st.Unlock() 598 err = s.o.Settle(settleTimeout) 599 st.Lock() 600 c.Assert(err, IsNil) 601 602 c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("install-snap unexpectedly succeeded")) 603 604 // check undo statutes 605 for _, t := range chg.Tasks() { 606 which := t.Kind() 607 expectedStatus := state.UndoneStatus 608 switch t.Kind() { 609 case "prerequisites": 610 expectedStatus = state.DoneStatus 611 case "run-hook": 612 var hs hookstate.HookSetup 613 err := t.Get("hook-setup", &hs) 614 c.Assert(err, IsNil) 615 switch hs.Hook { 616 case "install": 617 expectedStatus = state.UndoneStatus 618 case "configure": 619 expectedStatus = state.ErrorStatus 620 case "check-health": 621 expectedStatus = state.HoldStatus 622 } 623 which += fmt.Sprintf("[%s]", hs.Hook) 624 } 625 c.Assert(t.Status(), Equals, expectedStatus, Commentf("%s", which)) 626 } 627 628 // install hooks was called 629 c.Check(installHook, Equals, true) 630 631 // nothing in snaps 632 all, err := snapstate.All(st) 633 c.Assert(err, IsNil) 634 c.Check(all, HasLen, 1) 635 _, ok := all["core"] 636 c.Check(ok, Equals, true) 637 638 // nothing in config 639 var config map[string]*json.RawMessage 640 err = st.Get("config", &config) 641 c.Assert(err, IsNil) 642 c.Check(config, HasLen, 1) 643 _, ok = config["core"] 644 c.Check(ok, Equals, true) 645 646 snapdirs, err := filepath.Glob(filepath.Join(dirs.SnapMountDir, "*")) 647 c.Assert(err, IsNil) 648 // just README and bin 649 c.Check(snapdirs, HasLen, 2) 650 for _, d := range snapdirs { 651 c.Check(filepath.Base(d), Not(Equals), "foo") 652 } 653 } 654 655 func (s *mgrsSuite) TestHappyRemove(c *C) { 656 oldEstimateSnapshotSize := snapstate.EstimateSnapshotSize 657 snapstate.EstimateSnapshotSize = func(st *state.State, instanceName string, users []string) (uint64, error) { 658 return 0, nil 659 } 660 defer func() { 661 snapstate.EstimateSnapshotSize = oldEstimateSnapshotSize 662 }() 663 664 st := s.o.State() 665 st.Lock() 666 defer st.Unlock() 667 668 snapYamlContent := `name: foo 669 apps: 670 bar: 671 command: bin/bar 672 ` 673 snapInfo := s.installLocalTestSnap(c, snapYamlContent+"version: 1.0") 674 675 // set config 676 tr := config.NewTransaction(st) 677 c.Assert(tr.Set("foo", "key", "value"), IsNil) 678 tr.Commit() 679 680 ts, err := snapstate.Remove(st, "foo", snap.R(0), nil) 681 c.Assert(err, IsNil) 682 chg := st.NewChange("remove-snap", "...") 683 chg.AddAll(ts) 684 685 st.Unlock() 686 err = s.o.Settle(settleTimeout) 687 st.Lock() 688 c.Assert(err, IsNil) 689 690 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("remove-snap change failed with: %v", chg.Err())) 691 692 // ensure that the binary wrapper file got removed 693 binaryWrapper := filepath.Join(dirs.SnapBinariesDir, "foo.bar") 694 c.Assert(osutil.FileExists(binaryWrapper), Equals, false) 695 696 // data dirs 697 c.Assert(osutil.FileExists(snapInfo.DataDir()), Equals, false) 698 c.Assert(osutil.FileExists(snapInfo.CommonDataDir()), Equals, false) 699 700 // snap file and its mount 701 c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "foo_x1.snap")), Equals, false) 702 mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "foo/x1")) 703 c.Assert(osutil.FileExists(mup), Equals, false) 704 705 // automatic snapshot was created 706 c.Assert(s.automaticSnapshots, DeepEquals, []automaticSnapshotCall{{"foo", map[string]interface{}{"key": "value"}, nil}}) 707 } 708 709 func (s *mgrsSuite) TestHappyRemoveWithQuotas(c *C) { 710 r := servicestate.MockSystemdVersion(248) 711 defer r() 712 713 st := s.o.State() 714 st.Lock() 715 defer st.Unlock() 716 717 snapYamlContent := `name: foo 718 apps: 719 bar: 720 command: bin/bar 721 daemon: simple 722 ` 723 s.installLocalTestSnap(c, snapYamlContent+"version: 1.0") 724 725 tr := config.NewTransaction(st) 726 c.Assert(tr.Set("core", "experimental.quota-groups", "true"), IsNil) 727 tr.Commit() 728 729 // put the snap in a quota group 730 err := servicestatetest.MockQuotaInState(st, "quota-grp", "", []string{"foo"}, quantity.SizeMiB) 731 c.Assert(err, IsNil) 732 733 ts, err := snapstate.Remove(st, "foo", snap.R(0), &snapstate.RemoveFlags{Purge: true}) 734 c.Assert(err, IsNil) 735 chg := st.NewChange("remove-snap", "...") 736 chg.AddAll(ts) 737 738 st.Unlock() 739 err = s.o.Settle(settleTimeout) 740 st.Lock() 741 c.Assert(err, IsNil) 742 743 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("remove-snap change failed with: %v", chg.Err())) 744 745 // ensure that the quota group no longer contains the snap we removed 746 grp, err := servicestate.AllQuotas(st) 747 c.Assert(grp, HasLen, 1) 748 c.Assert(grp["quota-grp"].Snaps, HasLen, 0) 749 c.Assert(err, IsNil) 750 } 751 752 func fakeSnapID(name string) string { 753 if id := naming.WellKnownSnapID(name); id != "" { 754 return id 755 } 756 return snaptest.AssertedSnapID(name) 757 } 758 759 const ( 760 snapV2 = `{ 761 "architectures": [ 762 "all" 763 ], 764 "download": { 765 "url": "@URL@", 766 "size": 123 767 }, 768 "epoch": @EPOCH@, 769 "type": "@TYPE@", 770 "name": "@NAME@", 771 "revision": @REVISION@, 772 "snap-id": "@SNAPID@", 773 "summary": "Foo", 774 "description": "this is a description", 775 "version": "@VERSION@", 776 "publisher": { 777 "id": "devdevdev", 778 "name": "bar" 779 }, 780 "media": [ 781 {"type": "icon", "url": "@ICON@"} 782 ] 783 }` 784 ) 785 786 var fooSnapID = fakeSnapID("foo") 787 788 func (s *baseMgrsSuite) prereqSnapAssertions(c *C, extraHeaders ...map[string]interface{}) *asserts.SnapDeclaration { 789 if len(extraHeaders) == 0 { 790 extraHeaders = []map[string]interface{}{{}} 791 } 792 var snapDecl *asserts.SnapDeclaration 793 for _, extraHeaders := range extraHeaders { 794 headers := map[string]interface{}{ 795 "series": "16", 796 "snap-name": "foo", 797 "publisher-id": "devdevdev", 798 "timestamp": time.Now().Format(time.RFC3339), 799 } 800 for h, v := range extraHeaders { 801 headers[h] = v 802 } 803 headers["snap-id"] = fakeSnapID(headers["snap-name"].(string)) 804 a, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 805 c.Assert(err, IsNil) 806 err = s.storeSigning.Add(a) 807 c.Assert(err, IsNil) 808 snapDecl = a.(*asserts.SnapDeclaration) 809 } 810 return snapDecl 811 } 812 813 func (s *baseMgrsSuite) makeStoreTestSnapWithFiles(c *C, snapYaml string, revno string, files [][]string) (path, digest string) { 814 info, err := snap.InfoFromSnapYaml([]byte(snapYaml)) 815 c.Assert(err, IsNil) 816 817 snapPath := makeTestSnapWithFiles(c, snapYaml, files) 818 819 snapDigest, size, err := asserts.SnapFileSHA3_384(snapPath) 820 c.Assert(err, IsNil) 821 822 s.makeStoreSnapRevision(c, info.SnapName(), revno, snapDigest, size) 823 824 return snapPath, snapDigest 825 } 826 827 func (s *baseMgrsSuite) makeStoreTestSnap(c *C, snapYaml string, revno string) (path, digest string) { 828 return s.makeStoreTestSnapWithFiles(c, snapYaml, revno, nil) 829 } 830 831 func (s *baseMgrsSuite) makeStoreSnapRevision(c *C, name, revno, digest string, size uint64) asserts.Assertion { 832 headers := map[string]interface{}{ 833 "snap-id": fakeSnapID(name), 834 "snap-sha3-384": digest, 835 "snap-size": fmt.Sprintf("%d", size), 836 "snap-revision": revno, 837 "developer-id": "devdevdev", 838 "timestamp": time.Now().Format(time.RFC3339), 839 } 840 snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "") 841 c.Assert(err, IsNil) 842 err = s.storeSigning.Add(snapRev) 843 c.Assert(err, IsNil) 844 return snapRev 845 } 846 847 func (s *baseMgrsSuite) pathFor(name, revno string) string { 848 if revno == s.serveRevision[name] { 849 return s.serveSnapPath[name] 850 } 851 for i, r := range s.serveOldRevs[name] { 852 if r == revno { 853 return s.serveOldPaths[name][i] 854 } 855 } 856 return "/not/found" 857 } 858 859 func (s *baseMgrsSuite) newestThatCanRead(name string, epoch snap.Epoch) (info *snap.Info, rev string) { 860 if s.serveSnapPath[name] == "" { 861 return nil, "" 862 } 863 idx := len(s.serveOldPaths[name]) 864 rev = s.serveRevision[name] 865 path := s.serveSnapPath[name] 866 for { 867 snapf, err := snapfile.Open(path) 868 if err != nil { 869 panic(err) 870 } 871 info, err := snap.ReadInfoFromSnapFile(snapf, nil) 872 if err != nil { 873 panic(err) 874 } 875 if info.Epoch.CanRead(epoch) { 876 return info, rev 877 } 878 idx-- 879 if idx < 0 { 880 return nil, "" 881 } 882 path = s.serveOldPaths[name][idx] 883 rev = s.serveOldRevs[name][idx] 884 } 885 } 886 887 func (s *baseMgrsSuite) mockStore(c *C) *httptest.Server { 888 var baseURL *url.URL 889 fillHit := func(hitTemplate, revno string, info *snap.Info) string { 890 epochBuf, err := json.Marshal(info.Epoch) 891 if err != nil { 892 panic(err) 893 } 894 name := info.SnapName() 895 896 hit := strings.Replace(hitTemplate, "@URL@", baseURL.String()+"/api/v1/snaps/download/"+name+"/"+revno, -1) 897 hit = strings.Replace(hit, "@NAME@", name, -1) 898 hit = strings.Replace(hit, "@SNAPID@", fakeSnapID(name), -1) 899 hit = strings.Replace(hit, "@ICON@", baseURL.String()+"/icon", -1) 900 hit = strings.Replace(hit, "@VERSION@", info.Version, -1) 901 hit = strings.Replace(hit, "@REVISION@", revno, -1) 902 hit = strings.Replace(hit, `@TYPE@`, string(info.Type()), -1) 903 hit = strings.Replace(hit, `@EPOCH@`, string(epochBuf), -1) 904 return hit 905 } 906 907 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 908 // all URLS are /api/v1/snaps/... or /v2/snaps/ or /v2/assertions/... so 909 // check the url is sane and discard the common prefix 910 // to simplify indexing into the comps slice. 911 comps := strings.Split(r.URL.Path, "/") 912 if len(comps) < 2 { 913 panic("unexpected url path: " + r.URL.Path) 914 } 915 if comps[1] == "api" { //v1 916 if len(comps) <= 4 { 917 panic("unexpected url path: " + r.URL.Path) 918 } 919 comps = comps[4:] 920 if comps[0] == "auth" { 921 comps[0] = "auth:" + comps[1] 922 } 923 } else { // v2 924 if len(comps) <= 3 { 925 panic("unexpected url path: " + r.URL.Path) 926 } 927 if comps[2] == "assertions" { 928 // preserve "assertions" component 929 comps = comps[2:] 930 } else { 931 // drop common "snap" component 932 comps = comps[3:] 933 } 934 comps[0] = "v2:" + comps[0] 935 } 936 937 switch comps[0] { 938 case "auth:nonces": 939 w.Write([]byte(`{"nonce": "NONCE"}`)) 940 return 941 case "auth:sessions": 942 // quick sanity check 943 reqBody, err := ioutil.ReadAll(r.Body) 944 c.Check(err, IsNil) 945 c.Check(bytes.Contains(reqBody, []byte("nonce: NONCE")), Equals, true) 946 c.Check(bytes.Contains(reqBody, []byte(fmt.Sprintf("serial: %s", s.expectedSerial))), Equals, true) 947 c.Check(bytes.Contains(reqBody, []byte(fmt.Sprintf("store: %s", s.expectedStore))), Equals, true) 948 949 c.Check(s.sessionMacaroon, Not(Equals), "") 950 w.WriteHeader(200) 951 w.Write([]byte(fmt.Sprintf(`{"macaroon": "%s"}`, s.sessionMacaroon))) 952 return 953 case "v2:assertions": 954 ref := &asserts.Ref{ 955 Type: asserts.Type(comps[1]), 956 PrimaryKey: comps[2:], 957 } 958 a, err := ref.Resolve(s.storeSigning.Find) 959 if asserts.IsNotFound(err) { 960 w.Header().Set("Content-Type", "application/problem+json") 961 w.WriteHeader(404) 962 w.Write([]byte(`{"error-list":[{"code":"not-found","message":"..."}]}`)) 963 return 964 } 965 if err != nil { 966 panic(err) 967 } 968 w.Header().Set("Content-Type", asserts.MediaType) 969 w.WriteHeader(200) 970 w.Write(asserts.Encode(a)) 971 return 972 case "download": 973 if s.sessionMacaroon != "" { 974 // FIXME: download is still using the old headers! 975 c.Check(r.Header.Get("X-Device-Authorization"), Equals, fmt.Sprintf(`Macaroon root="%s"`, s.sessionMacaroon)) 976 } 977 if s.failNextDownload == comps[1] { 978 s.failNextDownload = "" 979 w.WriteHeader(418) 980 return 981 } 982 if s.hijackServeSnap != nil { 983 s.hijackServeSnap(w) 984 return 985 } 986 snapR, err := os.Open(s.pathFor(comps[1], comps[2])) 987 if err != nil { 988 panic(err) 989 } 990 io.Copy(w, snapR) 991 case "v2:refresh": 992 if s.sessionMacaroon != "" { 993 c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, fmt.Sprintf(`Macaroon root="%s"`, s.sessionMacaroon)) 994 } 995 dec := json.NewDecoder(r.Body) 996 var input struct { 997 Actions []struct { 998 Action string `json:"action"` 999 SnapID string `json:"snap-id"` 1000 Name string `json:"name"` 1001 InstanceKey string `json:"instance-key"` 1002 Epoch snap.Epoch `json:"epoch"` 1003 // assertions 1004 Key string `json:"key"` 1005 Assertions []struct { 1006 Type string `json:"type"` 1007 PrimaryKey []string `json:"primary-key"` 1008 IfNewerThan *int `json:"if-newer-than"` 1009 } 1010 } `json:"actions"` 1011 Context []struct { 1012 SnapID string `json:"snap-id"` 1013 Epoch snap.Epoch `json:"epoch"` 1014 } `json:"context"` 1015 } 1016 if err := dec.Decode(&input); err != nil { 1017 panic(err) 1018 } 1019 id2epoch := make(map[string]snap.Epoch, len(input.Context)) 1020 for _, s := range input.Context { 1021 id2epoch[s.SnapID] = s.Epoch 1022 } 1023 type resultJSON struct { 1024 Result string `json:"result"` 1025 SnapID string `json:"snap-id"` 1026 Name string `json:"name"` 1027 Snap json.RawMessage `json:"snap"` 1028 InstanceKey string `json:"instance-key"` 1029 // For assertions 1030 Key string `json:"key"` 1031 AssertionURLs []string `json:"assertion-stream-urls"` 1032 } 1033 var results []resultJSON 1034 for _, a := range input.Actions { 1035 if a.Action == "fetch-assertions" { 1036 urls := []string{} 1037 for _, ar := range a.Assertions { 1038 ref := &asserts.Ref{ 1039 Type: asserts.Type(ar.Type), 1040 PrimaryKey: ar.PrimaryKey, 1041 } 1042 _, err := ref.Resolve(s.storeSigning.Find) 1043 if err != nil { 1044 panic("missing assertions not supported") 1045 } 1046 urls = append(urls, fmt.Sprintf("%s/v2/assertions/%s", baseURL.String(), ref.Unique())) 1047 1048 } 1049 results = append(results, resultJSON{ 1050 Result: "fetch-assertions", 1051 Key: a.Key, 1052 AssertionURLs: urls, 1053 }) 1054 continue 1055 } 1056 name := s.serveIDtoName[a.SnapID] 1057 epoch := id2epoch[a.SnapID] 1058 if a.Action == "install" { 1059 name = a.Name 1060 epoch = a.Epoch 1061 } 1062 1063 info, revno := s.newestThatCanRead(name, epoch) 1064 if info == nil { 1065 // no match 1066 continue 1067 } 1068 results = append(results, resultJSON{ 1069 Result: a.Action, 1070 SnapID: a.SnapID, 1071 InstanceKey: a.InstanceKey, 1072 Name: name, 1073 Snap: json.RawMessage(fillHit(snapV2, revno, info)), 1074 }) 1075 } 1076 w.WriteHeader(200) 1077 output, err := json.Marshal(map[string]interface{}{ 1078 "results": results, 1079 }) 1080 if err != nil { 1081 panic(err) 1082 } 1083 w.Write(output) 1084 1085 default: 1086 panic("unexpected url path: " + r.URL.Path) 1087 } 1088 })) 1089 c.Assert(mockServer, NotNil) 1090 1091 baseURL, _ = url.Parse(mockServer.URL) 1092 storeCfg := store.Config{ 1093 StoreBaseURL: baseURL, 1094 } 1095 1096 mStore := store.New(&storeCfg, nil) 1097 st := s.o.State() 1098 st.Lock() 1099 snapstate.ReplaceStore(s.o.State(), mStore) 1100 st.Unlock() 1101 1102 // this will be used by remodeling cases 1103 storeNew := func(cfg *store.Config, dac store.DeviceAndAuthContext) *store.Store { 1104 cfg.StoreBaseURL = baseURL 1105 if s.checkDeviceAndAuthContext != nil { 1106 s.checkDeviceAndAuthContext(dac) 1107 } 1108 return store.New(cfg, dac) 1109 } 1110 1111 s.AddCleanup(overlord.MockStoreNew(storeNew)) 1112 1113 return mockServer 1114 } 1115 1116 // serveSnap starts serving the snap at snapPath, moving the current 1117 // one onto the list of previous ones if already set. 1118 func (s *baseMgrsSuite) serveSnap(snapPath, revno string) { 1119 snapf, err := snapfile.Open(snapPath) 1120 if err != nil { 1121 panic(err) 1122 } 1123 info, err := snap.ReadInfoFromSnapFile(snapf, nil) 1124 if err != nil { 1125 panic(err) 1126 } 1127 name := info.SnapName() 1128 s.serveIDtoName[fakeSnapID(name)] = name 1129 1130 if oldPath := s.serveSnapPath[name]; oldPath != "" { 1131 oldRev := s.serveRevision[name] 1132 if oldRev == "" { 1133 panic("old path set but not old revision") 1134 } 1135 s.serveOldPaths[name] = append(s.serveOldPaths[name], oldPath) 1136 s.serveOldRevs[name] = append(s.serveOldRevs[name], oldRev) 1137 } 1138 s.serveSnapPath[name] = snapPath 1139 s.serveRevision[name] = revno 1140 } 1141 1142 func (s *mgrsSuite) TestHappyRemoteInstallAndUpgradeSvc(c *C) { 1143 // test install through store and update, plus some mechanics 1144 // of update 1145 // TODO: ok to split if it gets too messy to maintain 1146 1147 s.prereqSnapAssertions(c) 1148 1149 snapYamlContent := `name: foo 1150 version: @VERSION@ 1151 apps: 1152 bar: 1153 command: bin/bar 1154 svc: 1155 command: svc 1156 daemon: forking 1157 ` 1158 1159 ver := "1.0" 1160 revno := "42" 1161 snapPath, digest := s.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) 1162 s.serveSnap(snapPath, revno) 1163 1164 mockServer := s.mockStore(c) 1165 defer mockServer.Close() 1166 1167 st := s.o.State() 1168 st.Lock() 1169 defer st.Unlock() 1170 1171 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 1172 c.Assert(err, IsNil) 1173 chg := st.NewChange("install-snap", "...") 1174 chg.AddAll(ts) 1175 1176 st.Unlock() 1177 err = s.o.Settle(settleTimeout) 1178 st.Lock() 1179 c.Assert(err, IsNil) 1180 1181 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1182 1183 info, err := snapstate.CurrentInfo(st, "foo") 1184 c.Assert(err, IsNil) 1185 1186 c.Check(info.Revision, Equals, snap.R(42)) 1187 c.Check(info.SnapID, Equals, fooSnapID) 1188 c.Check(info.Version, Equals, "1.0") 1189 c.Check(info.Summary(), Equals, "Foo") 1190 c.Check(info.Description(), Equals, "this is a description") 1191 c.Assert(osutil.FileExists(info.MountFile()), Equals, true) 1192 1193 pubAcct, err := assertstate.Publisher(st, info.SnapID) 1194 c.Assert(err, IsNil) 1195 c.Check(pubAcct.AccountID(), Equals, "devdevdev") 1196 c.Check(pubAcct.Username(), Equals, "devdevdev") 1197 1198 snapRev42, err := assertstate.DB(st).Find(asserts.SnapRevisionType, map[string]string{ 1199 "snap-sha3-384": digest, 1200 }) 1201 c.Assert(err, IsNil) 1202 c.Check(snapRev42.(*asserts.SnapRevision).SnapID(), Equals, fooSnapID) 1203 c.Check(snapRev42.(*asserts.SnapRevision).SnapRevision(), Equals, 42) 1204 1205 // check service was setup properly 1206 svcFile := filepath.Join(dirs.SnapServicesDir, "snap.foo.svc.service") 1207 c.Assert(osutil.FileExists(svcFile), Equals, true) 1208 stat, err := os.Stat(svcFile) 1209 c.Assert(err, IsNil) 1210 // should _not_ be executable 1211 c.Assert(stat.Mode().String(), Equals, "-rw-r--r--") 1212 1213 // Refresh 1214 1215 ver = "2.0" 1216 revno = "50" 1217 snapPath, digest = s.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) 1218 s.serveSnap(snapPath, revno) 1219 1220 ts, err = snapstate.Update(st, "foo", nil, 0, snapstate.Flags{}) 1221 c.Assert(err, IsNil) 1222 chg = st.NewChange("upgrade-snap", "...") 1223 chg.AddAll(ts) 1224 1225 st.Unlock() 1226 err = s.o.Settle(settleTimeout) 1227 st.Lock() 1228 c.Assert(err, IsNil) 1229 1230 c.Assert(chg.Err(), IsNil) 1231 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 1232 1233 info, err = snapstate.CurrentInfo(st, "foo") 1234 c.Assert(err, IsNil) 1235 1236 c.Check(info.Revision, Equals, snap.R(50)) 1237 c.Check(info.SnapID, Equals, fooSnapID) 1238 c.Check(info.Version, Equals, "2.0") 1239 1240 snapRev50, err := assertstate.DB(st).Find(asserts.SnapRevisionType, map[string]string{ 1241 "snap-sha3-384": digest, 1242 }) 1243 c.Assert(err, IsNil) 1244 c.Check(snapRev50.(*asserts.SnapRevision).SnapID(), Equals, fooSnapID) 1245 c.Check(snapRev50.(*asserts.SnapRevision).SnapRevision(), Equals, 50) 1246 1247 // check updated wrapper 1248 symlinkTarget, err := os.Readlink(info.Apps["bar"].WrapperPath()) 1249 c.Assert(err, IsNil) 1250 c.Assert(symlinkTarget, Equals, "/usr/bin/snap") 1251 1252 // check updated service file 1253 c.Assert(svcFile, testutil.FileContains, "/var/snap/foo/"+revno) 1254 } 1255 1256 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateWithEpochBump(c *C) { 1257 // test install through store and update, where there's an epoch bump in the upgrade 1258 // this does less checks on the details of install/update than TestHappyRemoteInstallAndUpgradeSvc 1259 1260 s.prereqSnapAssertions(c) 1261 1262 snapPath, _ := s.makeStoreTestSnap(c, "{name: foo, version: 0}", "1") 1263 s.serveSnap(snapPath, "1") 1264 1265 mockServer := s.mockStore(c) 1266 defer mockServer.Close() 1267 1268 st := s.o.State() 1269 st.Lock() 1270 defer st.Unlock() 1271 1272 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 1273 c.Assert(err, IsNil) 1274 chg := st.NewChange("install-snap", "...") 1275 chg.AddAll(ts) 1276 1277 st.Unlock() 1278 err = s.o.Settle(settleTimeout) 1279 st.Lock() 1280 c.Assert(err, IsNil) 1281 1282 // confirm it worked 1283 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1284 1285 // sanity checks 1286 info, err := snapstate.CurrentInfo(st, "foo") 1287 c.Assert(err, IsNil) 1288 c.Assert(info.Revision, Equals, snap.R(1)) 1289 c.Assert(info.SnapID, Equals, fooSnapID) 1290 c.Assert(info.Epoch.String(), Equals, "0") 1291 1292 // now add some more snaps 1293 for i, epoch := range []string{"1*", "2*", "3*"} { 1294 revno := fmt.Sprint(i + 2) 1295 snapPath, _ := s.makeStoreTestSnap(c, "{name: foo, version: 0, epoch: "+epoch+"}", revno) 1296 s.serveSnap(snapPath, revno) 1297 } 1298 1299 // refresh 1300 1301 ts, err = snapstate.Update(st, "foo", nil, 0, snapstate.Flags{}) 1302 c.Assert(err, IsNil) 1303 chg = st.NewChange("upgrade-snap", "...") 1304 chg.AddAll(ts) 1305 1306 st.Unlock() 1307 err = s.o.Settle(settleTimeout) 1308 st.Lock() 1309 c.Assert(err, IsNil) 1310 1311 c.Assert(chg.Err(), IsNil) 1312 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 1313 1314 info, err = snapstate.CurrentInfo(st, "foo") 1315 c.Assert(err, IsNil) 1316 1317 c.Check(info.Revision, Equals, snap.R(4)) 1318 c.Check(info.SnapID, Equals, fooSnapID) 1319 c.Check(info.Epoch.String(), Equals, "3*") 1320 } 1321 1322 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateWithPostHocEpochBump(c *C) { 1323 // test install through store and update, where there is an epoch 1324 // bump in the upgrade that comes in after the initial update is 1325 // computed. 1326 1327 // this is mostly checking the same as TestHappyRemoteInstallAndUpdateWithEpochBump 1328 // but serves as a sanity check for the Without case that follows 1329 // (these two together serve as a test for the refresh filtering) 1330 s.testHappyRemoteInstallAndUpdateWithMaybeEpochBump(c, true) 1331 } 1332 1333 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateWithoutEpochBump(c *C) { 1334 // test install through store and update, where there _isn't_ an epoch bump in the upgrade 1335 // note that there _are_ refreshes available after the refresh, 1336 // but they're not an epoch bump so they're ignored 1337 s.testHappyRemoteInstallAndUpdateWithMaybeEpochBump(c, false) 1338 } 1339 1340 func (s *mgrsSuite) testHappyRemoteInstallAndUpdateWithMaybeEpochBump(c *C, doBump bool) { 1341 s.prereqSnapAssertions(c) 1342 1343 snapPath, _ := s.makeStoreTestSnap(c, "{name: foo, version: 1}", "1") 1344 s.serveSnap(snapPath, "1") 1345 1346 mockServer := s.mockStore(c) 1347 defer mockServer.Close() 1348 1349 st := s.o.State() 1350 st.Lock() 1351 defer st.Unlock() 1352 1353 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 1354 c.Assert(err, IsNil) 1355 chg := st.NewChange("install-snap", "...") 1356 chg.AddAll(ts) 1357 1358 st.Unlock() 1359 err = s.o.Settle(settleTimeout) 1360 st.Lock() 1361 c.Assert(err, IsNil) 1362 1363 // confirm it worked 1364 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1365 1366 // sanity checks 1367 info, err := snapstate.CurrentInfo(st, "foo") 1368 c.Assert(err, IsNil) 1369 c.Assert(info.Revision, Equals, snap.R(1)) 1370 c.Assert(info.SnapID, Equals, fooSnapID) 1371 c.Assert(info.Epoch.String(), Equals, "0") 1372 1373 // add a new revision 1374 snapPath, _ = s.makeStoreTestSnap(c, "{name: foo, version: 2}", "2") 1375 s.serveSnap(snapPath, "2") 1376 1377 // refresh 1378 1379 ts, err = snapstate.Update(st, "foo", nil, 0, snapstate.Flags{}) 1380 c.Assert(err, IsNil) 1381 chg = st.NewChange("upgrade-snap", "...") 1382 chg.AddAll(ts) 1383 1384 // add another new revision, after the update was computed (maybe with an epoch bump) 1385 if doBump { 1386 snapPath, _ = s.makeStoreTestSnap(c, "{name: foo, version: 3, epoch: 1*}", "3") 1387 } else { 1388 snapPath, _ = s.makeStoreTestSnap(c, "{name: foo, version: 3}", "3") 1389 } 1390 s.serveSnap(snapPath, "3") 1391 1392 st.Unlock() 1393 err = s.o.Settle(settleTimeout) 1394 st.Lock() 1395 c.Assert(err, IsNil) 1396 1397 c.Assert(chg.Err(), IsNil) 1398 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 1399 1400 info, err = snapstate.CurrentInfo(st, "foo") 1401 c.Assert(err, IsNil) 1402 1403 if doBump { 1404 // if the epoch bumped, then we should've re-refreshed 1405 c.Check(info.Revision, Equals, snap.R(3)) 1406 c.Check(info.SnapID, Equals, fooSnapID) 1407 c.Check(info.Epoch.String(), Equals, "1*") 1408 } else { 1409 // if the epoch did not bump, then we should _not_ have re-refreshed 1410 c.Check(info.Revision, Equals, snap.R(2)) 1411 c.Check(info.SnapID, Equals, fooSnapID) 1412 c.Check(info.Epoch.String(), Equals, "0") 1413 } 1414 } 1415 1416 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateManyWithEpochBump(c *C) { 1417 // test install through store and update many, where there's an epoch bump in the upgrade 1418 // this does less checks on the details of install/update than TestHappyRemoteInstallAndUpgradeSvc 1419 1420 snapNames := []string{"aaaa", "bbbb", "cccc"} 1421 for _, name := range snapNames { 1422 s.prereqSnapAssertions(c, map[string]interface{}{"snap-name": name}) 1423 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 0}", name), "1") 1424 s.serveSnap(snapPath, "1") 1425 } 1426 1427 mockServer := s.mockStore(c) 1428 defer mockServer.Close() 1429 1430 st := s.o.State() 1431 st.Lock() 1432 defer st.Unlock() 1433 1434 affected, tasksets, err := snapstate.InstallMany(st, snapNames, 0) 1435 c.Assert(err, IsNil) 1436 sort.Strings(affected) 1437 c.Check(affected, DeepEquals, snapNames) 1438 chg := st.NewChange("install-snaps", "...") 1439 for _, taskset := range tasksets { 1440 chg.AddAll(taskset) 1441 } 1442 1443 st.Unlock() 1444 err = s.o.Settle(settleTimeout) 1445 st.Lock() 1446 c.Assert(err, IsNil) 1447 1448 // confirm it worked 1449 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1450 1451 // sanity checks 1452 for _, name := range snapNames { 1453 info, err := snapstate.CurrentInfo(st, name) 1454 c.Assert(err, IsNil) 1455 c.Assert(info.Revision, Equals, snap.R(1)) 1456 c.Assert(info.SnapID, Equals, fakeSnapID(name)) 1457 c.Assert(info.Epoch.String(), Equals, "0") 1458 } 1459 1460 // now add some more snap revisions with increasing epochs 1461 for _, name := range snapNames { 1462 for i, epoch := range []string{"1*", "2*", "3*"} { 1463 revno := fmt.Sprint(i + 2) 1464 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 0, epoch: %s}", name, epoch), revno) 1465 s.serveSnap(snapPath, revno) 1466 } 1467 } 1468 1469 // refresh 1470 1471 affected, tasksets, err = snapstate.UpdateMany(context.TODO(), st, nil, 0, &snapstate.Flags{}) 1472 c.Assert(err, IsNil) 1473 sort.Strings(affected) 1474 c.Check(affected, DeepEquals, snapNames) 1475 chg = st.NewChange("upgrade-snaps", "...") 1476 for _, taskset := range tasksets { 1477 chg.AddAll(taskset) 1478 } 1479 1480 st.Unlock() 1481 err = s.o.Settle(settleTimeout) 1482 st.Lock() 1483 c.Assert(err, IsNil) 1484 1485 c.Assert(chg.Err(), IsNil) 1486 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 1487 1488 for _, name := range snapNames { 1489 info, err := snapstate.CurrentInfo(st, name) 1490 c.Assert(err, IsNil) 1491 1492 c.Check(info.Revision, Equals, snap.R(4)) 1493 c.Check(info.SnapID, Equals, fakeSnapID(name)) 1494 c.Check(info.Epoch.String(), Equals, "3*") 1495 } 1496 } 1497 1498 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateManyWithEpochBumpAndOneFailing(c *C) { 1499 // test install through store and update, where there's an epoch bump in the upgrade and one of them fails 1500 1501 snapNames := []string{"aaaa", "bbbb", "cccc"} 1502 for _, name := range snapNames { 1503 s.prereqSnapAssertions(c, map[string]interface{}{"snap-name": name}) 1504 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 0}", name), "1") 1505 s.serveSnap(snapPath, "1") 1506 } 1507 1508 mockServer := s.mockStore(c) 1509 defer mockServer.Close() 1510 1511 st := s.o.State() 1512 st.Lock() 1513 defer st.Unlock() 1514 1515 affected, tasksets, err := snapstate.InstallMany(st, snapNames, 0) 1516 c.Assert(err, IsNil) 1517 sort.Strings(affected) 1518 c.Check(affected, DeepEquals, snapNames) 1519 chg := st.NewChange("install-snaps", "...") 1520 for _, taskset := range tasksets { 1521 chg.AddAll(taskset) 1522 } 1523 1524 st.Unlock() 1525 err = s.o.Settle(settleTimeout) 1526 st.Lock() 1527 c.Assert(err, IsNil) 1528 1529 // confirm it worked 1530 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1531 1532 // sanity checks 1533 for _, name := range snapNames { 1534 info, err := snapstate.CurrentInfo(st, name) 1535 c.Assert(err, IsNil) 1536 c.Assert(info.Revision, Equals, snap.R(1)) 1537 c.Assert(info.SnapID, Equals, fakeSnapID(name)) 1538 c.Assert(info.Epoch.String(), Equals, "0") 1539 } 1540 1541 // now add some more snap revisions with increasing epochs 1542 for _, name := range snapNames { 1543 for i, epoch := range []string{"1*", "2*", "3*"} { 1544 revno := fmt.Sprint(i + 2) 1545 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 0, epoch: %s}", name, epoch), revno) 1546 s.serveSnap(snapPath, revno) 1547 } 1548 } 1549 1550 // refresh 1551 affected, tasksets, err = snapstate.UpdateMany(context.TODO(), st, nil, 0, &snapstate.Flags{}) 1552 c.Assert(err, IsNil) 1553 sort.Strings(affected) 1554 c.Check(affected, DeepEquals, snapNames) 1555 chg = st.NewChange("upgrade-snaps", "...") 1556 for _, taskset := range tasksets { 1557 chg.AddAll(taskset) 1558 } 1559 1560 st.Unlock() 1561 // the download for the refresh above will be performed below, during 'settle'. 1562 // fail the refresh of cccc by failing its download 1563 s.failNextDownload = "cccc" 1564 err = s.o.Settle(settleTimeout) 1565 st.Lock() 1566 c.Assert(err, IsNil) 1567 1568 c.Assert(chg.Err(), NotNil) 1569 c.Assert(chg.Status(), Equals, state.ErrorStatus) 1570 1571 for _, name := range snapNames { 1572 comment := Commentf("%q", name) 1573 info, err := snapstate.CurrentInfo(st, name) 1574 c.Assert(err, IsNil, comment) 1575 1576 if name == "cccc" { 1577 // the failed one: still on rev 1 (epoch 0) 1578 c.Assert(info.Revision, Equals, snap.R(1)) 1579 c.Assert(info.SnapID, Equals, fakeSnapID(name)) 1580 c.Assert(info.Epoch.String(), Equals, "0") 1581 } else { 1582 // the non-failed ones: refreshed to rev 4 (epoch 3*) 1583 c.Check(info.Revision, Equals, snap.R(4), comment) 1584 c.Check(info.SnapID, Equals, fakeSnapID(name), comment) 1585 c.Check(info.Epoch.String(), Equals, "3*", comment) 1586 } 1587 } 1588 } 1589 1590 func (s *mgrsSuite) TestHappyLocalInstallWithStoreMetadata(c *C) { 1591 snapDecl := s.prereqSnapAssertions(c) 1592 1593 snapYamlContent := `name: foo 1594 apps: 1595 bar: 1596 command: bin/bar 1597 ` 1598 snapPath := makeTestSnap(c, snapYamlContent+"version: 1.5") 1599 1600 si := &snap.SideInfo{ 1601 RealName: "foo", 1602 SnapID: fooSnapID, 1603 Revision: snap.R(55), 1604 } 1605 1606 st := s.o.State() 1607 st.Lock() 1608 defer st.Unlock() 1609 1610 // have the snap-declaration in the system db 1611 err := assertstate.Add(st, s.devAcct) 1612 c.Assert(err, IsNil) 1613 err = assertstate.Add(st, snapDecl) 1614 c.Assert(err, IsNil) 1615 1616 ts, _, err := snapstate.InstallPath(st, si, snapPath, "", "", snapstate.Flags{DevMode: true}) 1617 c.Assert(err, IsNil) 1618 chg := st.NewChange("install-snap", "...") 1619 chg.AddAll(ts) 1620 1621 st.Unlock() 1622 err = s.o.Settle(settleTimeout) 1623 st.Lock() 1624 c.Assert(err, IsNil) 1625 1626 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1627 1628 info, err := snapstate.CurrentInfo(st, "foo") 1629 c.Assert(err, IsNil) 1630 c.Check(info.Revision, Equals, snap.R(55)) 1631 c.Check(info.SnapID, Equals, fooSnapID) 1632 c.Check(info.Version, Equals, "1.5") 1633 1634 // ensure that the binary wrapper file got generated with the right 1635 // name 1636 binaryWrapper := filepath.Join(dirs.SnapBinariesDir, "foo.bar") 1637 c.Assert(osutil.IsSymlink(binaryWrapper), Equals, true) 1638 1639 // data dirs 1640 c.Assert(osutil.IsDirectory(info.DataDir()), Equals, true) 1641 c.Assert(osutil.IsDirectory(info.CommonDataDir()), Equals, true) 1642 1643 // snap file and its mounting 1644 1645 // after install the snap file is in the right dir 1646 c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "foo_55.snap")), Equals, true) 1647 1648 // ensure the right unit is created 1649 mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "foo/55")) 1650 c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s/foo/55", dirs.StripRootDir(dirs.SnapMountDir))) 1651 c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/foo_55.snap") 1652 } 1653 1654 func (s *mgrsSuite) TestParallelInstanceLocalInstallSnapNameMismatch(c *C) { 1655 snapDecl := s.prereqSnapAssertions(c) 1656 1657 snapYamlContent := `name: foo 1658 apps: 1659 bar: 1660 command: bin/bar 1661 ` 1662 snapPath := makeTestSnap(c, snapYamlContent+"version: 1.5") 1663 1664 si := &snap.SideInfo{ 1665 RealName: "foo", 1666 SnapID: fooSnapID, 1667 Revision: snap.R(55), 1668 } 1669 1670 st := s.o.State() 1671 st.Lock() 1672 defer st.Unlock() 1673 1674 // have the snap-declaration in the system db 1675 err := assertstate.Add(st, s.devAcct) 1676 c.Assert(err, IsNil) 1677 err = assertstate.Add(st, snapDecl) 1678 c.Assert(err, IsNil) 1679 1680 _, _, err = snapstate.InstallPath(st, si, snapPath, "bar_instance", "", snapstate.Flags{DevMode: true}) 1681 c.Assert(err, ErrorMatches, `cannot install snap "bar_instance", the name does not match the metadata "foo"`) 1682 } 1683 1684 func (s *mgrsSuite) TestParallelInstanceLocalInstallInvalidInstanceName(c *C) { 1685 snapDecl := s.prereqSnapAssertions(c) 1686 1687 snapYamlContent := `name: foo 1688 apps: 1689 bar: 1690 command: bin/bar 1691 ` 1692 snapPath := makeTestSnap(c, snapYamlContent+"version: 1.5") 1693 1694 si := &snap.SideInfo{ 1695 RealName: "foo", 1696 SnapID: fooSnapID, 1697 Revision: snap.R(55), 1698 } 1699 1700 st := s.o.State() 1701 st.Lock() 1702 defer st.Unlock() 1703 1704 // have the snap-declaration in the system db 1705 err := assertstate.Add(st, s.devAcct) 1706 c.Assert(err, IsNil) 1707 err = assertstate.Add(st, snapDecl) 1708 c.Assert(err, IsNil) 1709 1710 _, _, err = snapstate.InstallPath(st, si, snapPath, "bar_invalid_instance_name", "", snapstate.Flags{DevMode: true}) 1711 c.Assert(err, ErrorMatches, `invalid instance name: invalid instance key: "invalid_instance_name"`) 1712 } 1713 1714 func (s *mgrsSuite) TestCheckInterfaces(c *C) { 1715 snapDecl := s.prereqSnapAssertions(c) 1716 1717 snapYamlContent := `name: foo 1718 apps: 1719 bar: 1720 command: bin/bar 1721 slots: 1722 network: 1723 ` 1724 snapPath := makeTestSnap(c, snapYamlContent+"version: 1.5") 1725 1726 si := &snap.SideInfo{ 1727 RealName: "foo", 1728 SnapID: fooSnapID, 1729 Revision: snap.R(55), 1730 } 1731 1732 st := s.o.State() 1733 st.Lock() 1734 defer st.Unlock() 1735 1736 // have the snap-declaration in the system db 1737 err := assertstate.Add(st, s.devAcct) 1738 c.Assert(err, IsNil) 1739 err = assertstate.Add(st, snapDecl) 1740 c.Assert(err, IsNil) 1741 1742 // mock SanitizePlugsSlots so that unknown interfaces are not rejected 1743 restoreSanitize := snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}) 1744 defer restoreSanitize() 1745 1746 ts, _, err := snapstate.InstallPath(st, si, snapPath, "", "", snapstate.Flags{DevMode: true}) 1747 c.Assert(err, IsNil) 1748 chg := st.NewChange("install-snap", "...") 1749 chg.AddAll(ts) 1750 1751 st.Unlock() 1752 err = s.o.Settle(settleTimeout) 1753 st.Lock() 1754 c.Assert(err, IsNil) 1755 1756 c.Assert(chg.Err(), ErrorMatches, `(?s).*installation not allowed by "network" slot rule of interface "network".*`) 1757 c.Check(chg.Status(), Equals, state.ErrorStatus) 1758 } 1759 1760 func (s *mgrsSuite) TestHappyRefreshControl(c *C) { 1761 // test install through store and update, plus some mechanics 1762 // of update 1763 // TODO: ok to split if it gets too messy to maintain 1764 1765 s.prereqSnapAssertions(c) 1766 1767 snapYamlContent := `name: foo 1768 version: @VERSION@ 1769 ` 1770 1771 ver := "1.0" 1772 revno := "42" 1773 snapPath, _ := s.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) 1774 s.serveSnap(snapPath, revno) 1775 1776 mockServer := s.mockStore(c) 1777 defer mockServer.Close() 1778 1779 st := s.o.State() 1780 st.Lock() 1781 defer st.Unlock() 1782 1783 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 1784 c.Assert(err, IsNil) 1785 chg := st.NewChange("install-snap", "...") 1786 chg.AddAll(ts) 1787 1788 st.Unlock() 1789 err = s.o.Settle(settleTimeout) 1790 st.Lock() 1791 c.Assert(err, IsNil) 1792 1793 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1794 1795 info, err := snapstate.CurrentInfo(st, "foo") 1796 c.Assert(err, IsNil) 1797 1798 c.Check(info.Revision, Equals, snap.R(42)) 1799 1800 // Refresh 1801 1802 // Setup refresh control 1803 1804 headers := map[string]interface{}{ 1805 "series": "16", 1806 "snap-id": "bar-id", 1807 "snap-name": "bar", 1808 "publisher-id": "devdevdev", 1809 "refresh-control": []interface{}{fooSnapID}, 1810 "timestamp": time.Now().Format(time.RFC3339), 1811 } 1812 snapDeclBar, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 1813 c.Assert(err, IsNil) 1814 err = s.storeSigning.Add(snapDeclBar) 1815 c.Assert(err, IsNil) 1816 err = assertstate.Add(st, snapDeclBar) 1817 c.Assert(err, IsNil) 1818 1819 snapstate.Set(st, "bar", &snapstate.SnapState{ 1820 Active: true, 1821 Sequence: []*snap.SideInfo{ 1822 {RealName: "bar", SnapID: "bar-id", Revision: snap.R(1)}, 1823 }, 1824 Current: snap.R(1), 1825 SnapType: "app", 1826 }) 1827 1828 develSigning := assertstest.NewSigningDB("devdevdev", develPrivKey) 1829 1830 develAccKey := assertstest.NewAccountKey(s.storeSigning, s.devAcct, nil, develPrivKey.PublicKey(), "") 1831 err = s.storeSigning.Add(develAccKey) 1832 c.Assert(err, IsNil) 1833 1834 ver = "2.0" 1835 revno = "50" 1836 snapPath, _ = s.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) 1837 s.serveSnap(snapPath, revno) 1838 1839 updated, tss, err := snapstate.UpdateMany(context.TODO(), st, []string{"foo"}, 0, nil) 1840 c.Check(updated, IsNil) 1841 c.Check(tss, IsNil) 1842 // no validation we, get an error 1843 c.Check(err, ErrorMatches, `cannot refresh "foo" to revision 50: no validation by "bar"`) 1844 1845 // setup validation 1846 headers = map[string]interface{}{ 1847 "series": "16", 1848 "snap-id": "bar-id", 1849 "approved-snap-id": fooSnapID, 1850 "approved-snap-revision": "50", 1851 "timestamp": time.Now().Format(time.RFC3339), 1852 } 1853 barValidation, err := develSigning.Sign(asserts.ValidationType, headers, nil, "") 1854 c.Assert(err, IsNil) 1855 err = s.storeSigning.Add(barValidation) 1856 c.Assert(err, IsNil) 1857 1858 // ... and try again 1859 updated, tss, err = snapstate.UpdateMany(context.TODO(), st, []string{"foo"}, 0, nil) 1860 c.Assert(err, IsNil) 1861 c.Assert(updated, DeepEquals, []string{"foo"}) 1862 c.Assert(tss, HasLen, 2) 1863 verifyLastTasksetIsRerefresh(c, tss) 1864 chg = st.NewChange("upgrade-snaps", "...") 1865 chg.AddAll(tss[0]) 1866 1867 st.Unlock() 1868 err = s.o.Settle(settleTimeout) 1869 st.Lock() 1870 c.Assert(err, IsNil) 1871 1872 c.Assert(chg.Err(), IsNil) 1873 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 1874 1875 info, err = snapstate.CurrentInfo(st, "foo") 1876 c.Assert(err, IsNil) 1877 1878 c.Check(info.Revision, Equals, snap.R(50)) 1879 } 1880 1881 // core & kernel 1882 1883 var modelDefaults = map[string]interface{}{ 1884 "architecture": "amd64", 1885 "store": "my-brand-store-id", 1886 "gadget": "pc", 1887 "kernel": "pc-kernel", 1888 } 1889 1890 func findKind(chg *state.Change, kind string) *state.Task { 1891 for _, t := range chg.Tasks() { 1892 if t.Kind() == kind { 1893 return t 1894 } 1895 } 1896 return nil 1897 } 1898 1899 func (s *mgrsSuite) TestInstallCoreSnapUpdatesBootloaderEnvAndSplitsAcrossRestart(c *C) { 1900 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 1901 bootloader.Force(bloader) 1902 defer bootloader.Force(nil) 1903 bloader.SetBootBase("core_99.snap") 1904 1905 restore := release.MockOnClassic(false) 1906 defer restore() 1907 1908 model := s.brands.Model("my-brand", "my-model", modelDefaults) 1909 1910 const packageOS = ` 1911 name: core 1912 version: 16.04-1 1913 type: os 1914 ` 1915 snapPath := makeTestSnap(c, packageOS) 1916 1917 st := s.o.State() 1918 st.Lock() 1919 defer st.Unlock() 1920 1921 // setup model assertion 1922 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 1923 devicestatetest.SetDevice(st, &auth.DeviceState{ 1924 Brand: "my-brand", 1925 Model: "my-model", 1926 Serial: "serialserialserial", 1927 }) 1928 err := assertstate.Add(st, model) 1929 c.Assert(err, IsNil) 1930 1931 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "core"}, snapPath, "", "", snapstate.Flags{}) 1932 c.Assert(err, IsNil) 1933 chg := st.NewChange("install-snap", "...") 1934 chg.AddAll(ts) 1935 1936 st.Unlock() 1937 err = s.o.Settle(settleTimeout) 1938 st.Lock() 1939 c.Assert(err, IsNil) 1940 1941 // final steps will are post poned until we are in the restarted snapd 1942 ok, rst := st.Restarting() 1943 c.Assert(ok, Equals, true) 1944 c.Assert(rst, Equals, state.RestartSystem) 1945 1946 t := findKind(chg, "auto-connect") 1947 c.Assert(t, NotNil) 1948 c.Assert(t.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1949 1950 // this is already set 1951 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 1952 "snap_core": "core_99.snap", 1953 "snap_try_core": "core_x1.snap", 1954 "snap_try_kernel": "", 1955 "snap_mode": boot.TryStatus, 1956 }) 1957 1958 // simulate successful restart happened 1959 state.MockRestarting(st, state.RestartUnset) 1960 bloader.BootVars["snap_mode"] = boot.DefaultStatus 1961 bloader.SetBootBase("core_x1.snap") 1962 1963 st.Unlock() 1964 err = s.o.Settle(settleTimeout) 1965 st.Lock() 1966 c.Assert(err, IsNil) 1967 1968 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 1969 } 1970 1971 type rebootEnv interface { 1972 SetTryingDuringReboot(which []snap.Type) error 1973 SetRollbackAcrossReboot(which []snap.Type) error 1974 } 1975 1976 func (s *baseMgrsSuite) mockSuccessfulReboot(c *C, be rebootEnv, which []snap.Type) { 1977 st := s.o.State() 1978 restarting, restartType := st.Restarting() 1979 c.Assert(restarting, Equals, true, Commentf("mockSuccessfulReboot called when there was no pending restart")) 1980 c.Assert(restartType, Equals, state.RestartSystem, Commentf("mockSuccessfulReboot called but restartType is not SystemRestart but %v", restartType)) 1981 state.MockRestarting(st, state.RestartUnset) 1982 err := be.SetTryingDuringReboot(which) 1983 c.Assert(err, IsNil) 1984 s.o.DeviceManager().ResetToPostBootState() 1985 st.Unlock() 1986 defer st.Lock() 1987 err = s.o.DeviceManager().Ensure() 1988 c.Assert(err, IsNil) 1989 } 1990 1991 func (s *baseMgrsSuite) mockRollbackAcrossReboot(c *C, be rebootEnv, which []snap.Type) { 1992 st := s.o.State() 1993 restarting, restartType := st.Restarting() 1994 c.Assert(restarting, Equals, true, Commentf("mockRollbackAcrossReboot called when there was no pending restart")) 1995 c.Assert(restartType, Equals, state.RestartSystem, Commentf("mockRollbackAcrossReboot called but restartType is not SystemRestart but %v", restartType)) 1996 state.MockRestarting(st, state.RestartUnset) 1997 err := be.SetRollbackAcrossReboot(which) 1998 c.Assert(err, IsNil) 1999 s.o.DeviceManager().ResetToPostBootState() 2000 st.Unlock() 2001 s.o.Settle(settleTimeout) 2002 st.Lock() 2003 } 2004 2005 func (s *mgrsSuite) TestInstallKernelSnapUpdatesBootloaderEnv(c *C) { 2006 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 2007 bootloader.Force(bloader) 2008 defer bootloader.Force(nil) 2009 2010 restore := release.MockOnClassic(false) 2011 defer restore() 2012 2013 model := s.brands.Model("my-brand", "my-model", modelDefaults) 2014 2015 const packageKernel = ` 2016 name: pc-kernel 2017 version: 4.0-1 2018 type: kernel` 2019 2020 files := [][]string{ 2021 {"kernel.img", "I'm a kernel"}, 2022 {"initrd.img", "...and I'm an initrd"}, 2023 {"meta/kernel.yaml", "version: 4.2"}, 2024 } 2025 snapPath := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 2026 2027 st := s.o.State() 2028 st.Lock() 2029 defer st.Unlock() 2030 2031 // pretend we have core18/pc-kernel 2032 bloader.BootVars = map[string]string{ 2033 "snap_core": "core18_2.snap", 2034 "snap_kernel": "pc-kernel_123.snap", 2035 "snap_mode": boot.DefaultStatus, 2036 } 2037 si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(123)} 2038 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 2039 SnapType: "kernel", 2040 Active: true, 2041 Sequence: []*snap.SideInfo{si1}, 2042 Current: si1.Revision, 2043 }) 2044 snaptest.MockSnapWithFiles(c, packageKernel, si1, [][]string{ 2045 {"meta/kernel.yaml", ""}, 2046 }) 2047 si2 := &snap.SideInfo{RealName: "core18", Revision: snap.R(2)} 2048 snapstate.Set(st, "core18", &snapstate.SnapState{ 2049 SnapType: "base", 2050 Active: true, 2051 Sequence: []*snap.SideInfo{si2}, 2052 Current: si2.Revision, 2053 }) 2054 2055 // setup model assertion 2056 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 2057 devicestatetest.SetDevice(st, &auth.DeviceState{ 2058 Brand: "my-brand", 2059 Model: "my-model", 2060 Serial: "serialserialserial", 2061 }) 2062 err := assertstate.Add(st, model) 2063 c.Assert(err, IsNil) 2064 2065 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "pc-kernel"}, snapPath, "", "", snapstate.Flags{}) 2066 c.Assert(err, IsNil) 2067 chg := st.NewChange("install-snap", "...") 2068 chg.AddAll(ts) 2069 2070 // run, this will trigger a wait for the restart 2071 st.Unlock() 2072 err = s.o.Settle(settleTimeout) 2073 st.Lock() 2074 c.Assert(err, IsNil) 2075 // we are in restarting state and the change is not done yet 2076 restarting, _ := st.Restarting() 2077 c.Check(restarting, Equals, true) 2078 c.Check(chg.Status(), Equals, state.DoingStatus) 2079 2080 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 2081 "snap_core": "core18_2.snap", 2082 "snap_try_core": "", 2083 "snap_kernel": "pc-kernel_123.snap", 2084 "snap_try_kernel": "pc-kernel_x1.snap", 2085 "snap_mode": boot.TryStatus, 2086 }) 2087 // pretend we restarted 2088 s.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeKernel}) 2089 2090 st.Unlock() 2091 err = s.o.Settle(settleTimeout) 2092 st.Lock() 2093 c.Assert(err, IsNil) 2094 2095 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 2096 } 2097 2098 func (s *mgrsSuite) TestInstallKernelSnapUndoUpdatesBootloaderEnv(c *C) { 2099 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 2100 bootloader.Force(bloader) 2101 defer bootloader.Force(nil) 2102 2103 restore := release.MockOnClassic(false) 2104 defer restore() 2105 2106 model := s.brands.Model("my-brand", "my-model", modelDefaults) 2107 2108 const packageKernel = ` 2109 name: pc-kernel 2110 version: 4.0-1 2111 type: kernel` 2112 2113 files := [][]string{ 2114 {"kernel.img", "I'm a kernel"}, 2115 {"initrd.img", "...and I'm an initrd"}, 2116 {"meta/kernel.yaml", "version: 4.2"}, 2117 } 2118 snapPath := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 2119 2120 st := s.o.State() 2121 st.Lock() 2122 defer st.Unlock() 2123 2124 // pretend we have core18/pc-kernel 2125 bloader.BootVars = map[string]string{ 2126 "snap_core": "core18_2.snap", 2127 "snap_kernel": "pc-kernel_123.snap", 2128 "snap_mode": boot.DefaultStatus, 2129 } 2130 si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(123)} 2131 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 2132 SnapType: "kernel", 2133 Active: true, 2134 Sequence: []*snap.SideInfo{si1}, 2135 Current: si1.Revision, 2136 }) 2137 snaptest.MockSnapWithFiles(c, packageKernel, si1, [][]string{ 2138 {"meta/kernel.yaml", ""}, 2139 }) 2140 si2 := &snap.SideInfo{RealName: "core18", Revision: snap.R(2)} 2141 snapstate.Set(st, "core18", &snapstate.SnapState{ 2142 SnapType: "base", 2143 Active: true, 2144 Sequence: []*snap.SideInfo{si2}, 2145 Current: si2.Revision, 2146 }) 2147 2148 // setup model assertion 2149 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 2150 devicestatetest.SetDevice(st, &auth.DeviceState{ 2151 Brand: "my-brand", 2152 Model: "my-model", 2153 Serial: "serialserialserial", 2154 }) 2155 err := assertstate.Add(st, model) 2156 c.Assert(err, IsNil) 2157 2158 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "pc-kernel"}, snapPath, "", "", snapstate.Flags{}) 2159 c.Assert(err, IsNil) 2160 2161 terr := st.NewTask("error-trigger", "provoking total undo") 2162 terr.WaitFor(ts.Tasks()[len(ts.Tasks())-1]) 2163 ts.AddTask(terr) 2164 chg := st.NewChange("install-snap", "...") 2165 chg.AddAll(ts) 2166 2167 // run, this will trigger a wait for the restart 2168 st.Unlock() 2169 err = s.o.Settle(settleTimeout) 2170 st.Lock() 2171 c.Assert(err, IsNil) 2172 2173 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 2174 "snap_core": "core18_2.snap", 2175 "snap_kernel": "pc-kernel_123.snap", 2176 "snap_try_kernel": "pc-kernel_x1.snap", 2177 "snap_mode": boot.TryStatus, 2178 "snap_try_core": "", 2179 }) 2180 2181 // we are in restarting state and the change is not done yet 2182 restarting, _ := st.Restarting() 2183 c.Check(restarting, Equals, true) 2184 c.Check(chg.Status(), Equals, state.DoingStatus) 2185 // pretend we restarted 2186 s.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeKernel}) 2187 2188 st.Unlock() 2189 err = s.o.Settle(settleTimeout) 2190 st.Lock() 2191 c.Assert(err, IsNil) 2192 2193 c.Assert(chg.Status(), Equals, state.ErrorStatus) 2194 2195 // and we undo the bootvars and trigger a reboot 2196 c.Check(bloader.BootVars, DeepEquals, map[string]string{ 2197 "snap_core": "core18_2.snap", 2198 "snap_try_core": "", 2199 "snap_try_kernel": "pc-kernel_123.snap", 2200 "snap_kernel": "pc-kernel_x1.snap", 2201 "snap_mode": boot.TryStatus, 2202 }) 2203 restarting, _ = st.Restarting() 2204 c.Check(restarting, Equals, true) 2205 } 2206 2207 func (s *mgrsSuite) TestInstallKernelSnap20UpdatesBootloaderEnv(c *C) { 2208 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 2209 bootloader.Force(bloader) 2210 defer bootloader.Force(nil) 2211 2212 // we have revision 1 installed 2213 kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 2214 c.Assert(err, IsNil) 2215 restore := bloader.SetEnabledKernel(kernel) 2216 defer restore() 2217 2218 restore = release.MockOnClassic(false) 2219 defer restore() 2220 2221 uc20ModelDefaults := map[string]interface{}{ 2222 "architecture": "amd64", 2223 "base": "core20", 2224 "store": "my-brand-store-id", 2225 "snaps": []interface{}{ 2226 map[string]interface{}{ 2227 "name": "pc-kernel", 2228 "id": snaptest.AssertedSnapID("pc-kernel"), 2229 "type": "kernel", 2230 "default-channel": "20", 2231 }, 2232 map[string]interface{}{ 2233 "name": "pc", 2234 "id": snaptest.AssertedSnapID("pc"), 2235 "type": "gadget", 2236 "default-channel": "20", 2237 }}, 2238 } 2239 2240 model := s.brands.Model("my-brand", "my-model", uc20ModelDefaults) 2241 2242 const packageKernel = ` 2243 name: pc-kernel 2244 version: 4.0-1 2245 type: kernel` 2246 2247 files := [][]string{ 2248 {"kernel.efi", "I'm a kernel.efi"}, 2249 {"meta/kernel.yaml", "version: 4.2"}, 2250 } 2251 kernelSnapSideInfo := &snap.SideInfo{RealName: "pc-kernel"} 2252 kernelSnapPath, kernelSnapInfo := snaptest.MakeTestSnapInfoWithFiles(c, packageKernel, files, kernelSnapSideInfo) 2253 2254 // mock the modeenv file 2255 m := boot.Modeenv{ 2256 Mode: "run", 2257 RecoverySystem: "20191127", 2258 Base: "core20_1.snap", 2259 } 2260 err = m.WriteTo("") 2261 c.Assert(err, IsNil) 2262 c.Assert(s.o.DeviceManager().ReloadModeenv(), IsNil) 2263 2264 st := s.o.State() 2265 st.Lock() 2266 defer st.Unlock() 2267 2268 si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(1)} 2269 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 2270 SnapType: "kernel", 2271 Active: true, 2272 Sequence: []*snap.SideInfo{si1}, 2273 Current: si1.Revision, 2274 }) 2275 snaptest.MockSnapWithFiles(c, packageKernel, si1, [][]string{ 2276 {"meta/kernel.yaml", ""}, 2277 }) 2278 si2 := &snap.SideInfo{RealName: "core20", Revision: snap.R(1)} 2279 snapstate.Set(st, "core20", &snapstate.SnapState{ 2280 SnapType: "base", 2281 Active: true, 2282 Sequence: []*snap.SideInfo{si2}, 2283 Current: si2.Revision, 2284 }) 2285 2286 // setup model assertion 2287 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 2288 devicestatetest.SetDevice(st, &auth.DeviceState{ 2289 Brand: "my-brand", 2290 Model: "my-model", 2291 Serial: "serialserialserial", 2292 }) 2293 err = assertstate.Add(st, model) 2294 c.Assert(err, IsNil) 2295 2296 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "pc-kernel"}, kernelSnapPath, "", "", snapstate.Flags{}) 2297 c.Assert(err, IsNil) 2298 chg := st.NewChange("install-snap", "...") 2299 chg.AddAll(ts) 2300 2301 // run, this will trigger a wait for the restart 2302 st.Unlock() 2303 err = s.o.Settle(settleTimeout) 2304 st.Lock() 2305 c.Assert(err, IsNil) 2306 2307 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 2308 "kernel_status": boot.TryStatus, 2309 }) 2310 2311 // we are in restarting state and the change is not done yet 2312 restarting, _ := st.Restarting() 2313 c.Check(restarting, Equals, true) 2314 c.Check(chg.Status(), Equals, state.DoingStatus) 2315 2316 // the kernelSnapInfo we mocked earlier will not have a revision set for the 2317 // SideInfo, but since the previous revision was "1", the next revision will 2318 // be x1 since it's unasserted, so we can set the Revision on the SideInfo 2319 // here to make comparison easier 2320 kernelSnapInfo.SideInfo.Revision = snap.R(-1) 2321 2322 // the current kernel in the bootloader is still the same 2323 currentKernel, err := bloader.Kernel() 2324 c.Assert(err, IsNil) 2325 firstKernel := snap.Info{SideInfo: *si1} 2326 c.Assert(currentKernel.Filename(), Equals, firstKernel.Filename()) 2327 2328 // the current try kernel in the bootloader is our new kernel 2329 currentTryKernel, err := bloader.TryKernel() 2330 c.Assert(err, IsNil) 2331 c.Assert(currentTryKernel.Filename(), Equals, kernelSnapInfo.Filename()) 2332 2333 // check that we extracted the kernel snap assets 2334 extractedKernels := bloader.ExtractKernelAssetsCalls 2335 c.Assert(extractedKernels, HasLen, 1) 2336 c.Assert(extractedKernels[0].Filename(), Equals, kernelSnapInfo.Filename()) 2337 2338 // pretend we restarted 2339 s.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeKernel}) 2340 2341 st.Unlock() 2342 err = s.o.Settle(settleTimeout) 2343 st.Lock() 2344 c.Assert(err, IsNil) 2345 2346 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 2347 2348 // also check that we are active on the second revision 2349 var snapst snapstate.SnapState 2350 err = snapstate.Get(st, "pc-kernel", &snapst) 2351 c.Assert(err, IsNil) 2352 c.Check(snapst.Sequence, HasLen, 2) 2353 c.Check(snapst.Sequence, DeepEquals, []*snap.SideInfo{si1, &kernelSnapInfo.SideInfo}) 2354 c.Check(snapst.Active, Equals, true) 2355 c.Check(snapst.Current, DeepEquals, snap.R(-1)) 2356 2357 // since we need to do a reboot to go back to the old kernel, we should now 2358 // have kernel on the bootloader as the new one, and no try kernel on the 2359 // bootloader 2360 finalCurrentKernel, err := bloader.Kernel() 2361 c.Assert(err, IsNil) 2362 c.Assert(finalCurrentKernel.Filename(), Equals, kernelSnapInfo.Filename()) 2363 2364 _, err = bloader.TryKernel() 2365 c.Assert(err, Equals, bootloader.ErrNoTryKernelRef) 2366 2367 // finally check that GetCurrentBoot gives us the new kernel 2368 dev, err := devicestate.DeviceCtx(st, nil, nil) 2369 c.Assert(err, IsNil) 2370 sn, err := boot.GetCurrentBoot(snap.TypeKernel, dev) 2371 c.Assert(err, IsNil) 2372 c.Assert(sn.Filename(), Equals, kernelSnapInfo.Filename()) 2373 } 2374 2375 func (s *mgrsSuite) TestInstallKernelSnap20UndoUpdatesBootloaderEnv(c *C) { 2376 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 2377 bootloader.Force(bloader) 2378 defer bootloader.Force(nil) 2379 2380 // we have revision 1 installed 2381 kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 2382 c.Assert(err, IsNil) 2383 restore := bloader.SetEnabledKernel(kernel) 2384 defer restore() 2385 2386 restore = release.MockOnClassic(false) 2387 defer restore() 2388 2389 uc20ModelDefaults := map[string]interface{}{ 2390 "architecture": "amd64", 2391 "base": "core20", 2392 "store": "my-brand-store-id", 2393 "snaps": []interface{}{ 2394 map[string]interface{}{ 2395 "name": "pc-kernel", 2396 "id": snaptest.AssertedSnapID("pc-kernel"), 2397 "type": "kernel", 2398 "default-channel": "20", 2399 }, 2400 map[string]interface{}{ 2401 "name": "pc", 2402 "id": snaptest.AssertedSnapID("pc"), 2403 "type": "gadget", 2404 "default-channel": "20", 2405 }}, 2406 } 2407 2408 model := s.brands.Model("my-brand", "my-model", uc20ModelDefaults) 2409 2410 const packageKernel = ` 2411 name: pc-kernel 2412 version: 4.0-1 2413 type: kernel` 2414 2415 files := [][]string{ 2416 {"kernel.efi", "I'm a kernel.efi"}, 2417 {"meta/kernel.yaml", "version: 4.2"}, 2418 } 2419 kernelSnapSideInfo := &snap.SideInfo{RealName: "pc-kernel"} 2420 kernelSnapPath, kernelSnapInfo := snaptest.MakeTestSnapInfoWithFiles(c, packageKernel, files, kernelSnapSideInfo) 2421 2422 // mock the modeenv file 2423 m := boot.Modeenv{ 2424 Mode: "run", 2425 RecoverySystem: "20191127", 2426 Base: "core20_1.snap", 2427 } 2428 err = m.WriteTo("") 2429 c.Assert(err, IsNil) 2430 c.Assert(s.o.DeviceManager().ReloadModeenv(), IsNil) 2431 2432 st := s.o.State() 2433 st.Lock() 2434 defer st.Unlock() 2435 2436 si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(1)} 2437 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 2438 SnapType: "kernel", 2439 Active: true, 2440 Sequence: []*snap.SideInfo{si1}, 2441 Current: si1.Revision, 2442 }) 2443 snaptest.MockSnapWithFiles(c, packageKernel, si1, [][]string{ 2444 {"meta/kernel.yaml", ""}, 2445 }) 2446 si2 := &snap.SideInfo{RealName: "core20", Revision: snap.R(1)} 2447 snapstate.Set(st, "core20", &snapstate.SnapState{ 2448 SnapType: "base", 2449 Active: true, 2450 Sequence: []*snap.SideInfo{si2}, 2451 Current: si2.Revision, 2452 }) 2453 2454 // setup model assertion 2455 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 2456 devicestatetest.SetDevice(st, &auth.DeviceState{ 2457 Brand: "my-brand", 2458 Model: "my-model", 2459 Serial: "serialserialserial", 2460 }) 2461 err = assertstate.Add(st, model) 2462 c.Assert(err, IsNil) 2463 2464 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "pc-kernel"}, kernelSnapPath, "", "", snapstate.Flags{}) 2465 c.Assert(err, IsNil) 2466 2467 terr := st.NewTask("error-trigger", "provoking total undo") 2468 terr.WaitFor(ts.Tasks()[len(ts.Tasks())-1]) 2469 ts.AddTask(terr) 2470 chg := st.NewChange("install-snap", "...") 2471 chg.AddAll(ts) 2472 2473 // run, this will trigger a wait for the restart 2474 st.Unlock() 2475 err = s.o.Settle(settleTimeout) 2476 st.Lock() 2477 c.Assert(err, IsNil) 2478 2479 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 2480 "kernel_status": boot.TryStatus, 2481 }) 2482 2483 // the kernelSnapInfo we mocked earlier will not have a revision set for the 2484 // SideInfo, but since the previous revision was "1", the next revision will 2485 // be x1 since it's unasserted, so we can set the Revision on the SideInfo 2486 // here to make comparison easier 2487 kernelSnapInfo.SideInfo.Revision = snap.R(-1) 2488 2489 // check that we extracted the kernel snap assets 2490 extractedKernels := bloader.ExtractKernelAssetsCalls 2491 c.Assert(extractedKernels, HasLen, 1) 2492 c.Assert(extractedKernels[0].Filename(), Equals, kernelSnapInfo.Filename()) 2493 2494 // the current kernel in the bootloader is still the same 2495 currentKernel, err := bloader.Kernel() 2496 c.Assert(err, IsNil) 2497 firstKernel := snap.Info{SideInfo: *si1} 2498 c.Assert(currentKernel.Filename(), Equals, firstKernel.Filename()) 2499 2500 // the current try kernel in the bootloader is our new kernel 2501 currentTryKernel, err := bloader.TryKernel() 2502 c.Assert(err, IsNil) 2503 c.Assert(currentTryKernel.Filename(), Equals, kernelSnapInfo.Filename()) 2504 2505 // we are in restarting state and the change is not done yet 2506 restarting, _ := st.Restarting() 2507 c.Check(restarting, Equals, true) 2508 c.Check(chg.Status(), Equals, state.DoingStatus) 2509 // pretend we restarted 2510 s.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeKernel}) 2511 2512 st.Unlock() 2513 err = s.o.Settle(settleTimeout) 2514 st.Lock() 2515 c.Assert(err, IsNil) 2516 2517 c.Assert(chg.Status(), Equals, state.ErrorStatus) 2518 2519 // we should have triggered a reboot to undo the boot changes 2520 restarting, _ = st.Restarting() 2521 c.Check(restarting, Equals, true) 2522 2523 // we need to reboot with a "new" try kernel, so kernel_status was set again 2524 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 2525 "kernel_status": boot.TryStatus, 2526 }) 2527 2528 // we should not have extracted any more kernel assets than before, since 2529 // the fallback kernel was already extracted 2530 extractedKernels = bloader.ExtractKernelAssetsCalls 2531 c.Assert(extractedKernels, HasLen, 1) // same as above check 2532 2533 // also check that we are active on the first revision again 2534 var snapst snapstate.SnapState 2535 err = snapstate.Get(st, "pc-kernel", &snapst) 2536 c.Assert(err, IsNil) 2537 c.Check(snapst.Sequence, HasLen, 1) 2538 c.Check(snapst.Sequence, DeepEquals, []*snap.SideInfo{si1}) 2539 c.Check(snapst.Active, Equals, true) 2540 c.Check(snapst.Current, DeepEquals, snap.R(1)) 2541 2542 // since we need to do a reboot to go back to the old kernel, we should now 2543 // have kernel on the bootloader as the new one, and the try kernel on the 2544 // booloader as the old one 2545 finalCurrentKernel, err := bloader.Kernel() 2546 c.Assert(err, IsNil) 2547 c.Assert(finalCurrentKernel.Filename(), Equals, kernelSnapInfo.Filename()) 2548 2549 finalTryKernel, err := bloader.TryKernel() 2550 c.Assert(err, IsNil) 2551 c.Assert(finalTryKernel.Filename(), Equals, firstKernel.Filename()) 2552 2553 // TODO:UC20: this test should probably simulate another reboot and confirm 2554 // that at the end of everything we have GetCurrentBoot() return the old 2555 // kernel we reverted back to again 2556 } 2557 2558 func (s *mgrsSuite) installLocalTestSnap(c *C, snapYamlContent string) *snap.Info { 2559 st := s.o.State() 2560 2561 snapPath := makeTestSnap(c, snapYamlContent) 2562 snapf, err := snapfile.Open(snapPath) 2563 c.Assert(err, IsNil) 2564 info, err := snap.ReadInfoFromSnapFile(snapf, nil) 2565 c.Assert(err, IsNil) 2566 2567 // store current state 2568 snapName := info.InstanceName() 2569 var snapst snapstate.SnapState 2570 snapstate.Get(st, snapName, &snapst) 2571 2572 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: snapName}, snapPath, "", "", snapstate.Flags{DevMode: true}) 2573 c.Assert(err, IsNil) 2574 chg := st.NewChange("install-snap", "...") 2575 chg.AddAll(ts) 2576 2577 st.Unlock() 2578 err = s.o.Settle(settleTimeout) 2579 st.Lock() 2580 c.Assert(err, IsNil) 2581 2582 c.Assert(chg.Err(), IsNil) 2583 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 2584 2585 return info 2586 } 2587 2588 func (s *mgrsSuite) removeSnap(c *C, name string) { 2589 st := s.o.State() 2590 2591 ts, err := snapstate.Remove(st, name, snap.R(0), &snapstate.RemoveFlags{Purge: true}) 2592 c.Assert(err, IsNil) 2593 chg := st.NewChange("remove-snap", "...") 2594 chg.AddAll(ts) 2595 2596 st.Unlock() 2597 err = s.o.Settle(settleTimeout) 2598 st.Lock() 2599 c.Assert(err, IsNil) 2600 2601 c.Assert(chg.Err(), IsNil) 2602 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("remove-snap change failed with: %v", chg.Err())) 2603 } 2604 2605 func (s *mgrsSuite) TestHappyRevert(c *C) { 2606 st := s.o.State() 2607 st.Lock() 2608 defer st.Unlock() 2609 2610 x1Yaml := `name: foo 2611 version: 1.0 2612 apps: 2613 x1: 2614 command: bin/bar 2615 ` 2616 x1binary := filepath.Join(dirs.SnapBinariesDir, "foo.x1") 2617 2618 x2Yaml := `name: foo 2619 version: 2.0 2620 apps: 2621 x2: 2622 command: bin/bar 2623 ` 2624 x2binary := filepath.Join(dirs.SnapBinariesDir, "foo.x2") 2625 2626 s.installLocalTestSnap(c, x1Yaml) 2627 s.installLocalTestSnap(c, x2Yaml) 2628 2629 // ensure we are on x2 2630 _, err := os.Lstat(x2binary) 2631 c.Assert(err, IsNil) 2632 _, err = os.Lstat(x1binary) 2633 c.Assert(err, ErrorMatches, ".*no such file.*") 2634 2635 // now do the revert 2636 ts, err := snapstate.Revert(st, "foo", snapstate.Flags{}) 2637 c.Assert(err, IsNil) 2638 chg := st.NewChange("revert-snap", "...") 2639 chg.AddAll(ts) 2640 2641 st.Unlock() 2642 err = s.o.Settle(settleTimeout) 2643 st.Lock() 2644 c.Assert(err, IsNil) 2645 2646 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("revert-snap change failed with: %v", chg.Err())) 2647 2648 // ensure that we use x1 now 2649 _, err = os.Lstat(x1binary) 2650 c.Assert(err, IsNil) 2651 _, err = os.Lstat(x2binary) 2652 c.Assert(err, ErrorMatches, ".*no such file.*") 2653 2654 // ensure that x1,x2 is still there, revert just moves the "current" 2655 // pointer 2656 for _, fn := range []string{"foo_x2.snap", "foo_x1.snap"} { 2657 p := filepath.Join(dirs.SnapBlobDir, fn) 2658 c.Assert(osutil.FileExists(p), Equals, true) 2659 } 2660 } 2661 2662 func (s *mgrsSuite) TestHappyAlias(c *C) { 2663 st := s.o.State() 2664 st.Lock() 2665 defer st.Unlock() 2666 2667 fooYaml := `name: foo 2668 version: 1.0 2669 apps: 2670 foo: 2671 command: bin/foo 2672 ` 2673 s.installLocalTestSnap(c, fooYaml) 2674 2675 ts, err := snapstate.Alias(st, "foo", "foo", "foo_") 2676 c.Assert(err, IsNil) 2677 chg := st.NewChange("alias", "...") 2678 chg.AddAll(ts) 2679 2680 st.Unlock() 2681 err = s.o.Settle(settleTimeout) 2682 st.Lock() 2683 c.Assert(err, IsNil) 2684 2685 c.Assert(chg.Err(), IsNil) 2686 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err())) 2687 2688 foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_") 2689 dest, err := os.Readlink(foo_Alias) 2690 c.Assert(err, IsNil) 2691 2692 c.Check(dest, Equals, "foo") 2693 2694 var snapst snapstate.SnapState 2695 err = snapstate.Get(st, "foo", &snapst) 2696 c.Assert(err, IsNil) 2697 c.Check(snapst.AutoAliasesDisabled, Equals, false) 2698 c.Check(snapst.AliasesPending, Equals, false) 2699 c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 2700 "foo_": {Manual: "foo"}, 2701 }) 2702 2703 s.removeSnap(c, "foo") 2704 2705 c.Check(osutil.IsSymlink(foo_Alias), Equals, false) 2706 } 2707 2708 func (s *mgrsSuite) TestHappyUnalias(c *C) { 2709 st := s.o.State() 2710 st.Lock() 2711 defer st.Unlock() 2712 2713 fooYaml := `name: foo 2714 version: 1.0 2715 apps: 2716 foo: 2717 command: bin/foo 2718 ` 2719 s.installLocalTestSnap(c, fooYaml) 2720 2721 ts, err := snapstate.Alias(st, "foo", "foo", "foo_") 2722 c.Assert(err, IsNil) 2723 chg := st.NewChange("alias", "...") 2724 chg.AddAll(ts) 2725 2726 st.Unlock() 2727 err = s.o.Settle(settleTimeout) 2728 st.Lock() 2729 c.Assert(err, IsNil) 2730 2731 c.Assert(chg.Err(), IsNil) 2732 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err())) 2733 2734 foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_") 2735 dest, err := os.Readlink(foo_Alias) 2736 c.Assert(err, IsNil) 2737 2738 c.Check(dest, Equals, "foo") 2739 2740 ts, snapName, err := snapstate.RemoveManualAlias(st, "foo_") 2741 c.Assert(err, IsNil) 2742 c.Check(snapName, Equals, "foo") 2743 chg = st.NewChange("unalias", "...") 2744 chg.AddAll(ts) 2745 2746 st.Unlock() 2747 err = s.o.Settle(settleTimeout) 2748 st.Lock() 2749 c.Assert(err, IsNil) 2750 2751 c.Assert(chg.Err(), IsNil) 2752 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("unalias change failed with: %v", chg.Err())) 2753 2754 c.Check(osutil.IsSymlink(foo_Alias), Equals, false) 2755 2756 var snapst snapstate.SnapState 2757 err = snapstate.Get(st, "foo", &snapst) 2758 c.Assert(err, IsNil) 2759 c.Check(snapst.AutoAliasesDisabled, Equals, false) 2760 c.Check(snapst.AliasesPending, Equals, false) 2761 c.Check(snapst.Aliases, HasLen, 0) 2762 } 2763 2764 func (s *mgrsSuite) TestHappyRemoteInstallAutoAliases(c *C) { 2765 s.prereqSnapAssertions(c, map[string]interface{}{ 2766 "snap-name": "foo", 2767 "aliases": []interface{}{ 2768 map[string]interface{}{"name": "app1", "target": "app1"}, 2769 map[string]interface{}{"name": "app2", "target": "app2"}, 2770 }, 2771 }) 2772 2773 snapYamlContent := `name: foo 2774 version: @VERSION@ 2775 apps: 2776 app1: 2777 command: bin/app1 2778 app2: 2779 command: bin/app2 2780 ` 2781 2782 ver := "1.0" 2783 revno := "42" 2784 snapPath, _ := s.makeStoreTestSnap(c, strings.Replace(snapYamlContent, "@VERSION@", ver, -1), revno) 2785 s.serveSnap(snapPath, revno) 2786 2787 mockServer := s.mockStore(c) 2788 defer mockServer.Close() 2789 2790 st := s.o.State() 2791 st.Lock() 2792 defer st.Unlock() 2793 2794 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 2795 c.Assert(err, IsNil) 2796 chg := st.NewChange("install-snap", "...") 2797 chg.AddAll(ts) 2798 2799 st.Unlock() 2800 err = s.o.Settle(settleTimeout) 2801 st.Lock() 2802 c.Assert(err, IsNil) 2803 2804 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 2805 2806 var snapst snapstate.SnapState 2807 err = snapstate.Get(st, "foo", &snapst) 2808 c.Assert(err, IsNil) 2809 c.Check(snapst.AutoAliasesDisabled, Equals, false) 2810 c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 2811 "app1": {Auto: "app1"}, 2812 "app2": {Auto: "app2"}, 2813 }) 2814 2815 // check disk 2816 app1Alias := filepath.Join(dirs.SnapBinariesDir, "app1") 2817 dest, err := os.Readlink(app1Alias) 2818 c.Assert(err, IsNil) 2819 c.Check(dest, Equals, "foo.app1") 2820 2821 app2Alias := filepath.Join(dirs.SnapBinariesDir, "app2") 2822 dest, err = os.Readlink(app2Alias) 2823 c.Assert(err, IsNil) 2824 c.Check(dest, Equals, "foo.app2") 2825 } 2826 2827 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateAutoAliases(c *C) { 2828 s.prereqSnapAssertions(c, map[string]interface{}{ 2829 "snap-name": "foo", 2830 "aliases": []interface{}{ 2831 map[string]interface{}{"name": "app1", "target": "app1"}, 2832 }, 2833 }) 2834 2835 fooYaml := `name: foo 2836 version: @VERSION@ 2837 apps: 2838 app1: 2839 command: bin/app1 2840 app2: 2841 command: bin/app2 2842 ` 2843 2844 fooPath, _ := s.makeStoreTestSnap(c, strings.Replace(fooYaml, "@VERSION@", "1.0", -1), "10") 2845 s.serveSnap(fooPath, "10") 2846 2847 mockServer := s.mockStore(c) 2848 defer mockServer.Close() 2849 2850 st := s.o.State() 2851 st.Lock() 2852 defer st.Unlock() 2853 2854 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 2855 c.Assert(err, IsNil) 2856 chg := st.NewChange("install-snap", "...") 2857 chg.AddAll(ts) 2858 2859 st.Unlock() 2860 err = s.o.Settle(settleTimeout) 2861 st.Lock() 2862 c.Assert(err, IsNil) 2863 2864 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 2865 2866 info, err := snapstate.CurrentInfo(st, "foo") 2867 c.Assert(err, IsNil) 2868 c.Check(info.Revision, Equals, snap.R(10)) 2869 c.Check(info.Version, Equals, "1.0") 2870 2871 var snapst snapstate.SnapState 2872 err = snapstate.Get(st, "foo", &snapst) 2873 c.Assert(err, IsNil) 2874 c.Check(snapst.AutoAliasesDisabled, Equals, false) 2875 c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 2876 "app1": {Auto: "app1"}, 2877 }) 2878 2879 app1Alias := filepath.Join(dirs.SnapBinariesDir, "app1") 2880 dest, err := os.Readlink(app1Alias) 2881 c.Assert(err, IsNil) 2882 c.Check(dest, Equals, "foo.app1") 2883 2884 s.prereqSnapAssertions(c, map[string]interface{}{ 2885 "snap-name": "foo", 2886 "aliases": []interface{}{ 2887 map[string]interface{}{"name": "app2", "target": "app2"}, 2888 }, 2889 "revision": "1", 2890 }) 2891 2892 // new foo version/revision 2893 fooPath, _ = s.makeStoreTestSnap(c, strings.Replace(fooYaml, "@VERSION@", "1.5", -1), "15") 2894 s.serveSnap(fooPath, "15") 2895 2896 // refresh all 2897 updated, tss, err := snapstate.UpdateMany(context.TODO(), st, nil, 0, nil) 2898 c.Assert(err, IsNil) 2899 c.Assert(updated, DeepEquals, []string{"foo"}) 2900 c.Assert(tss, HasLen, 2) 2901 verifyLastTasksetIsRerefresh(c, tss) 2902 chg = st.NewChange("upgrade-snaps", "...") 2903 chg.AddAll(tss[0]) 2904 2905 st.Unlock() 2906 err = s.o.Settle(settleTimeout) 2907 st.Lock() 2908 c.Assert(err, IsNil) 2909 2910 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 2911 2912 info, err = snapstate.CurrentInfo(st, "foo") 2913 c.Assert(err, IsNil) 2914 c.Check(info.Revision, Equals, snap.R(15)) 2915 c.Check(info.Version, Equals, "1.5") 2916 2917 var snapst2 snapstate.SnapState 2918 err = snapstate.Get(st, "foo", &snapst2) 2919 c.Assert(err, IsNil) 2920 c.Check(snapst2.AutoAliasesDisabled, Equals, false) 2921 c.Check(snapst2.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 2922 "app2": {Auto: "app2"}, 2923 }) 2924 2925 c.Check(osutil.IsSymlink(app1Alias), Equals, false) 2926 2927 app2Alias := filepath.Join(dirs.SnapBinariesDir, "app2") 2928 dest, err = os.Readlink(app2Alias) 2929 c.Assert(err, IsNil) 2930 c.Check(dest, Equals, "foo.app2") 2931 } 2932 2933 func (s *mgrsSuite) TestHappyRemoteInstallAndUpdateAutoAliasesUnaliased(c *C) { 2934 s.prereqSnapAssertions(c, map[string]interface{}{ 2935 "snap-name": "foo", 2936 "aliases": []interface{}{ 2937 map[string]interface{}{"name": "app1", "target": "app1"}, 2938 }, 2939 }) 2940 2941 fooYaml := `name: foo 2942 version: @VERSION@ 2943 apps: 2944 app1: 2945 command: bin/app1 2946 app2: 2947 command: bin/app2 2948 ` 2949 2950 fooPath, _ := s.makeStoreTestSnap(c, strings.Replace(fooYaml, "@VERSION@", "1.0", -1), "10") 2951 s.serveSnap(fooPath, "10") 2952 2953 mockServer := s.mockStore(c) 2954 defer mockServer.Close() 2955 2956 st := s.o.State() 2957 st.Lock() 2958 defer st.Unlock() 2959 2960 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{Unaliased: true}) 2961 c.Assert(err, IsNil) 2962 chg := st.NewChange("install-snap", "...") 2963 chg.AddAll(ts) 2964 2965 st.Unlock() 2966 err = s.o.Settle(settleTimeout) 2967 st.Lock() 2968 c.Assert(err, IsNil) 2969 2970 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 2971 2972 info, err := snapstate.CurrentInfo(st, "foo") 2973 c.Assert(err, IsNil) 2974 c.Check(info.Revision, Equals, snap.R(10)) 2975 c.Check(info.Version, Equals, "1.0") 2976 2977 var snapst snapstate.SnapState 2978 err = snapstate.Get(st, "foo", &snapst) 2979 c.Assert(err, IsNil) 2980 c.Check(snapst.AutoAliasesDisabled, Equals, true) 2981 c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 2982 "app1": {Auto: "app1"}, 2983 }) 2984 2985 app1Alias := filepath.Join(dirs.SnapBinariesDir, "app1") 2986 c.Check(osutil.IsSymlink(app1Alias), Equals, false) 2987 2988 s.prereqSnapAssertions(c, map[string]interface{}{ 2989 "snap-name": "foo", 2990 "aliases": []interface{}{ 2991 map[string]interface{}{"name": "app2", "target": "app2"}, 2992 }, 2993 "revision": "1", 2994 }) 2995 2996 // new foo version/revision 2997 fooPath, _ = s.makeStoreTestSnap(c, strings.Replace(fooYaml, "@VERSION@", "1.5", -1), "15") 2998 s.serveSnap(fooPath, "15") 2999 3000 // refresh foo 3001 ts, err = snapstate.Update(st, "foo", nil, 0, snapstate.Flags{}) 3002 c.Assert(err, IsNil) 3003 chg = st.NewChange("upgrade-snap", "...") 3004 chg.AddAll(ts) 3005 3006 st.Unlock() 3007 err = s.o.Settle(settleTimeout) 3008 st.Lock() 3009 c.Assert(err, IsNil) 3010 3011 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 3012 3013 info, err = snapstate.CurrentInfo(st, "foo") 3014 c.Assert(err, IsNil) 3015 c.Check(info.Revision, Equals, snap.R(15)) 3016 c.Check(info.Version, Equals, "1.5") 3017 3018 var snapst2 snapstate.SnapState 3019 err = snapstate.Get(st, "foo", &snapst2) 3020 c.Assert(err, IsNil) 3021 c.Check(snapst2.AutoAliasesDisabled, Equals, true) 3022 c.Check(snapst2.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 3023 "app2": {Auto: "app2"}, 3024 }) 3025 3026 c.Check(osutil.IsSymlink(app1Alias), Equals, false) 3027 3028 app2Alias := filepath.Join(dirs.SnapBinariesDir, "app2") 3029 c.Check(osutil.IsSymlink(app2Alias), Equals, false) 3030 } 3031 3032 func (s *mgrsSuite) TestHappyOrthogonalRefreshAutoAliases(c *C) { 3033 s.prereqSnapAssertions(c, map[string]interface{}{ 3034 "snap-name": "foo", 3035 "aliases": []interface{}{ 3036 map[string]interface{}{"name": "app1", "target": "app1"}, 3037 }, 3038 }, map[string]interface{}{ 3039 "snap-name": "bar", 3040 }) 3041 3042 fooYaml := `name: foo 3043 version: @VERSION@ 3044 apps: 3045 app1: 3046 command: bin/app1 3047 app2: 3048 command: bin/app2 3049 ` 3050 3051 barYaml := `name: bar 3052 version: @VERSION@ 3053 apps: 3054 app1: 3055 command: bin/app1 3056 app3: 3057 command: bin/app3 3058 ` 3059 3060 fooPath, _ := s.makeStoreTestSnap(c, strings.Replace(fooYaml, "@VERSION@", "1.0", -1), "10") 3061 s.serveSnap(fooPath, "10") 3062 3063 barPath, _ := s.makeStoreTestSnap(c, strings.Replace(barYaml, "@VERSION@", "2.0", -1), "20") 3064 s.serveSnap(barPath, "20") 3065 3066 mockServer := s.mockStore(c) 3067 defer mockServer.Close() 3068 3069 st := s.o.State() 3070 st.Lock() 3071 defer st.Unlock() 3072 3073 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 3074 c.Assert(err, IsNil) 3075 chg := st.NewChange("install-snap", "...") 3076 chg.AddAll(ts) 3077 3078 st.Unlock() 3079 err = s.o.Settle(settleTimeout) 3080 st.Lock() 3081 c.Assert(err, IsNil) 3082 3083 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3084 3085 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3086 3087 ts, err = snapstate.Install(context.TODO(), st, "bar", nil, 0, snapstate.Flags{}) 3088 c.Assert(err, IsNil) 3089 chg = st.NewChange("install-snap", "...") 3090 chg.AddAll(ts) 3091 3092 st.Unlock() 3093 err = s.o.Settle(settleTimeout) 3094 st.Lock() 3095 c.Assert(err, IsNil) 3096 3097 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3098 3099 info, err := snapstate.CurrentInfo(st, "foo") 3100 c.Assert(err, IsNil) 3101 c.Check(info.Revision, Equals, snap.R(10)) 3102 c.Check(info.Version, Equals, "1.0") 3103 3104 info, err = snapstate.CurrentInfo(st, "bar") 3105 c.Assert(err, IsNil) 3106 c.Check(info.Revision, Equals, snap.R(20)) 3107 c.Check(info.Version, Equals, "2.0") 3108 3109 var snapst snapstate.SnapState 3110 err = snapstate.Get(st, "foo", &snapst) 3111 c.Assert(err, IsNil) 3112 c.Check(snapst.AutoAliasesDisabled, Equals, false) 3113 c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 3114 "app1": {Auto: "app1"}, 3115 }) 3116 3117 // foo gets a new version/revision and a change of automatic aliases 3118 // bar gets only the latter 3119 // app1 is transferred from foo to bar 3120 // UpdateMany after a snap-declaration refresh handles all of this 3121 s.prereqSnapAssertions(c, map[string]interface{}{ 3122 "snap-name": "foo", 3123 "aliases": []interface{}{ 3124 map[string]interface{}{"name": "app2", "target": "app2"}, 3125 }, 3126 "revision": "1", 3127 }, map[string]interface{}{ 3128 "snap-name": "bar", 3129 "aliases": []interface{}{ 3130 map[string]interface{}{"name": "app1", "target": "app1"}, 3131 map[string]interface{}{"name": "app3", "target": "app3"}, 3132 }, 3133 "revision": "1", 3134 }) 3135 3136 // new foo version/revision 3137 fooPath, _ = s.makeStoreTestSnap(c, strings.Replace(fooYaml, "@VERSION@", "1.5", -1), "15") 3138 s.serveSnap(fooPath, "15") 3139 3140 // refresh all 3141 err = assertstate.RefreshSnapDeclarations(st, 0) 3142 c.Assert(err, IsNil) 3143 3144 updated, tss, err := snapstate.UpdateMany(context.TODO(), st, nil, 0, nil) 3145 c.Assert(err, IsNil) 3146 sort.Strings(updated) 3147 c.Assert(updated, DeepEquals, []string{"bar", "foo"}) 3148 c.Assert(tss, HasLen, 4) 3149 verifyLastTasksetIsRerefresh(c, tss) 3150 chg = st.NewChange("upgrade-snaps", "...") 3151 chg.AddAll(tss[0]) 3152 chg.AddAll(tss[1]) 3153 chg.AddAll(tss[2]) 3154 3155 st.Unlock() 3156 err = s.o.Settle(settleTimeout) 3157 st.Lock() 3158 c.Assert(err, IsNil) 3159 3160 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 3161 3162 info, err = snapstate.CurrentInfo(st, "foo") 3163 c.Assert(err, IsNil) 3164 c.Check(info.Revision, Equals, snap.R(15)) 3165 c.Check(info.Version, Equals, "1.5") 3166 3167 var snapst2 snapstate.SnapState 3168 err = snapstate.Get(st, "foo", &snapst2) 3169 c.Assert(err, IsNil) 3170 c.Check(snapst2.AutoAliasesDisabled, Equals, false) 3171 c.Check(snapst2.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 3172 "app2": {Auto: "app2"}, 3173 }) 3174 var snapst3 snapstate.SnapState 3175 err = snapstate.Get(st, "bar", &snapst3) 3176 c.Assert(err, IsNil) 3177 c.Check(snapst3.AutoAliasesDisabled, Equals, false) 3178 c.Check(snapst3.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{ 3179 "app1": {Auto: "app1"}, 3180 "app3": {Auto: "app3"}, 3181 }) 3182 3183 app2Alias := filepath.Join(dirs.SnapBinariesDir, "app2") 3184 dest, err := os.Readlink(app2Alias) 3185 c.Assert(err, IsNil) 3186 c.Check(dest, Equals, "foo.app2") 3187 3188 app1Alias := filepath.Join(dirs.SnapBinariesDir, "app1") 3189 dest, err = os.Readlink(app1Alias) 3190 c.Assert(err, IsNil) 3191 c.Check(dest, Equals, "bar.app1") 3192 app3Alias := filepath.Join(dirs.SnapBinariesDir, "app3") 3193 dest, err = os.Readlink(app3Alias) 3194 c.Assert(err, IsNil) 3195 c.Check(dest, Equals, "bar.app3") 3196 } 3197 3198 func (s *mgrsSuite) TestHappyStopWhileDownloadingHeader(c *C) { 3199 s.prereqSnapAssertions(c) 3200 3201 snapYamlContent := `name: foo 3202 version: 1.0 3203 ` 3204 snapPath, _ := s.makeStoreTestSnap(c, snapYamlContent, "42") 3205 s.serveSnap(snapPath, "42") 3206 3207 stopped := make(chan struct{}) 3208 s.hijackServeSnap = func(_ http.ResponseWriter) { 3209 s.o.Stop() 3210 close(stopped) 3211 } 3212 3213 mockServer := s.mockStore(c) 3214 defer mockServer.Close() 3215 3216 st := s.o.State() 3217 st.Lock() 3218 defer st.Unlock() 3219 3220 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 3221 c.Assert(err, IsNil) 3222 chg := st.NewChange("install-snap", "...") 3223 chg.AddAll(ts) 3224 3225 st.Unlock() 3226 s.o.Loop() 3227 3228 <-stopped 3229 3230 st.Lock() 3231 c.Assert(chg.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3232 } 3233 3234 func (s *mgrsSuite) TestHappyStopWhileDownloadingBody(c *C) { 3235 s.prereqSnapAssertions(c) 3236 3237 snapYamlContent := `name: foo 3238 version: 1.0 3239 ` 3240 snapPath, _ := s.makeStoreTestSnap(c, snapYamlContent, "42") 3241 s.serveSnap(snapPath, "42") 3242 3243 stopped := make(chan struct{}) 3244 s.hijackServeSnap = func(w http.ResponseWriter) { 3245 w.WriteHeader(200) 3246 // best effort to reach the body reading part in the client 3247 w.Write(make([]byte, 10000)) 3248 time.Sleep(100 * time.Millisecond) 3249 w.Write(make([]byte, 10000)) 3250 s.o.Stop() 3251 close(stopped) 3252 } 3253 3254 mockServer := s.mockStore(c) 3255 defer mockServer.Close() 3256 3257 st := s.o.State() 3258 st.Lock() 3259 defer st.Unlock() 3260 3261 ts, err := snapstate.Install(context.TODO(), st, "foo", nil, 0, snapstate.Flags{}) 3262 c.Assert(err, IsNil) 3263 chg := st.NewChange("install-snap", "...") 3264 chg.AddAll(ts) 3265 3266 st.Unlock() 3267 s.o.Loop() 3268 3269 <-stopped 3270 3271 st.Lock() 3272 c.Assert(chg.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3273 } 3274 3275 type storeCtxSetupSuite struct { 3276 o *overlord.Overlord 3277 sc store.DeviceAndAuthContext 3278 3279 storeSigning *assertstest.StoreStack 3280 restoreTrusted func() 3281 3282 brands *assertstest.SigningAccounts 3283 3284 model *asserts.Model 3285 serial *asserts.Serial 3286 3287 restoreBackends func() 3288 } 3289 3290 func (s *storeCtxSetupSuite) SetUpTest(c *C) { 3291 tempdir := c.MkDir() 3292 dirs.SetRootDir(tempdir) 3293 err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) 3294 c.Assert(err, IsNil) 3295 3296 captureStoreCtx := func(_ *store.Config, dac store.DeviceAndAuthContext) *store.Store { 3297 s.sc = dac 3298 return store.New(nil, nil) 3299 } 3300 r := overlord.MockStoreNew(captureStoreCtx) 3301 defer r() 3302 3303 s.storeSigning = assertstest.NewStoreStack("can0nical", nil) 3304 s.restoreTrusted = sysdb.InjectTrusted(s.storeSigning.Trusted) 3305 3306 s.brands = assertstest.NewSigningAccounts(s.storeSigning) 3307 s.brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 3308 "verification": "verified", 3309 }) 3310 assertstest.AddMany(s.storeSigning, s.brands.AccountsAndKeys("my-brand")...) 3311 3312 s.model = s.brands.Model("my-brand", "my-model", modelDefaults) 3313 3314 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 3315 c.Assert(err, IsNil) 3316 serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{ 3317 "authority-id": "my-brand", 3318 "brand-id": "my-brand", 3319 "model": "my-model", 3320 "serial": "7878", 3321 "device-key": string(encDevKey), 3322 "device-key-sha3-384": deviceKey.PublicKey().ID(), 3323 "timestamp": time.Now().Format(time.RFC3339), 3324 }, nil, "") 3325 c.Assert(err, IsNil) 3326 s.serial = serial.(*asserts.Serial) 3327 3328 s.restoreBackends = ifacestate.MockSecurityBackends(nil) 3329 3330 o, err := overlord.New(nil) 3331 c.Assert(err, IsNil) 3332 o.InterfaceManager().DisableUDevMonitor() 3333 s.o = o 3334 3335 st := o.State() 3336 st.Lock() 3337 defer st.Unlock() 3338 3339 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 3340 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 3341 } 3342 3343 func (s *storeCtxSetupSuite) TearDownTest(c *C) { 3344 dirs.SetRootDir("") 3345 s.restoreBackends() 3346 s.restoreTrusted() 3347 } 3348 3349 func (s *storeCtxSetupSuite) TestStoreID(c *C) { 3350 st := s.o.State() 3351 st.Lock() 3352 defer st.Unlock() 3353 3354 st.Unlock() 3355 storeID, err := s.sc.StoreID("fallback") 3356 st.Lock() 3357 c.Assert(err, IsNil) 3358 c.Check(storeID, Equals, "fallback") 3359 3360 // setup model in system statey 3361 devicestatetest.SetDevice(st, &auth.DeviceState{ 3362 Brand: s.serial.BrandID(), 3363 Model: s.serial.Model(), 3364 Serial: s.serial.Serial(), 3365 }) 3366 err = assertstate.Add(st, s.model) 3367 c.Assert(err, IsNil) 3368 3369 st.Unlock() 3370 storeID, err = s.sc.StoreID("fallback") 3371 st.Lock() 3372 c.Assert(err, IsNil) 3373 c.Check(storeID, Equals, "my-brand-store-id") 3374 } 3375 3376 func (s *storeCtxSetupSuite) TestDeviceSessionRequestParams(c *C) { 3377 st := s.o.State() 3378 st.Lock() 3379 defer st.Unlock() 3380 3381 st.Unlock() 3382 _, err := s.sc.DeviceSessionRequestParams("NONCE") 3383 st.Lock() 3384 c.Check(err, Equals, store.ErrNoSerial) 3385 3386 // setup model, serial and key in system state 3387 err = assertstate.Add(st, s.model) 3388 c.Assert(err, IsNil) 3389 err = assertstate.Add(st, s.serial) 3390 c.Assert(err, IsNil) 3391 kpMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir) 3392 c.Assert(err, IsNil) 3393 err = kpMgr.Put(deviceKey) 3394 c.Assert(err, IsNil) 3395 devicestatetest.SetDevice(st, &auth.DeviceState{ 3396 Brand: s.serial.BrandID(), 3397 Model: s.serial.Model(), 3398 Serial: s.serial.Serial(), 3399 KeyID: deviceKey.PublicKey().ID(), 3400 }) 3401 3402 st.Unlock() 3403 params, err := s.sc.DeviceSessionRequestParams("NONCE") 3404 st.Lock() 3405 c.Assert(err, IsNil) 3406 c.Check(strings.HasPrefix(params.EncodedRequest(), "type: device-session-request\n"), Equals, true) 3407 c.Check(params.EncodedSerial(), DeepEquals, string(asserts.Encode(s.serial))) 3408 c.Check(params.EncodedModel(), DeepEquals, string(asserts.Encode(s.model))) 3409 3410 } 3411 3412 func (s *storeCtxSetupSuite) TestProxyStoreParams(c *C) { 3413 st := s.o.State() 3414 st.Lock() 3415 defer st.Unlock() 3416 3417 defURL, err := url.Parse("http://store") 3418 c.Assert(err, IsNil) 3419 3420 st.Unlock() 3421 proxyStoreID, proxyStoreURL, err := s.sc.ProxyStoreParams(defURL) 3422 st.Lock() 3423 c.Assert(err, IsNil) 3424 c.Check(proxyStoreID, Equals, "") 3425 c.Check(proxyStoreURL, Equals, defURL) 3426 3427 // setup proxy store reference and assertion 3428 operatorAcct := assertstest.NewAccount(s.storeSigning, "foo-operator", nil, "") 3429 err = assertstate.Add(st, operatorAcct) 3430 c.Assert(err, IsNil) 3431 stoAs, err := s.storeSigning.Sign(asserts.StoreType, map[string]interface{}{ 3432 "store": "foo", 3433 "operator-id": operatorAcct.AccountID(), 3434 "url": "http://foo.internal", 3435 "timestamp": time.Now().Format(time.RFC3339), 3436 }, nil, "") 3437 c.Assert(err, IsNil) 3438 err = assertstate.Add(st, stoAs) 3439 c.Assert(err, IsNil) 3440 tr := config.NewTransaction(st) 3441 err = tr.Set("core", "proxy.store", "foo") 3442 c.Assert(err, IsNil) 3443 tr.Commit() 3444 3445 fooURL, err := url.Parse("http://foo.internal") 3446 c.Assert(err, IsNil) 3447 3448 st.Unlock() 3449 proxyStoreID, proxyStoreURL, err = s.sc.ProxyStoreParams(defURL) 3450 st.Lock() 3451 c.Assert(err, IsNil) 3452 c.Check(proxyStoreID, Equals, "foo") 3453 c.Check(proxyStoreURL, DeepEquals, fooURL) 3454 } 3455 3456 const snapYamlContent1 = `name: snap1 3457 plugs: 3458 shared-data-plug: 3459 interface: content 3460 target: import 3461 content: mylib 3462 apps: 3463 bar: 3464 command: bin/bar 3465 ` 3466 const snapYamlContent2 = `name: snap2 3467 slots: 3468 shared-data-slot: 3469 interface: content 3470 content: mylib 3471 read: 3472 - / 3473 apps: 3474 bar: 3475 command: bin/bar 3476 ` 3477 3478 func (s *mgrsSuite) testTwoInstalls(c *C, snapName1, snapYaml1, snapName2, snapYaml2 string) { 3479 snapPath1 := makeTestSnap(c, snapYaml1+"version: 1.0") 3480 snapPath2 := makeTestSnap(c, snapYaml2+"version: 1.0") 3481 3482 st := s.o.State() 3483 st.Lock() 3484 defer st.Unlock() 3485 3486 ts1, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: snapName1, SnapID: fakeSnapID(snapName1), Revision: snap.R(3)}, snapPath1, "", "", snapstate.Flags{DevMode: true}) 3487 c.Assert(err, IsNil) 3488 chg := st.NewChange("install-snap", "...") 3489 chg.AddAll(ts1) 3490 3491 ts2, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: snapName2, SnapID: fakeSnapID(snapName2), Revision: snap.R(3)}, snapPath2, "", "", snapstate.Flags{DevMode: true}) 3492 c.Assert(err, IsNil) 3493 3494 ts2.WaitAll(ts1) 3495 chg.AddAll(ts2) 3496 3497 st.Unlock() 3498 err = s.o.Settle(settleTimeout) 3499 st.Lock() 3500 c.Assert(err, IsNil) 3501 3502 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3503 3504 tasks := chg.Tasks() 3505 connectTask := tasks[len(tasks)-2] 3506 c.Assert(connectTask.Kind(), Equals, "connect") 3507 3508 setupProfilesTask := tasks[len(tasks)-1] 3509 c.Assert(setupProfilesTask.Kind(), Equals, "setup-profiles") 3510 3511 // verify connect task data 3512 var plugRef interfaces.PlugRef 3513 var slotRef interfaces.SlotRef 3514 c.Assert(connectTask.Get("plug", &plugRef), IsNil) 3515 c.Assert(connectTask.Get("slot", &slotRef), IsNil) 3516 c.Assert(plugRef.Snap, Equals, "snap1") 3517 c.Assert(plugRef.Name, Equals, "shared-data-plug") 3518 c.Assert(slotRef.Snap, Equals, "snap2") 3519 c.Assert(slotRef.Name, Equals, "shared-data-slot") 3520 3521 // verify that connection was made 3522 var conns map[string]interface{} 3523 c.Assert(st.Get("conns", &conns), IsNil) 3524 c.Assert(conns, HasLen, 1) 3525 3526 repo := s.o.InterfaceManager().Repository() 3527 cn, err := repo.Connected("snap1", "shared-data-plug") 3528 c.Assert(err, IsNil) 3529 c.Assert(cn, HasLen, 1) 3530 c.Assert(cn, DeepEquals, []*interfaces.ConnRef{{ 3531 PlugRef: interfaces.PlugRef{Snap: "snap1", Name: "shared-data-plug"}, 3532 SlotRef: interfaces.SlotRef{Snap: "snap2", Name: "shared-data-slot"}, 3533 }}) 3534 } 3535 3536 func (s *mgrsSuite) TestTwoInstallsWithAutoconnectPlugSnapFirst(c *C) { 3537 s.testTwoInstalls(c, "snap1", snapYamlContent1, "snap2", snapYamlContent2) 3538 } 3539 3540 func (s *mgrsSuite) TestTwoInstallsWithAutoconnectSlotSnapFirst(c *C) { 3541 s.testTwoInstalls(c, "snap2", snapYamlContent2, "snap1", snapYamlContent1) 3542 } 3543 3544 func (s *mgrsSuite) TestRemoveAndInstallWithAutoconnectHappy(c *C) { 3545 st := s.o.State() 3546 st.Lock() 3547 defer st.Unlock() 3548 3549 _ = s.installLocalTestSnap(c, snapYamlContent1+"version: 1.0") 3550 3551 ts, err := snapstate.Remove(st, "snap1", snap.R(0), &snapstate.RemoveFlags{Purge: true}) 3552 c.Assert(err, IsNil) 3553 chg := st.NewChange("remove-snap", "...") 3554 chg.AddAll(ts) 3555 3556 snapPath := makeTestSnap(c, snapYamlContent2+"version: 1.0") 3557 chg2 := st.NewChange("install-snap", "...") 3558 ts2, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "snap2", SnapID: fakeSnapID("snap2"), Revision: snap.R(3)}, snapPath, "", "", snapstate.Flags{DevMode: true}) 3559 chg2.AddAll(ts2) 3560 c.Assert(err, IsNil) 3561 3562 st.Unlock() 3563 err = s.o.Settle(settleTimeout) 3564 st.Lock() 3565 c.Assert(err, IsNil) 3566 3567 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("remove-snap change failed with: %v", chg.Err())) 3568 c.Assert(chg2.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err())) 3569 } 3570 3571 const otherSnapYaml = `name: other-snap 3572 version: 1.0 3573 apps: 3574 baz: 3575 command: bin/bar 3576 plugs: [media-hub] 3577 ` 3578 3579 func (s *mgrsSuite) TestUpdateManyWithAutoconnect(c *C) { 3580 const someSnapYaml = `name: some-snap 3581 version: 1.0 3582 apps: 3583 foo: 3584 command: bin/bar 3585 plugs: [network,home] 3586 slots: [media-hub] 3587 ` 3588 3589 const coreSnapYaml = `name: core 3590 type: os 3591 version: @VERSION@` 3592 3593 snapPath, _ := s.makeStoreTestSnap(c, someSnapYaml, "40") 3594 s.serveSnap(snapPath, "40") 3595 3596 snapPath, _ = s.makeStoreTestSnap(c, otherSnapYaml, "50") 3597 s.serveSnap(snapPath, "50") 3598 3599 corePath, _ := s.makeStoreTestSnap(c, strings.Replace(coreSnapYaml, "@VERSION@", "30", -1), "30") 3600 s.serveSnap(corePath, "30") 3601 3602 mockServer := s.mockStore(c) 3603 defer mockServer.Close() 3604 3605 st := s.o.State() 3606 st.Lock() 3607 defer st.Unlock() 3608 3609 st.Set("conns", map[string]interface{}{}) 3610 3611 si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)} 3612 snapInfo := snaptest.MockSnap(c, someSnapYaml, si) 3613 c.Assert(snapInfo.Plugs, HasLen, 2) 3614 3615 oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)} 3616 otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi) 3617 c.Assert(otherInfo.Plugs, HasLen, 1) 3618 3619 csi := &snap.SideInfo{RealName: "core", SnapID: fakeSnapID("core"), Revision: snap.R(1)} 3620 coreInfo := snaptest.MockSnap(c, strings.Replace(coreSnapYaml, "@VERSION@", "1", -1), csi) 3621 3622 // add implicit slots 3623 coreInfo.Slots["network"] = &snap.SlotInfo{ 3624 Name: "network", 3625 Snap: coreInfo, 3626 Interface: "network", 3627 } 3628 coreInfo.Slots["home"] = &snap.SlotInfo{ 3629 Name: "home", 3630 Snap: coreInfo, 3631 Interface: "home", 3632 } 3633 3634 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 3635 Active: true, 3636 Sequence: []*snap.SideInfo{si}, 3637 Current: snap.R(1), 3638 SnapType: "app", 3639 }) 3640 snapstate.Set(st, "other-snap", &snapstate.SnapState{ 3641 Active: true, 3642 Sequence: []*snap.SideInfo{oi}, 3643 Current: snap.R(1), 3644 SnapType: "app", 3645 }) 3646 3647 repo := s.o.InterfaceManager().Repository() 3648 3649 // add snaps to the repo to have plugs/slots 3650 c.Assert(repo.AddSnap(snapInfo), IsNil) 3651 c.Assert(repo.AddSnap(otherInfo), IsNil) 3652 c.Assert(repo.AddSnap(coreInfo), IsNil) 3653 3654 // refresh all 3655 err := assertstate.RefreshSnapDeclarations(st, 0) 3656 c.Assert(err, IsNil) 3657 3658 updates, tts, err := snapstate.UpdateMany(context.TODO(), st, []string{"core", "some-snap", "other-snap"}, 0, nil) 3659 c.Assert(err, IsNil) 3660 c.Check(updates, HasLen, 3) 3661 c.Assert(tts, HasLen, 4) 3662 verifyLastTasksetIsRerefresh(c, tts) 3663 3664 // to make TaskSnapSetup work 3665 chg := st.NewChange("refresh", "...") 3666 for _, ts := range tts[:len(tts)-1] { 3667 chg.AddAll(ts) 3668 } 3669 3670 // force hold state to hit ignore status of findSymmetricAutoconnect 3671 tts[2].Tasks()[0].SetStatus(state.HoldStatus) 3672 3673 st.Unlock() 3674 err = s.o.Settle(3 * time.Second) 3675 st.Lock() 3676 c.Assert(err, IsNil) 3677 3678 // simulate successful restart happened 3679 state.MockRestarting(st, state.RestartUnset) 3680 tts[2].Tasks()[0].SetStatus(state.DefaultStatus) 3681 st.Unlock() 3682 3683 err = s.o.Settle(settleTimeout) 3684 st.Lock() 3685 3686 c.Assert(err, IsNil) 3687 3688 c.Assert(chg.Status(), Equals, state.DoneStatus) 3689 3690 // check connections 3691 var conns map[string]interface{} 3692 st.Get("conns", &conns) 3693 c.Assert(conns, DeepEquals, map[string]interface{}{ 3694 "some-snap:home core:home": map[string]interface{}{"interface": "home", "auto": true}, 3695 "some-snap:network core:network": map[string]interface{}{"interface": "network", "auto": true}, 3696 "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": true}, 3697 }) 3698 3699 connections, err := repo.Connections("some-snap") 3700 c.Assert(err, IsNil) 3701 c.Assert(connections, HasLen, 3) 3702 } 3703 3704 func (s *mgrsSuite) TestUpdateWithAutoconnectAndInactiveRevisions(c *C) { 3705 const someSnapYaml = `name: some-snap 3706 version: 1.0 3707 apps: 3708 foo: 3709 command: bin/bar 3710 plugs: [network] 3711 ` 3712 const coreSnapYaml = `name: core 3713 type: os 3714 version: 1` 3715 3716 snapPath, _ := s.makeStoreTestSnap(c, someSnapYaml, "40") 3717 s.serveSnap(snapPath, "40") 3718 3719 mockServer := s.mockStore(c) 3720 defer mockServer.Close() 3721 3722 st := s.o.State() 3723 st.Lock() 3724 defer st.Unlock() 3725 3726 si1 := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)} 3727 snapInfo := snaptest.MockSnap(c, someSnapYaml, si1) 3728 c.Assert(snapInfo.Plugs, HasLen, 1) 3729 3730 csi := &snap.SideInfo{RealName: "core", SnapID: fakeSnapID("core"), Revision: snap.R(1)} 3731 coreInfo := snaptest.MockSnap(c, coreSnapYaml, csi) 3732 3733 // add implicit slots 3734 coreInfo.Slots["network"] = &snap.SlotInfo{ 3735 Name: "network", 3736 Snap: coreInfo, 3737 Interface: "network", 3738 } 3739 3740 // some-snap has inactive revisions 3741 si0 := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(0)} 3742 si2 := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(2)} 3743 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 3744 Active: true, 3745 Sequence: []*snap.SideInfo{si0, si1, si2}, 3746 Current: snap.R(1), 3747 SnapType: "app", 3748 }) 3749 3750 repo := s.o.InterfaceManager().Repository() 3751 3752 // add snaps to the repo to have plugs/slots 3753 c.Assert(repo.AddSnap(snapInfo), IsNil) 3754 c.Assert(repo.AddSnap(coreInfo), IsNil) 3755 3756 // refresh all 3757 err := assertstate.RefreshSnapDeclarations(st, 0) 3758 c.Assert(err, IsNil) 3759 3760 updates, tts, err := snapstate.UpdateMany(context.TODO(), st, []string{"some-snap"}, 0, nil) 3761 c.Assert(err, IsNil) 3762 c.Check(updates, HasLen, 1) 3763 c.Assert(tts, HasLen, 2) 3764 verifyLastTasksetIsRerefresh(c, tts) 3765 3766 // to make TaskSnapSetup work 3767 chg := st.NewChange("refresh", "...") 3768 chg.AddAll(tts[0]) 3769 3770 st.Unlock() 3771 err = s.o.Settle(settleTimeout) 3772 st.Lock() 3773 3774 c.Assert(err, IsNil) 3775 c.Assert(chg.Status(), Equals, state.DoneStatus) 3776 3777 // check connections 3778 var conns map[string]interface{} 3779 st.Get("conns", &conns) 3780 c.Assert(conns, DeepEquals, map[string]interface{}{ 3781 "some-snap:network core:network": map[string]interface{}{"interface": "network", "auto": true}, 3782 }) 3783 } 3784 3785 const someSnapYaml = `name: some-snap 3786 version: 1.0 3787 apps: 3788 foo: 3789 command: bin/bar 3790 slots: [media-hub] 3791 ` 3792 3793 func (s *mgrsSuite) testUpdateWithAutoconnectRetry(c *C, updateSnapName, removeSnapName string) { 3794 snapPath, _ := s.makeStoreTestSnap(c, someSnapYaml, "40") 3795 s.serveSnap(snapPath, "40") 3796 3797 snapPath, _ = s.makeStoreTestSnap(c, otherSnapYaml, "50") 3798 s.serveSnap(snapPath, "50") 3799 3800 mockServer := s.mockStore(c) 3801 defer mockServer.Close() 3802 3803 st := s.o.State() 3804 st.Lock() 3805 defer st.Unlock() 3806 3807 st.Set("conns", map[string]interface{}{}) 3808 3809 si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)} 3810 snapInfo := snaptest.MockSnap(c, someSnapYaml, si) 3811 c.Assert(snapInfo.Slots, HasLen, 1) 3812 3813 oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)} 3814 otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi) 3815 c.Assert(otherInfo.Plugs, HasLen, 1) 3816 3817 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 3818 Active: true, 3819 Sequence: []*snap.SideInfo{si}, 3820 Current: snap.R(1), 3821 SnapType: "app", 3822 }) 3823 snapstate.Set(st, "other-snap", &snapstate.SnapState{ 3824 Active: true, 3825 Sequence: []*snap.SideInfo{oi}, 3826 Current: snap.R(1), 3827 SnapType: "app", 3828 }) 3829 3830 repo := s.o.InterfaceManager().Repository() 3831 3832 // add snaps to the repo to have plugs/slots 3833 c.Assert(repo.AddSnap(snapInfo), IsNil) 3834 c.Assert(repo.AddSnap(otherInfo), IsNil) 3835 3836 // refresh all 3837 err := assertstate.RefreshSnapDeclarations(st, 0) 3838 c.Assert(err, IsNil) 3839 3840 ts, err := snapstate.Update(st, updateSnapName, nil, 0, snapstate.Flags{}) 3841 c.Assert(err, IsNil) 3842 3843 // to make TaskSnapSetup work 3844 chg := st.NewChange("refresh", "...") 3845 chg.AddAll(ts) 3846 3847 // remove other-snap 3848 ts2, err := snapstate.Remove(st, removeSnapName, snap.R(0), &snapstate.RemoveFlags{Purge: true}) 3849 c.Assert(err, IsNil) 3850 chg2 := st.NewChange("remove-snap", "...") 3851 chg2.AddAll(ts2) 3852 3853 // force hold state on first removal task to hit Retry error 3854 ts2.Tasks()[0].SetStatus(state.HoldStatus) 3855 3856 // Settle is not converging here because of the task in Hold status, therefore 3857 // it always hits given timeout before we carry on with the test. We're 3858 // interested in hitting the retry condition on auto-connect task, so 3859 // instead of passing a generous timeout to Settle(), repeat Settle() a number 3860 // of times with an aggressive timeout and break as soon as we reach the desired 3861 // state of auto-connect task. 3862 var retryCheck bool 3863 var autoconnectLog string 3864 for i := 0; i < 50 && !retryCheck; i++ { 3865 st.Unlock() 3866 s.o.Settle(aggressiveSettleTimeout) 3867 st.Lock() 3868 3869 for _, t := range st.Tasks() { 3870 if t.Kind() == "auto-connect" && t.Status() == state.DoingStatus && strings.Contains(strings.Join(t.Log(), ""), "Waiting") { 3871 autoconnectLog = strings.Join(t.Log(), "") 3872 retryCheck = true 3873 break 3874 } 3875 } 3876 } 3877 3878 c.Check(retryCheck, Equals, true) 3879 c.Assert(autoconnectLog, Matches, `.*Waiting for conflicting change in progress: conflicting snap.*`) 3880 3881 // back to default state, that will unblock autoconnect 3882 ts2.Tasks()[0].SetStatus(state.DefaultStatus) 3883 st.Unlock() 3884 err = s.o.Settle(settleTimeout) 3885 st.Lock() 3886 c.Assert(err, IsNil) 3887 3888 c.Check(chg.Err(), IsNil) 3889 c.Assert(chg.Status(), Equals, state.DoneStatus) 3890 3891 // check connections 3892 var conns map[string]interface{} 3893 st.Get("conns", &conns) 3894 c.Assert(conns, HasLen, 0) 3895 } 3896 3897 func (s *mgrsSuite) TestUpdateWithAutoconnectRetrySlotSide(c *C) { 3898 s.testUpdateWithAutoconnectRetry(c, "some-snap", "other-snap") 3899 } 3900 3901 func (s *mgrsSuite) TestUpdateWithAutoconnectRetryPlugSide(c *C) { 3902 s.testUpdateWithAutoconnectRetry(c, "other-snap", "some-snap") 3903 } 3904 3905 func (s *mgrsSuite) TestDisconnectIgnoredOnSymmetricRemove(c *C) { 3906 const someSnapYaml = `name: some-snap 3907 version: 1.0 3908 apps: 3909 foo: 3910 command: bin/bar 3911 slots: [media-hub] 3912 hooks: 3913 disconnect-slot-media-hub: 3914 ` 3915 const otherSnapYaml = `name: other-snap 3916 version: 1.0 3917 apps: 3918 baz: 3919 command: bin/bar 3920 plugs: [media-hub] 3921 hooks: 3922 disconnect-plug-media-hub: 3923 ` 3924 st := s.o.State() 3925 st.Lock() 3926 defer st.Unlock() 3927 3928 st.Set("conns", map[string]interface{}{ 3929 "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": false}, 3930 }) 3931 3932 si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)} 3933 snapInfo := snaptest.MockSnap(c, someSnapYaml, si) 3934 c.Assert(snapInfo.Slots, HasLen, 1) 3935 3936 oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)} 3937 otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi) 3938 c.Assert(otherInfo.Plugs, HasLen, 1) 3939 3940 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 3941 Active: true, 3942 Sequence: []*snap.SideInfo{si}, 3943 Current: snap.R(1), 3944 SnapType: "app", 3945 }) 3946 snapstate.Set(st, "other-snap", &snapstate.SnapState{ 3947 Active: true, 3948 Sequence: []*snap.SideInfo{oi}, 3949 Current: snap.R(1), 3950 SnapType: "app", 3951 }) 3952 3953 repo := s.o.InterfaceManager().Repository() 3954 3955 // add snaps to the repo to have plugs/slots 3956 c.Assert(repo.AddSnap(snapInfo), IsNil) 3957 c.Assert(repo.AddSnap(otherInfo), IsNil) 3958 repo.Connect(&interfaces.ConnRef{ 3959 PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"}, 3960 SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"}, 3961 }, nil, nil, nil, nil, nil) 3962 3963 flags := &snapstate.RemoveFlags{Purge: true} 3964 ts, err := snapstate.Remove(st, "some-snap", snap.R(0), flags) 3965 c.Assert(err, IsNil) 3966 chg := st.NewChange("uninstall", "...") 3967 chg.AddAll(ts) 3968 3969 // remove other-snap 3970 ts2, err := snapstate.Remove(st, "other-snap", snap.R(0), flags) 3971 c.Assert(err, IsNil) 3972 chg2 := st.NewChange("uninstall", "...") 3973 chg2.AddAll(ts2) 3974 3975 st.Unlock() 3976 err = s.o.Settle(settleTimeout) 3977 st.Lock() 3978 c.Assert(err, IsNil) 3979 3980 c.Assert(chg.Status(), Equals, state.DoneStatus) 3981 3982 // check connections 3983 var conns map[string]interface{} 3984 st.Get("conns", &conns) 3985 c.Assert(conns, HasLen, 0) 3986 3987 var disconnectInterfacesCount, slotHookCount, plugHookCount int 3988 for _, t := range st.Tasks() { 3989 if t.Kind() == "auto-disconnect" { 3990 disconnectInterfacesCount++ 3991 } 3992 if t.Kind() == "run-hook" { 3993 var hsup hookstate.HookSetup 3994 c.Assert(t.Get("hook-setup", &hsup), IsNil) 3995 if hsup.Hook == "disconnect-plug-media-hub" { 3996 plugHookCount++ 3997 } 3998 if hsup.Hook == "disconnect-slot-media-hub" { 3999 slotHookCount++ 4000 } 4001 } 4002 } 4003 c.Assert(plugHookCount, Equals, 1) 4004 c.Assert(slotHookCount, Equals, 1) 4005 c.Assert(disconnectInterfacesCount, Equals, 2) 4006 4007 var snst snapstate.SnapState 4008 err = snapstate.Get(st, "other-snap", &snst) 4009 c.Assert(err, Equals, state.ErrNoState) 4010 _, err = repo.Connected("other-snap", "media-hub") 4011 c.Assert(err, ErrorMatches, `snap "other-snap" has no plug or slot named "media-hub"`) 4012 } 4013 4014 func (s *mgrsSuite) TestDisconnectOnUninstallRemovesAutoconnection(c *C) { 4015 st := s.o.State() 4016 st.Lock() 4017 defer st.Unlock() 4018 4019 st.Set("conns", map[string]interface{}{ 4020 "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": true}, 4021 }) 4022 4023 si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)} 4024 snapInfo := snaptest.MockSnap(c, someSnapYaml, si) 4025 4026 oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)} 4027 otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi) 4028 4029 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 4030 Active: true, 4031 Sequence: []*snap.SideInfo{si}, 4032 Current: snap.R(1), 4033 SnapType: "app", 4034 }) 4035 snapstate.Set(st, "other-snap", &snapstate.SnapState{ 4036 Active: true, 4037 Sequence: []*snap.SideInfo{oi}, 4038 Current: snap.R(1), 4039 SnapType: "app", 4040 }) 4041 4042 repo := s.o.InterfaceManager().Repository() 4043 4044 // add snaps to the repo to have plugs/slots 4045 c.Assert(repo.AddSnap(snapInfo), IsNil) 4046 c.Assert(repo.AddSnap(otherInfo), IsNil) 4047 repo.Connect(&interfaces.ConnRef{ 4048 PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"}, 4049 SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"}, 4050 }, nil, nil, nil, nil, nil) 4051 4052 ts, err := snapstate.Remove(st, "some-snap", snap.R(0), &snapstate.RemoveFlags{Purge: true}) 4053 c.Assert(err, IsNil) 4054 chg := st.NewChange("uninstall", "...") 4055 chg.AddAll(ts) 4056 4057 st.Unlock() 4058 err = s.o.Settle(settleTimeout) 4059 st.Lock() 4060 c.Assert(err, IsNil) 4061 4062 c.Assert(chg.Status(), Equals, state.DoneStatus) 4063 4064 // check connections; auto-connection should be removed completely from conns on uninstall. 4065 var conns map[string]interface{} 4066 st.Get("conns", &conns) 4067 c.Assert(conns, HasLen, 0) 4068 } 4069 4070 // TODO: add a custom checker in testutils for this and similar 4071 func validateDownloadCheckTasks(c *C, tasks []*state.Task, name, revno, channel string) int { 4072 var i int 4073 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Ensure prerequisites for "%s" are available`, name)) 4074 i++ 4075 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Download snap "%s" (%s) from channel "%s"`, name, revno, channel)) 4076 i++ 4077 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Fetch and check assertions for snap "%s" (%s)`, name, revno)) 4078 i++ 4079 return i 4080 } 4081 4082 const ( 4083 noConfigure = 1 << iota 4084 isGadget 4085 isKernel 4086 ) 4087 4088 func validateInstallTasks(c *C, tasks []*state.Task, name, revno string, flags int) int { 4089 var i int 4090 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Mount snap "%s" (%s)`, name, revno)) 4091 i++ 4092 if flags&isGadget != 0 || flags&isKernel != 0 { 4093 what := "gadget" 4094 if flags&isKernel != 0 { 4095 what = "kernel" 4096 } 4097 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update assets from %s "%s" (%s)`, what, name, revno)) 4098 i++ 4099 } 4100 if flags&isGadget != 0 { 4101 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update kernel command line from gadget %q (%s)`, name, revno)) 4102 i++ 4103 } 4104 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Copy snap "%s" data`, name)) 4105 i++ 4106 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Setup snap "%s" (%s) security profiles`, name, revno)) 4107 i++ 4108 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Make snap "%s" (%s) available to the system`, name, revno)) 4109 i++ 4110 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Automatically connect eligible plugs and slots of snap "%s"`, name)) 4111 i++ 4112 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Set automatic aliases for snap "%s"`, name)) 4113 i++ 4114 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Setup snap "%s" aliases`, name)) 4115 i++ 4116 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run install hook of "%s" snap if present`, name)) 4117 i++ 4118 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Start snap "%s" (%s) services`, name, revno)) 4119 i++ 4120 if flags&noConfigure == 0 { 4121 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run configure hook of "%s" snap if present`, name)) 4122 i++ 4123 } 4124 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run health check of "%s" snap`, name)) 4125 i++ 4126 return i 4127 } 4128 4129 func validateRefreshTasks(c *C, tasks []*state.Task, name, revno string, flags int) int { 4130 var i int 4131 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Mount snap "%s" (%s)`, name, revno)) 4132 i++ 4133 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run pre-refresh hook of "%s" snap if present`, name)) 4134 i++ 4135 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Stop snap "%s" services`, name)) 4136 i++ 4137 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Remove aliases for snap "%s"`, name)) 4138 i++ 4139 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Make current revision for snap "%s" unavailable`, name)) 4140 i++ 4141 if flags&isGadget != 0 || flags&isKernel != 0 { 4142 what := "gadget" 4143 if flags&isKernel != 0 { 4144 what = "kernel" 4145 } 4146 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update assets from %s %q (%s)`, what, name, revno)) 4147 i++ 4148 } 4149 if flags&isGadget != 0 { 4150 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update kernel command line from gadget %q (%s)`, name, revno)) 4151 i++ 4152 } 4153 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Copy snap "%s" data`, name)) 4154 i++ 4155 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Setup snap "%s" (%s) security profiles`, name, revno)) 4156 i++ 4157 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Make snap "%s" (%s) available to the system`, name, revno)) 4158 i++ 4159 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Automatically connect eligible plugs and slots of snap "%s"`, name)) 4160 i++ 4161 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Set automatic aliases for snap "%s"`, name)) 4162 i++ 4163 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Setup snap "%s" aliases`, name)) 4164 i++ 4165 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run post-refresh hook of "%s" snap if present`, name)) 4166 i++ 4167 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Start snap "%s" (%s) services`, name, revno)) 4168 i++ 4169 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Clean up "%s" (%s) install`, name, revno)) 4170 i++ 4171 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run configure hook of "%s" snap if present`, name)) 4172 i++ 4173 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Run health check of "%s" snap`, name)) 4174 i++ 4175 return i 4176 } 4177 4178 func validateRecoverySystemTasks(c *C, tasks []*state.Task, label string) int { 4179 var i int 4180 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Create recovery system with label %q`, label)) 4181 i++ 4182 c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Finalize recovery system with label %q`, label)) 4183 i++ 4184 return i 4185 } 4186 4187 // byReadyTime sorts a list of tasks by their "ready" time 4188 type byReadyTime []*state.Task 4189 4190 func (a byReadyTime) Len() int { return len(a) } 4191 func (a byReadyTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 4192 func (a byReadyTime) Less(i, j int) bool { return a[i].ReadyTime().Before(a[j].ReadyTime()) } 4193 4194 func (s *mgrsSuite) TestRemodelRequiredSnapsAdded(c *C) { 4195 for _, name := range []string{"foo", "bar", "baz"} { 4196 s.prereqSnapAssertions(c, map[string]interface{}{ 4197 "snap-name": name, 4198 }) 4199 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 1.0}", name), "1") 4200 s.serveSnap(snapPath, "1") 4201 } 4202 4203 mockServer := s.mockStore(c) 4204 defer mockServer.Close() 4205 4206 st := s.o.State() 4207 st.Lock() 4208 defer st.Unlock() 4209 4210 // pretend we have an old required snap installed 4211 si1 := &snap.SideInfo{RealName: "old-required-snap-1", Revision: snap.R(1)} 4212 snapstate.Set(st, "old-required-snap-1", &snapstate.SnapState{ 4213 SnapType: "app", 4214 Active: true, 4215 Sequence: []*snap.SideInfo{si1}, 4216 Current: si1.Revision, 4217 Flags: snapstate.Flags{Required: true}, 4218 }) 4219 4220 // create/set custom model assertion 4221 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 4222 4223 model := s.brands.Model("my-brand", "my-model", modelDefaults) 4224 4225 // setup model assertion 4226 devicestatetest.SetDevice(st, &auth.DeviceState{ 4227 Brand: "my-brand", 4228 Model: "my-model", 4229 Serial: "serialserialserial", 4230 }) 4231 err := assertstate.Add(st, model) 4232 c.Assert(err, IsNil) 4233 s.makeSerialAssertionInState(c, st, "my-brand", "my-model", "serialserialserial") 4234 4235 // create a new model 4236 newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 4237 "required-snaps": []interface{}{"foo", "bar", "baz"}, 4238 "revision": "1", 4239 }) 4240 4241 chg, err := devicestate.Remodel(st, newModel) 4242 c.Assert(err, IsNil) 4243 4244 c.Check(devicestate.Remodeling(st), Equals, true) 4245 4246 st.Unlock() 4247 err = s.o.Settle(settleTimeout) 4248 st.Lock() 4249 c.Assert(err, IsNil) 4250 4251 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 4252 4253 c.Check(devicestate.Remodeling(st), Equals, false) 4254 4255 // the new required-snap "foo" is installed 4256 var snapst snapstate.SnapState 4257 err = snapstate.Get(st, "foo", &snapst) 4258 c.Assert(err, IsNil) 4259 info, err := snapst.CurrentInfo() 4260 c.Assert(err, IsNil) 4261 c.Check(info.Revision, Equals, snap.R(1)) 4262 c.Check(info.Version, Equals, "1.0") 4263 4264 // and marked required 4265 c.Check(snapst.Required, Equals, true) 4266 4267 // and core is still marked required 4268 err = snapstate.Get(st, "core", &snapst) 4269 c.Assert(err, IsNil) 4270 c.Check(snapst.Required, Equals, true) 4271 4272 // but old-required-snap-1 is no longer marked required 4273 err = snapstate.Get(st, "old-required-snap-1", &snapst) 4274 c.Assert(err, IsNil) 4275 c.Check(snapst.Required, Equals, false) 4276 4277 // ensure sorting is correct 4278 tasks := chg.Tasks() 4279 sort.Sort(byReadyTime(tasks)) 4280 4281 var i int 4282 // first all downloads/checks in sequential order 4283 for _, name := range []string{"foo", "bar", "baz"} { 4284 i += validateDownloadCheckTasks(c, tasks[i:], name, "1", "stable") 4285 } 4286 // then all installs in sequential order 4287 for _, name := range []string{"foo", "bar", "baz"} { 4288 i += validateInstallTasks(c, tasks[i:], name, "1", 0) 4289 } 4290 // ensure that we only have the tasks we checked (plus the one 4291 // extra "set-model" task) 4292 c.Assert(tasks, HasLen, i+1) 4293 } 4294 4295 func (s *mgrsSuite) TestRemodelRequiredSnapsAddedUndo(c *C) { 4296 for _, name := range []string{"foo", "bar", "baz"} { 4297 s.prereqSnapAssertions(c, map[string]interface{}{ 4298 "snap-name": name, 4299 }) 4300 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 1.0}", name), "1") 4301 s.serveSnap(snapPath, "1") 4302 } 4303 4304 mockServer := s.mockStore(c) 4305 defer mockServer.Close() 4306 4307 st := s.o.State() 4308 st.Lock() 4309 defer st.Unlock() 4310 4311 // pretend we have an old required snap installed 4312 si1 := &snap.SideInfo{RealName: "old-required-snap-1", Revision: snap.R(1)} 4313 snapstate.Set(st, "old-required-snap-1", &snapstate.SnapState{ 4314 SnapType: "app", 4315 Active: true, 4316 Sequence: []*snap.SideInfo{si1}, 4317 Current: si1.Revision, 4318 Flags: snapstate.Flags{Required: true}, 4319 }) 4320 4321 // create/set custom model assertion 4322 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 4323 curModel := s.brands.Model("my-brand", "my-model", modelDefaults) 4324 4325 // setup model assertion 4326 devicestatetest.SetDevice(st, &auth.DeviceState{ 4327 Brand: "my-brand", 4328 Model: "my-model", 4329 Serial: "serialserialserial", 4330 }) 4331 err := assertstate.Add(st, curModel) 4332 c.Assert(err, IsNil) 4333 s.makeSerialAssertionInState(c, st, "my-brand", "my-model", "serialserialserial") 4334 4335 // create a new model 4336 newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 4337 "required-snaps": []interface{}{"foo", "bar", "baz"}, 4338 "revision": "1", 4339 }) 4340 4341 devicestate.InjectSetModelError(fmt.Errorf("boom")) 4342 defer devicestate.InjectSetModelError(nil) 4343 4344 chg, err := devicestate.Remodel(st, newModel) 4345 c.Assert(err, IsNil) 4346 4347 st.Unlock() 4348 err = s.o.Settle(settleTimeout) 4349 st.Lock() 4350 c.Assert(err, IsNil) 4351 4352 c.Assert(chg.Status(), Equals, state.ErrorStatus) 4353 4354 // None of the new snaps got installed 4355 var snapst snapstate.SnapState 4356 for _, snapName := range []string{"foo", "bar", "baz"} { 4357 err = snapstate.Get(st, snapName, &snapst) 4358 c.Assert(err, Equals, state.ErrNoState) 4359 } 4360 4361 // old-required-snap-1 is still marked required 4362 err = snapstate.Get(st, "old-required-snap-1", &snapst) 4363 c.Assert(err, IsNil) 4364 c.Check(snapst.Required, Equals, true) 4365 4366 // check tasks are in undo state 4367 for _, t := range chg.Tasks() { 4368 if t.Kind() == "link-snap" { 4369 c.Assert(t.Status(), Equals, state.UndoneStatus) 4370 } 4371 } 4372 4373 model, err := s.o.DeviceManager().Model() 4374 c.Assert(err, IsNil) 4375 c.Assert(model, DeepEquals, curModel) 4376 } 4377 4378 func (s *mgrsSuite) TestRemodelDifferentBase(c *C) { 4379 // make "core18" snap available in the store 4380 snapYamlContent := `name: core18 4381 version: 18.04 4382 type: base` 4383 snapPath, _ := s.makeStoreTestSnap(c, snapYamlContent, "18") 4384 s.serveSnap(snapPath, "18") 4385 4386 mockServer := s.mockStore(c) 4387 defer mockServer.Close() 4388 4389 st := s.o.State() 4390 st.Lock() 4391 defer st.Unlock() 4392 4393 // create/set custom model assertion 4394 model := s.brands.Model("can0nical", "my-model", modelDefaults) 4395 // setup model assertion 4396 devicestatetest.SetDevice(st, &auth.DeviceState{ 4397 Brand: "can0nical", 4398 Model: "my-model", 4399 Serial: "serialserialserial", 4400 }) 4401 err := assertstate.Add(st, model) 4402 c.Assert(err, IsNil) 4403 s.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 4404 4405 // create a new model 4406 newModel := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4407 "base": "core18", 4408 "revision": "1", 4409 }) 4410 4411 chg, err := devicestate.Remodel(st, newModel) 4412 c.Assert(err, ErrorMatches, "cannot remodel from core to bases yet") 4413 c.Assert(chg, IsNil) 4414 } 4415 4416 func (ms *mgrsSuite) TestRemodelSwitchToDifferentBase(c *C) { 4417 bloader := bootloadertest.Mock("mock", c.MkDir()) 4418 bootloader.Force(bloader) 4419 defer bootloader.Force(nil) 4420 bloader.SetBootVars(map[string]string{ 4421 "snap_mode": boot.DefaultStatus, 4422 "snap_core": "core18_1.snap", 4423 "snap_kernel": "pc-kernel_1.snap", 4424 }) 4425 4426 restore := release.MockOnClassic(false) 4427 defer restore() 4428 4429 mockServer := ms.mockStore(c) 4430 defer mockServer.Close() 4431 4432 st := ms.o.State() 4433 st.Lock() 4434 defer st.Unlock() 4435 4436 si := &snap.SideInfo{RealName: "core18", SnapID: fakeSnapID("core18"), Revision: snap.R(1)} 4437 snapstate.Set(st, "core18", &snapstate.SnapState{ 4438 Active: true, 4439 Sequence: []*snap.SideInfo{si}, 4440 Current: snap.R(1), 4441 SnapType: "base", 4442 }) 4443 si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 4444 gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget" 4445 snapstate.Set(st, "pc", &snapstate.SnapState{ 4446 Active: true, 4447 Sequence: []*snap.SideInfo{si2}, 4448 Current: snap.R(1), 4449 SnapType: "gadget", 4450 }) 4451 gadgetYaml := ` 4452 volumes: 4453 volume-id: 4454 bootloader: grub 4455 ` 4456 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{ 4457 {"meta/gadget.yaml", gadgetYaml}, 4458 }) 4459 4460 // add "core20" snap to fake store 4461 const core20Yaml = `name: core20 4462 type: base 4463 version: 20.04` 4464 snapPath, _ := ms.makeStoreTestSnap(c, core20Yaml, "2") 4465 ms.serveSnap(snapPath, "2") 4466 4467 // add "foo" snap to fake store 4468 ms.prereqSnapAssertions(c, map[string]interface{}{ 4469 "snap-name": "foo", 4470 }) 4471 snapPath, _ = ms.makeStoreTestSnap(c, `{name: "foo", version: 1.0}`, "1") 4472 ms.serveSnap(snapPath, "1") 4473 4474 // create/set custom model assertion 4475 model := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4476 "base": "core18", 4477 }) 4478 4479 // setup model assertion 4480 devicestatetest.SetDevice(st, &auth.DeviceState{ 4481 Brand: "can0nical", 4482 Model: "my-model", 4483 Serial: "serialserialserial", 4484 }) 4485 err := assertstate.Add(st, model) 4486 c.Assert(err, IsNil) 4487 ms.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 4488 4489 // create a new model 4490 newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4491 "base": "core20", 4492 "revision": "1", 4493 "required-snaps": []interface{}{"foo"}, 4494 }) 4495 4496 chg, err := devicestate.Remodel(st, newModel) 4497 c.Assert(err, IsNil) 4498 4499 st.Unlock() 4500 err = ms.o.Settle(settleTimeout) 4501 st.Lock() 4502 c.Assert(err, IsNil) 4503 c.Assert(chg.Err(), IsNil) 4504 4505 // system waits for a restart because of the new base 4506 t := findKind(chg, "auto-connect") 4507 c.Assert(t, NotNil) 4508 c.Assert(t.Status(), Equals, state.DoingStatus) 4509 4510 // check that the boot vars got updated as expected 4511 bvars, err := bloader.GetBootVars("snap_mode", "snap_core", "snap_try_core", "snap_kernel", "snap_try_kernel") 4512 c.Assert(err, IsNil) 4513 c.Assert(bvars, DeepEquals, map[string]string{ 4514 "snap_mode": boot.TryStatus, 4515 "snap_core": "core18_1.snap", 4516 "snap_try_core": "core20_2.snap", 4517 "snap_kernel": "pc-kernel_1.snap", 4518 "snap_try_kernel": "", 4519 }) 4520 4521 // simulate successful restart happened and that the bootvars 4522 // got updated 4523 state.MockRestarting(st, state.RestartUnset) 4524 bloader.SetBootVars(map[string]string{ 4525 "snap_mode": boot.DefaultStatus, 4526 "snap_core": "core20_2.snap", 4527 "snap_kernel": "pc-kernel_1.snap", 4528 }) 4529 4530 // continue 4531 st.Unlock() 4532 err = ms.o.Settle(settleTimeout) 4533 st.Lock() 4534 c.Assert(err, IsNil) 4535 4536 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 4537 4538 // ensure tasks were run in the right order 4539 tasks := chg.Tasks() 4540 sort.Sort(byReadyTime(tasks)) 4541 4542 // first all downloads/checks in sequential order 4543 var i int 4544 i += validateDownloadCheckTasks(c, tasks[i:], "core20", "2", "stable") 4545 i += validateDownloadCheckTasks(c, tasks[i:], "foo", "1", "stable") 4546 4547 // then all installs in sequential order 4548 i += validateInstallTasks(c, tasks[i:], "core20", "2", noConfigure) 4549 i += validateInstallTasks(c, tasks[i:], "foo", "1", 0) 4550 4551 // ensure that we only have the tasks we checked (plus the one 4552 // extra "set-model" task) 4553 c.Assert(tasks, HasLen, i+1) 4554 } 4555 4556 func (ms *mgrsSuite) TestRemodelSwitchToDifferentBaseUndo(c *C) { 4557 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 4558 bootloader.Force(bloader) 4559 defer bootloader.Force(nil) 4560 bloader.SetBootVars(map[string]string{ 4561 "snap_mode": boot.DefaultStatus, 4562 "snap_core": "core18_1.snap", 4563 "snap_kernel": "pc-kernel_1.snap", 4564 }) 4565 4566 restore := release.MockOnClassic(false) 4567 defer restore() 4568 4569 mockServer := ms.mockStore(c) 4570 defer mockServer.Close() 4571 4572 st := ms.o.State() 4573 st.Lock() 4574 defer st.Unlock() 4575 4576 si := &snap.SideInfo{RealName: "core18", SnapID: fakeSnapID("core18"), Revision: snap.R(1)} 4577 snapstate.Set(st, "core18", &snapstate.SnapState{ 4578 Active: true, 4579 Sequence: []*snap.SideInfo{si}, 4580 Current: snap.R(1), 4581 SnapType: "base", 4582 }) 4583 snaptest.MockSnapWithFiles(c, "name: core18\ntype: base\nversion: 1.0", si, nil) 4584 4585 si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 4586 gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget" 4587 snapstate.Set(st, "pc", &snapstate.SnapState{ 4588 Active: true, 4589 Sequence: []*snap.SideInfo{si2}, 4590 Current: snap.R(1), 4591 SnapType: "gadget", 4592 }) 4593 gadgetYaml := ` 4594 volumes: 4595 volume-id: 4596 bootloader: grub 4597 ` 4598 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{ 4599 {"meta/gadget.yaml", gadgetYaml}, 4600 }) 4601 4602 // add "core20" snap to fake store 4603 const core20Yaml = `name: core20 4604 type: base 4605 version: 20.04` 4606 snapPath, _ := ms.makeStoreTestSnap(c, core20Yaml, "2") 4607 ms.serveSnap(snapPath, "2") 4608 4609 // add "foo" snap to fake store 4610 ms.prereqSnapAssertions(c, map[string]interface{}{ 4611 "snap-name": "foo", 4612 }) 4613 snapPath, _ = ms.makeStoreTestSnap(c, `{name: "foo", version: 1.0}`, "1") 4614 ms.serveSnap(snapPath, "1") 4615 4616 // create/set custom model assertion 4617 model := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4618 "base": "core18", 4619 }) 4620 4621 // setup model assertion 4622 devicestatetest.SetDevice(st, &auth.DeviceState{ 4623 Brand: "can0nical", 4624 Model: "my-model", 4625 Serial: "serialserialserial", 4626 }) 4627 err := assertstate.Add(st, model) 4628 c.Assert(err, IsNil) 4629 ms.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 4630 4631 // create a new model 4632 newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4633 "base": "core20", 4634 "revision": "1", 4635 "required-snaps": []interface{}{"foo"}, 4636 }) 4637 4638 devicestate.InjectSetModelError(fmt.Errorf("boom")) 4639 defer devicestate.InjectSetModelError(nil) 4640 4641 chg, err := devicestate.Remodel(st, newModel) 4642 c.Assert(err, IsNil) 4643 4644 st.Unlock() 4645 err = ms.o.Settle(settleTimeout) 4646 st.Lock() 4647 c.Assert(err, IsNil) 4648 c.Assert(chg.Err(), IsNil) 4649 4650 // system waits for a restart because of the new base 4651 t := findKind(chg, "auto-connect") 4652 c.Assert(t, NotNil) 4653 c.Assert(t.Status(), Equals, state.DoingStatus) 4654 4655 // check that the boot vars got updated as expected 4656 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 4657 "snap_mode": boot.TryStatus, 4658 "snap_core": "core18_1.snap", 4659 "snap_try_core": "core20_2.snap", 4660 "snap_kernel": "pc-kernel_1.snap", 4661 "snap_try_kernel": "", 4662 }) 4663 // simulate successful restart happened 4664 ms.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeBase}) 4665 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 4666 "snap_mode": boot.DefaultStatus, 4667 "snap_core": "core20_2.snap", 4668 "snap_try_core": "", 4669 "snap_kernel": "pc-kernel_1.snap", 4670 "snap_try_kernel": "", 4671 }) 4672 4673 // continue 4674 st.Unlock() 4675 err = ms.o.Settle(settleTimeout) 4676 st.Lock() 4677 c.Assert(err, IsNil) 4678 4679 c.Assert(chg.Status(), Equals, state.ErrorStatus) 4680 4681 // and we are in restarting state 4682 restarting, restartType := st.Restarting() 4683 c.Check(restarting, Equals, true) 4684 c.Check(restartType, Equals, state.RestartSystem) 4685 4686 // and the undo gave us our old kernel back 4687 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 4688 "snap_core": "core20_2.snap", 4689 "snap_try_core": "core18_1.snap", 4690 "snap_kernel": "pc-kernel_1.snap", 4691 "snap_try_kernel": "", 4692 "snap_mode": boot.TryStatus, 4693 }) 4694 } 4695 4696 func (ms *mgrsSuite) TestRemodelSwitchToDifferentBaseUndoOnRollback(c *C) { 4697 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 4698 bootloader.Force(bloader) 4699 defer bootloader.Force(nil) 4700 bloader.SetBootVars(map[string]string{ 4701 "snap_mode": boot.DefaultStatus, 4702 "snap_core": "core18_1.snap", 4703 "snap_kernel": "pc-kernel_1.snap", 4704 }) 4705 4706 restore := release.MockOnClassic(false) 4707 defer restore() 4708 4709 mockServer := ms.mockStore(c) 4710 defer mockServer.Close() 4711 4712 st := ms.o.State() 4713 st.Lock() 4714 defer st.Unlock() 4715 4716 si := &snap.SideInfo{RealName: "core18", SnapID: fakeSnapID("core18"), Revision: snap.R(1)} 4717 snapstate.Set(st, "core18", &snapstate.SnapState{ 4718 Active: true, 4719 Sequence: []*snap.SideInfo{si}, 4720 Current: snap.R(1), 4721 SnapType: "base", 4722 }) 4723 snaptest.MockSnapWithFiles(c, "name: core18\ntype: base\nversion: 1.0", si, nil) 4724 4725 si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 4726 gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget" 4727 snapstate.Set(st, "pc", &snapstate.SnapState{ 4728 Active: true, 4729 Sequence: []*snap.SideInfo{si2}, 4730 Current: snap.R(1), 4731 SnapType: "gadget", 4732 }) 4733 gadgetYaml := ` 4734 volumes: 4735 volume-id: 4736 bootloader: grub 4737 ` 4738 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{ 4739 {"meta/gadget.yaml", gadgetYaml}, 4740 }) 4741 4742 // add "core20" snap to fake store 4743 const core20Yaml = `name: core20 4744 type: base 4745 version: 20.04` 4746 snapPath, _ := ms.makeStoreTestSnap(c, core20Yaml, "2") 4747 ms.serveSnap(snapPath, "2") 4748 4749 // add "foo" snap to fake store 4750 ms.prereqSnapAssertions(c, map[string]interface{}{ 4751 "snap-name": "foo", 4752 }) 4753 snapPath, _ = ms.makeStoreTestSnap(c, `{name: "foo", version: 1.0}`, "1") 4754 ms.serveSnap(snapPath, "1") 4755 4756 // create/set custom model assertion 4757 model := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4758 "base": "core18", 4759 }) 4760 4761 // setup model assertion 4762 devicestatetest.SetDevice(st, &auth.DeviceState{ 4763 Brand: "can0nical", 4764 Model: "my-model", 4765 Serial: "serialserialserial", 4766 }) 4767 err := assertstate.Add(st, model) 4768 c.Assert(err, IsNil) 4769 ms.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 4770 4771 // create a new model 4772 newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 4773 "base": "core20", 4774 "revision": "1", 4775 "required-snaps": []interface{}{"foo"}, 4776 }) 4777 4778 chg, err := devicestate.Remodel(st, newModel) 4779 c.Assert(err, IsNil) 4780 4781 st.Unlock() 4782 err = ms.o.Settle(settleTimeout) 4783 st.Lock() 4784 c.Assert(err, IsNil) 4785 c.Assert(chg.Err(), IsNil) 4786 4787 // system waits for a restart because of the new base 4788 t := findKind(chg, "auto-connect") 4789 c.Assert(t, NotNil) 4790 c.Assert(t.Status(), Equals, state.DoingStatus) 4791 4792 // check that the boot vars got updated as expected 4793 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 4794 "snap_mode": boot.TryStatus, 4795 "snap_core": "core18_1.snap", 4796 "snap_try_core": "core20_2.snap", 4797 "snap_kernel": "pc-kernel_1.snap", 4798 "snap_try_kernel": "", 4799 }) 4800 // simulate successful restart happened 4801 ms.mockRollbackAcrossReboot(c, bloader, []snap.Type{snap.TypeBase}) 4802 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 4803 "snap_mode": boot.DefaultStatus, 4804 "snap_core": "core18_1.snap", 4805 "snap_try_core": "", 4806 "snap_kernel": "pc-kernel_1.snap", 4807 "snap_try_kernel": "", 4808 }) 4809 4810 // continue 4811 st.Unlock() 4812 err = ms.o.Settle(settleTimeout) 4813 st.Lock() 4814 c.Assert(err, IsNil) 4815 4816 c.Assert(chg.Status(), Equals, state.ErrorStatus) 4817 4818 // and we are *not* in restarting state 4819 restarting, _ := st.Restarting() 4820 c.Check(restarting, Equals, false) 4821 // bootvars unchanged 4822 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 4823 "snap_mode": boot.DefaultStatus, 4824 "snap_core": "core18_1.snap", 4825 "snap_try_core": "", 4826 "snap_kernel": "pc-kernel_1.snap", 4827 "snap_try_kernel": "", 4828 }) 4829 } 4830 4831 func (ms *mgrsSuite) TestRefreshSimpleSameRev(c *C) { 4832 // the "some-snap" in rev1 4833 snapYaml := "name: some-snap\nversion: 1.0" 4834 revStr := "1" 4835 // is available in the store 4836 snapPath, _ := ms.makeStoreTestSnap(c, snapYaml, revStr) 4837 ms.serveSnap(snapPath, revStr) 4838 4839 mockServer := ms.mockStore(c) 4840 ms.AddCleanup(mockServer.Close) 4841 4842 st := ms.o.State() 4843 st.Lock() 4844 defer st.Unlock() 4845 4846 // and some-snap:rev1 is also installed 4847 info := ms.mockInstalledSnapWithRevAndFiles(c, snapYaml, snap.R(revStr), nil) 4848 4849 // now refresh from rev1 to rev1 4850 revOpts := &snapstate.RevisionOptions{Revision: snap.R(revStr)} 4851 ts, err := snapstate.Update(st, "some-snap", revOpts, 0, snapstate.Flags{}) 4852 c.Assert(err, IsNil) 4853 4854 chg := st.NewChange("refresh", "...") 4855 chg.AddAll(ts) 4856 4857 st.Unlock() 4858 err = ms.o.Settle(settleTimeout) 4859 st.Lock() 4860 c.Assert(err, IsNil) 4861 c.Check(chg.Err(), IsNil) 4862 c.Check(chg.Status(), Equals, state.DoneStatus) 4863 4864 // the snap file is in the right place 4865 c.Check(info.MountFile(), testutil.FilePresent) 4866 4867 // rev1 is installed 4868 var snapst snapstate.SnapState 4869 snapstate.Get(st, "some-snap", &snapst) 4870 info, err = snapst.CurrentInfo() 4871 c.Assert(err, IsNil) 4872 c.Assert(info.Revision, Equals, snap.R(1)) 4873 } 4874 4875 func (ms *mgrsSuite) TestRefreshSimplePrevRev(c *C) { 4876 // the "some-snap" in rev1 4877 snapYaml := "name: some-snap\nversion: 1.0" 4878 revStr := "1" 4879 // is available in the store 4880 snapPath, _ := ms.makeStoreTestSnap(c, snapYaml, revStr) 4881 ms.serveSnap(snapPath, revStr) 4882 4883 mockServer := ms.mockStore(c) 4884 ms.AddCleanup(mockServer.Close) 4885 4886 st := ms.o.State() 4887 st.Lock() 4888 defer st.Unlock() 4889 4890 // and some-snap at both rev1, rev2 are installed 4891 info := snaptest.MockSnapWithFiles(c, snapYaml, &snap.SideInfo{Revision: snap.R(1)}, nil) 4892 snaptest.MockSnapWithFiles(c, snapYaml, &snap.SideInfo{Revision: snap.R(2)}, nil) 4893 si1 := &snap.SideInfo{ 4894 RealName: info.SnapName(), 4895 SnapID: fakeSnapID(info.SnapName()), 4896 Revision: snap.R(1), 4897 } 4898 si2 := &snap.SideInfo{ 4899 RealName: info.SnapName(), 4900 SnapID: fakeSnapID(info.SnapName()), 4901 Revision: snap.R(2), 4902 } 4903 snapstate.Set(st, info.InstanceName(), &snapstate.SnapState{ 4904 Active: true, 4905 Sequence: []*snap.SideInfo{si1, si2}, 4906 Current: snap.R(2), 4907 SnapType: string(info.Type()), 4908 }) 4909 4910 // now refresh from rev2 to the local rev1 4911 revOpts := &snapstate.RevisionOptions{Revision: snap.R(revStr)} 4912 ts, err := snapstate.Update(st, "some-snap", revOpts, 0, snapstate.Flags{}) 4913 c.Assert(err, IsNil) 4914 4915 chg := st.NewChange("refresh", "...") 4916 chg.AddAll(ts) 4917 4918 st.Unlock() 4919 err = ms.o.Settle(settleTimeout) 4920 st.Lock() 4921 c.Assert(err, IsNil) 4922 c.Check(chg.Err(), IsNil) 4923 c.Check(chg.Status(), Equals, state.DoneStatus) 4924 4925 // the snap file is in the right place 4926 c.Check(info.MountFile(), testutil.FilePresent) 4927 4928 var snapst snapstate.SnapState 4929 snapstate.Get(st, "some-snap", &snapst) 4930 info, err = snapst.CurrentInfo() 4931 c.Assert(err, IsNil) 4932 c.Assert(info.Revision, Equals, snap.R(1)) 4933 } 4934 4935 func (ms *mgrsSuite) TestRefreshSimpleSameRevFromLocalFile(c *C) { 4936 // the "some-snap" in rev1 4937 snapYaml := "name: some-snap\nversion: 1.0" 4938 revStr := "1" 4939 4940 // pretend we got a temp snap file from e.g. the snapd daemon 4941 tmpSnapFile := makeTestSnap(c, snapYaml) 4942 4943 st := ms.o.State() 4944 st.Lock() 4945 defer st.Unlock() 4946 4947 // and some-snap:rev1 is also installed 4948 info := ms.mockInstalledSnapWithRevAndFiles(c, snapYaml, snap.R(revStr), nil) 4949 4950 // now refresh from rev1 to rev1 4951 flags := snapstate.Flags{RemoveSnapPath: true} 4952 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "some-snap", Revision: snap.R(revStr)}, tmpSnapFile, "", "", flags) 4953 c.Assert(err, IsNil) 4954 4955 chg := st.NewChange("refresh", "...") 4956 chg.AddAll(ts) 4957 4958 st.Unlock() 4959 err = ms.o.Settle(settleTimeout) 4960 st.Lock() 4961 c.Assert(err, IsNil) 4962 c.Check(chg.Err(), IsNil) 4963 c.Check(chg.Status(), Equals, state.DoneStatus) 4964 4965 // the temp file got cleaned up 4966 snapsup, err := snapstate.TaskSnapSetup(chg.Tasks()[0]) 4967 c.Assert(err, IsNil) 4968 c.Check(snapsup.Flags.RemoveSnapPath, Equals, true) 4969 c.Check(snapsup.SnapPath, testutil.FileAbsent) 4970 4971 // the snap file is in the right place 4972 c.Check(info.MountFile(), testutil.FilePresent) 4973 4974 var snapst snapstate.SnapState 4975 snapstate.Get(st, "some-snap", &snapst) 4976 info, err = snapst.CurrentInfo() 4977 c.Assert(err, IsNil) 4978 c.Assert(info.Revision, Equals, snap.R(1)) 4979 } 4980 4981 func (ms *mgrsSuite) TestRefreshSimpleRevertToLocalFromLocalFile(c *C) { 4982 // the "some-snap" in rev1 4983 snapYaml := "name: some-snap\nversion: 1.0" 4984 revStr := "1" 4985 4986 // pretend we got a temp snap file from e.g. the snapd daemon 4987 tmpSnapFile := makeTestSnap(c, snapYaml) 4988 4989 st := ms.o.State() 4990 st.Lock() 4991 defer st.Unlock() 4992 4993 // and some-snap at both rev1, rev2 are installed 4994 info := snaptest.MockSnapWithFiles(c, snapYaml, &snap.SideInfo{Revision: snap.R(1)}, nil) 4995 snaptest.MockSnapWithFiles(c, snapYaml, &snap.SideInfo{Revision: snap.R(2)}, nil) 4996 si1 := &snap.SideInfo{ 4997 RealName: info.SnapName(), 4998 SnapID: fakeSnapID(info.SnapName()), 4999 Revision: snap.R(1), 5000 } 5001 si2 := &snap.SideInfo{ 5002 RealName: info.SnapName(), 5003 SnapID: fakeSnapID(info.SnapName()), 5004 Revision: snap.R(2), 5005 } 5006 snapstate.Set(st, info.InstanceName(), &snapstate.SnapState{ 5007 Active: true, 5008 Sequence: []*snap.SideInfo{si1, si2}, 5009 Current: snap.R(2), 5010 SnapType: string(info.Type()), 5011 }) 5012 5013 // now refresh from rev2 to rev1 5014 flags := snapstate.Flags{RemoveSnapPath: true} 5015 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "some-snap", Revision: snap.R(revStr)}, tmpSnapFile, "", "", flags) 5016 c.Assert(err, IsNil) 5017 5018 chg := st.NewChange("refresh", "...") 5019 chg.AddAll(ts) 5020 5021 st.Unlock() 5022 err = ms.o.Settle(settleTimeout) 5023 st.Lock() 5024 c.Assert(err, IsNil) 5025 c.Check(chg.Err(), IsNil) 5026 c.Check(chg.Status(), Equals, state.DoneStatus) 5027 5028 // the temp file got cleaned up 5029 snapsup, err := snapstate.TaskSnapSetup(chg.Tasks()[0]) 5030 c.Assert(err, IsNil) 5031 c.Check(snapsup.Flags.RemoveSnapPath, Equals, true) 5032 c.Check(snapsup.SnapPath, testutil.FileAbsent) 5033 5034 // the snap file is in the right place 5035 c.Check(info.MountFile(), testutil.FilePresent) 5036 5037 var snapst snapstate.SnapState 5038 snapstate.Get(st, "some-snap", &snapst) 5039 info, err = snapst.CurrentInfo() 5040 c.Assert(err, IsNil) 5041 c.Assert(info.Revision, Equals, snap.R(1)) 5042 } 5043 5044 type kernelSuite struct { 5045 baseMgrsSuite 5046 5047 bloader *boottest.Bootenv16 5048 } 5049 5050 var _ = Suite(&kernelSuite{}) 5051 5052 func (s *kernelSuite) SetUpTest(c *C) { 5053 s.baseMgrsSuite.SetUpTest(c) 5054 5055 s.bloader = boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 5056 s.bloader.SetBootKernel("pc-kernel_1.snap") 5057 s.bloader.SetBootBase("core_1.snap") 5058 bootloader.Force(s.bloader) 5059 s.AddCleanup(func() { bootloader.Force(nil) }) 5060 5061 restore := release.MockOnClassic(false) 5062 s.AddCleanup(restore) 5063 mockServer := s.mockStore(c) 5064 s.AddCleanup(mockServer.Close) 5065 5066 st := s.o.State() 5067 st.Lock() 5068 defer st.Unlock() 5069 5070 // create/set custom model assertion 5071 model := s.brands.Model("can0nical", "my-model", modelDefaults) 5072 devicestatetest.SetDevice(st, &auth.DeviceState{ 5073 Brand: "can0nical", 5074 Model: "my-model", 5075 Serial: "serialserialserial", 5076 }) 5077 err := assertstate.Add(st, model) 5078 c.Assert(err, IsNil) 5079 s.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 5080 5081 // make a mock "pc-kernel" kernel 5082 si := &snap.SideInfo{RealName: "pc-kernel", SnapID: fakeSnapID("pc-kernel"), Revision: snap.R(1)} 5083 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 5084 Active: true, 5085 Sequence: []*snap.SideInfo{si}, 5086 Current: snap.R(1), 5087 SnapType: "kernel", 5088 }) 5089 snaptest.MockSnapWithFiles(c, "name: pc-kernel\ntype: kernel\nversion: 1.0", si, nil) 5090 5091 // make a mock "pc" gadget 5092 si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 5093 gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget" 5094 snapstate.Set(st, "pc", &snapstate.SnapState{ 5095 Active: true, 5096 Sequence: []*snap.SideInfo{si2}, 5097 Current: snap.R(1), 5098 SnapType: "gadget", 5099 }) 5100 gadgetYaml := ` 5101 volumes: 5102 volume-id: 5103 bootloader: grub 5104 ` 5105 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{ 5106 {"meta/gadget.yaml", gadgetYaml}, 5107 }) 5108 5109 // add some store snaps 5110 const kernelYaml = `name: pc-kernel 5111 type: kernel 5112 version: 2.0` 5113 snapPath, _ := s.makeStoreTestSnap(c, kernelYaml, "2") 5114 s.serveSnap(snapPath, "2") 5115 5116 const brandKernelYaml = `name: brand-kernel 5117 type: kernel 5118 version: 1.0` 5119 s.prereqSnapAssertions(c, map[string]interface{}{ 5120 "snap-name": "brand-kernel", 5121 "publisher-id": "can0nical", 5122 }) 5123 snapPath, _ = s.makeStoreTestSnap(c, brandKernelYaml, "2") 5124 s.serveSnap(snapPath, "2") 5125 5126 s.prereqSnapAssertions(c, map[string]interface{}{ 5127 "snap-name": "foo", 5128 }) 5129 snapPath, _ = s.makeStoreTestSnap(c, `{name: "foo", version: 1.0}`, "1") 5130 s.serveSnap(snapPath, "1") 5131 } 5132 5133 func (s *kernelSuite) TestRemodelSwitchKernelTrack(c *C) { 5134 st := s.o.State() 5135 st.Lock() 5136 defer st.Unlock() 5137 5138 // create a new model 5139 newModel := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5140 "kernel": "pc-kernel=18", 5141 "revision": "1", 5142 "required-snaps": []interface{}{"foo"}, 5143 }) 5144 5145 chg, err := devicestate.Remodel(st, newModel) 5146 c.Assert(err, IsNil) 5147 5148 st.Unlock() 5149 err = s.o.Settle(settleTimeout) 5150 st.Lock() 5151 c.Assert(err, IsNil) 5152 5153 // system waits for a restart because of the new kernel 5154 t := findKind(chg, "auto-connect") 5155 c.Assert(t, NotNil) 5156 c.Assert(t.Status(), Equals, state.DoingStatus) 5157 5158 // simulate successful restart happened 5159 s.mockSuccessfulReboot(c, s.bloader, []snap.Type{snap.TypeKernel}) 5160 5161 // continue 5162 st.Unlock() 5163 err = s.o.Settle(settleTimeout) 5164 st.Lock() 5165 c.Assert(err, IsNil) 5166 5167 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 5168 5169 // ensure tasks were run in the right order 5170 tasks := chg.Tasks() 5171 sort.Sort(byReadyTime(tasks)) 5172 5173 // first all downloads/checks in sequential order 5174 var i int 5175 i += validateDownloadCheckTasks(c, tasks[i:], "pc-kernel", "2", "18") 5176 i += validateDownloadCheckTasks(c, tasks[i:], "foo", "1", "stable") 5177 5178 // then all installs in sequential order 5179 i += validateRefreshTasks(c, tasks[i:], "pc-kernel", "2", isKernel) 5180 i += validateInstallTasks(c, tasks[i:], "foo", "1", 0) 5181 5182 // ensure that we only have the tasks we checked (plus the one 5183 // extra "set-model" task) 5184 c.Assert(tasks, HasLen, i+1) 5185 } 5186 5187 func (ms *kernelSuite) TestRemodelSwitchToDifferentKernel(c *C) { 5188 st := ms.o.State() 5189 st.Lock() 5190 defer st.Unlock() 5191 5192 // create a new model 5193 newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5194 "kernel": "brand-kernel", 5195 "revision": "1", 5196 "required-snaps": []interface{}{"foo"}, 5197 }) 5198 5199 chg, err := devicestate.Remodel(st, newModel) 5200 c.Assert(err, IsNil) 5201 5202 st.Unlock() 5203 err = ms.o.Settle(settleTimeout) 5204 st.Lock() 5205 c.Assert(err, IsNil) 5206 c.Assert(chg.Err(), IsNil) 5207 5208 // system waits for a restart because of the new kernel 5209 t := findKind(chg, "auto-connect") 5210 c.Assert(t, NotNil) 5211 c.Assert(t.Status(), Equals, state.DoingStatus) 5212 5213 // check that the system tries to boot the new brand kernel 5214 c.Assert(ms.bloader.BootVars, DeepEquals, map[string]string{ 5215 "snap_core": "core_1.snap", 5216 "snap_kernel": "pc-kernel_1.snap", 5217 "snap_try_kernel": "brand-kernel_2.snap", 5218 "snap_mode": boot.TryStatus, 5219 "snap_try_core": "", 5220 }) 5221 // simulate successful system-restart bootenv updates (those 5222 // vars will be cleared by snapd on a restart) 5223 ms.mockSuccessfulReboot(c, ms.bloader, []snap.Type{snap.TypeKernel}) 5224 // bootvars are as expected 5225 c.Assert(ms.bloader.BootVars, DeepEquals, map[string]string{ 5226 "snap_core": "core_1.snap", 5227 "snap_kernel": "brand-kernel_2.snap", 5228 "snap_try_core": "", 5229 "snap_try_kernel": "", 5230 "snap_mode": boot.DefaultStatus, 5231 }) 5232 5233 // continue 5234 st.Unlock() 5235 err = ms.o.Settle(settleTimeout) 5236 st.Lock() 5237 c.Assert(err, IsNil) 5238 5239 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 5240 5241 // bootvars are as expected (i.e. nothing has changed since this 5242 // test simulated that we booted successfully) 5243 c.Assert(ms.bloader.BootVars, DeepEquals, map[string]string{ 5244 "snap_core": "core_1.snap", 5245 "snap_kernel": "brand-kernel_2.snap", 5246 "snap_try_kernel": "", 5247 "snap_try_core": "", 5248 "snap_mode": boot.DefaultStatus, 5249 }) 5250 5251 // ensure tasks were run in the right order 5252 tasks := chg.Tasks() 5253 sort.Sort(byReadyTime(tasks)) 5254 5255 // first all downloads/checks in sequential order 5256 var i int 5257 i += validateDownloadCheckTasks(c, tasks[i:], "brand-kernel", "2", "stable") 5258 i += validateDownloadCheckTasks(c, tasks[i:], "foo", "1", "stable") 5259 5260 // then all installs in sequential order 5261 i += validateInstallTasks(c, tasks[i:], "brand-kernel", "2", isKernel) 5262 i += validateInstallTasks(c, tasks[i:], "foo", "1", 0) 5263 5264 // ensure that we only have the tasks we checked (plus the one 5265 // extra "set-model" task) 5266 c.Assert(tasks, HasLen, i+1) 5267 5268 // ensure we did not try device registration 5269 for _, t := range st.Tasks() { 5270 if t.Kind() == "request-serial" { 5271 c.Fatalf("test should not create a request-serial task but did") 5272 } 5273 } 5274 } 5275 5276 func (ms *kernelSuite) TestRemodelSwitchToDifferentKernelUndo(c *C) { 5277 st := ms.o.State() 5278 st.Lock() 5279 defer st.Unlock() 5280 5281 // create a new model 5282 newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5283 "kernel": "brand-kernel", 5284 "revision": "1", 5285 "required-snaps": []interface{}{"foo"}, 5286 }) 5287 5288 devicestate.InjectSetModelError(fmt.Errorf("boom")) 5289 defer devicestate.InjectSetModelError(nil) 5290 5291 chg, err := devicestate.Remodel(st, newModel) 5292 c.Assert(err, IsNil) 5293 5294 st.Unlock() 5295 err = ms.o.Settle(settleTimeout) 5296 st.Lock() 5297 c.Assert(err, IsNil) 5298 c.Assert(chg.Err(), IsNil) 5299 5300 // system waits for a restart because of the new kernel 5301 t := findKind(chg, "auto-connect") 5302 c.Assert(t, NotNil) 5303 c.Assert(t.Status(), Equals, state.DoingStatus) 5304 5305 // simulate successful restart happened 5306 ms.mockSuccessfulReboot(c, ms.bloader, []snap.Type{snap.TypeKernel}) 5307 5308 // continue 5309 st.Unlock() 5310 err = ms.o.Settle(settleTimeout) 5311 st.Lock() 5312 c.Assert(err, IsNil) 5313 5314 // the change was not successful 5315 c.Assert(chg.Status(), Equals, state.ErrorStatus) 5316 5317 // and we are in restarting state 5318 restarting, restartType := st.Restarting() 5319 c.Check(restarting, Equals, true) 5320 c.Check(restartType, Equals, state.RestartSystem) 5321 5322 // and the undo gave us our old kernel back 5323 c.Assert(ms.bloader.BootVars, DeepEquals, map[string]string{ 5324 "snap_core": "core_1.snap", 5325 "snap_try_core": "", 5326 "snap_try_kernel": "pc-kernel_1.snap", 5327 "snap_kernel": "brand-kernel_2.snap", 5328 "snap_mode": boot.TryStatus, 5329 }) 5330 } 5331 5332 func (ms *kernelSuite) TestRemodelSwitchToDifferentKernelUndoOnRollback(c *C) { 5333 st := ms.o.State() 5334 st.Lock() 5335 defer st.Unlock() 5336 5337 // create a new model 5338 newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5339 "kernel": "brand-kernel", 5340 "revision": "1", 5341 "required-snaps": []interface{}{"foo"}, 5342 }) 5343 5344 devicestate.InjectSetModelError(fmt.Errorf("boom")) 5345 defer devicestate.InjectSetModelError(nil) 5346 5347 chg, err := devicestate.Remodel(st, newModel) 5348 c.Assert(err, IsNil) 5349 5350 st.Unlock() 5351 err = ms.o.Settle(settleTimeout) 5352 st.Lock() 5353 c.Assert(err, IsNil) 5354 c.Assert(chg.Err(), IsNil) 5355 5356 // system waits for a restart because of the new kernel 5357 t := findKind(chg, "auto-connect") 5358 c.Assert(t, NotNil) 5359 c.Assert(t.Status(), Equals, state.DoingStatus) 5360 5361 // simulate rollback of the kernel during reboot 5362 ms.mockRollbackAcrossReboot(c, ms.bloader, []snap.Type{snap.TypeKernel}) 5363 5364 // continue 5365 st.Unlock() 5366 err = ms.o.Settle(settleTimeout) 5367 st.Lock() 5368 c.Assert(err, IsNil) 5369 5370 // the change was not successful 5371 c.Assert(chg.Status(), Equals, state.ErrorStatus) 5372 5373 // and we are *not* in restarting state 5374 restarting, _ := st.Restarting() 5375 c.Check(restarting, Equals, false) 5376 5377 // and the undo gave us our old kernel back 5378 c.Assert(ms.bloader.BootVars, DeepEquals, map[string]string{ 5379 "snap_core": "core_1.snap", 5380 "snap_try_core": "", 5381 "snap_kernel": "pc-kernel_1.snap", 5382 "snap_try_kernel": "", 5383 "snap_mode": boot.DefaultStatus, 5384 }) 5385 } 5386 5387 func (s *mgrsSuite) TestRemodelStoreSwitch(c *C) { 5388 s.prereqSnapAssertions(c, map[string]interface{}{ 5389 "snap-name": "foo", 5390 }) 5391 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 1.0}", "foo"), "1") 5392 s.serveSnap(snapPath, "1") 5393 5394 // track the creation of new DeviceAndAutContext (for new Store) 5395 newDAC := false 5396 5397 mockServer := s.mockStore(c) 5398 defer mockServer.Close() 5399 5400 st := s.o.State() 5401 st.Lock() 5402 defer st.Unlock() 5403 5404 s.checkDeviceAndAuthContext = func(dac store.DeviceAndAuthContext) { 5405 // the DeviceAndAuthContext assumes state is unlocked 5406 st.Unlock() 5407 defer st.Lock() 5408 c.Check(dac, NotNil) 5409 stoID, err := dac.StoreID("") 5410 c.Assert(err, IsNil) 5411 c.Check(stoID, Equals, "switched-store") 5412 newDAC = true 5413 } 5414 5415 // create/set custom model assertion 5416 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 5417 5418 model := s.brands.Model("my-brand", "my-model", modelDefaults) 5419 5420 // setup model assertion 5421 err := assertstate.Add(st, model) 5422 c.Assert(err, IsNil) 5423 5424 // have a serial as well 5425 kpMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir) 5426 c.Assert(err, IsNil) 5427 err = kpMgr.Put(deviceKey) 5428 c.Assert(err, IsNil) 5429 5430 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 5431 c.Assert(err, IsNil) 5432 serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{ 5433 "authority-id": "my-brand", 5434 "brand-id": "my-brand", 5435 "model": "my-model", 5436 "serial": "store-switch-serial", 5437 "device-key": string(encDevKey), 5438 "device-key-sha3-384": deviceKey.PublicKey().ID(), 5439 "timestamp": time.Now().Format(time.RFC3339), 5440 }, nil, "") 5441 c.Assert(err, IsNil) 5442 err = assertstate.Add(st, serial) 5443 c.Assert(err, IsNil) 5444 5445 devicestatetest.SetDevice(st, &auth.DeviceState{ 5446 Brand: "my-brand", 5447 Model: "my-model", 5448 KeyID: deviceKey.PublicKey().ID(), 5449 Serial: "store-switch-serial", 5450 }) 5451 5452 // create a new model 5453 newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 5454 "store": "switched-store", 5455 "required-snaps": []interface{}{"foo"}, 5456 "revision": "1", 5457 }) 5458 5459 s.expectedSerial = "store-switch-serial" 5460 s.expectedStore = "switched-store" 5461 s.sessionMacaroon = "switched-store-session" 5462 5463 chg, err := devicestate.Remodel(st, newModel) 5464 c.Assert(err, IsNil) 5465 5466 st.Unlock() 5467 err = s.o.Settle(settleTimeout) 5468 st.Lock() 5469 c.Assert(err, IsNil) 5470 5471 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 5472 5473 // the new required-snap "foo" is installed 5474 var snapst snapstate.SnapState 5475 err = snapstate.Get(st, "foo", &snapst) 5476 c.Assert(err, IsNil) 5477 5478 // and marked required 5479 c.Check(snapst.Required, Equals, true) 5480 5481 // a new store was made 5482 c.Check(newDAC, Equals, true) 5483 5484 // we have a session with the new store 5485 device, err := devicestatetest.Device(st) 5486 c.Assert(err, IsNil) 5487 c.Check(device.Serial, Equals, "store-switch-serial") 5488 c.Check(device.SessionMacaroon, Equals, "switched-store-session") 5489 } 5490 5491 func (s *mgrsSuite) TestRemodelSwitchGadgetTrack(c *C) { 5492 bloader := bootloadertest.Mock("mock", c.MkDir()) 5493 bootloader.Force(bloader) 5494 defer bootloader.Force(nil) 5495 5496 restore := release.MockOnClassic(false) 5497 defer restore() 5498 5499 mockServer := s.mockStore(c) 5500 defer mockServer.Close() 5501 5502 st := s.o.State() 5503 st.Lock() 5504 defer st.Unlock() 5505 5506 si := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 5507 snapstate.Set(st, "pc", &snapstate.SnapState{ 5508 Active: true, 5509 Sequence: []*snap.SideInfo{si}, 5510 Current: snap.R(1), 5511 SnapType: "gadget", 5512 }) 5513 gadgetSnapYaml := "name: pc\nversion: 2.0\ntype: gadget" 5514 gadgetYaml := ` 5515 volumes: 5516 volume-id: 5517 bootloader: grub 5518 ` 5519 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si, [][]string{ 5520 {"meta/gadget.yaml", gadgetYaml}, 5521 }) 5522 snapPath, _ := s.makeStoreTestSnapWithFiles(c, gadgetSnapYaml, "2", [][]string{ 5523 {"meta/gadget.yaml", gadgetYaml}, 5524 }) 5525 s.serveSnap(snapPath, "2") 5526 5527 // create/set custom model assertion 5528 model := s.brands.Model("can0nical", "my-model", modelDefaults) 5529 5530 // setup model assertion 5531 devicestatetest.SetDevice(st, &auth.DeviceState{ 5532 Brand: "can0nical", 5533 Model: "my-model", 5534 Serial: "serialserialserial", 5535 }) 5536 err := assertstate.Add(st, model) 5537 c.Assert(err, IsNil) 5538 s.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 5539 5540 // create a new model 5541 newModel := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5542 "gadget": "pc=18", 5543 "revision": "1", 5544 }) 5545 5546 chg, err := devicestate.Remodel(st, newModel) 5547 c.Assert(err, IsNil) 5548 5549 st.Unlock() 5550 err = s.o.Settle(settleTimeout) 5551 st.Lock() 5552 c.Assert(err, IsNil) 5553 c.Assert(chg.Err(), IsNil) 5554 5555 // ensure tasks were run in the right order 5556 tasks := chg.Tasks() 5557 sort.Sort(byReadyTime(tasks)) 5558 5559 // first all downloads/checks in sequential order 5560 var i int 5561 i += validateDownloadCheckTasks(c, tasks[i:], "pc", "2", "18") 5562 5563 // then all installs in sequential order 5564 i += validateRefreshTasks(c, tasks[i:], "pc", "2", isGadget) 5565 5566 // ensure that we only have the tasks we checked (plus the one 5567 // extra "set-model" task) 5568 c.Assert(tasks, HasLen, i+1) 5569 } 5570 5571 type mockUpdater struct{} 5572 5573 func (m *mockUpdater) Backup() error { return nil } 5574 5575 func (m *mockUpdater) Rollback() error { return nil } 5576 5577 func (m *mockUpdater) Update() error { return nil } 5578 5579 func (s *mgrsSuite) TestRemodelSwitchToDifferentGadget(c *C) { 5580 bloader := bootloadertest.Mock("mock", c.MkDir()) 5581 bootloader.Force(bloader) 5582 defer bootloader.Force(nil) 5583 restore := release.MockOnClassic(false) 5584 defer restore() 5585 5586 mockServer := s.mockStore(c) 5587 defer mockServer.Close() 5588 5589 st := s.o.State() 5590 st.Lock() 5591 defer st.Unlock() 5592 5593 si := &snap.SideInfo{RealName: "core18", SnapID: fakeSnapID("core18"), Revision: snap.R(1)} 5594 snapstate.Set(st, "core18", &snapstate.SnapState{ 5595 Active: true, 5596 Sequence: []*snap.SideInfo{si}, 5597 Current: snap.R(1), 5598 SnapType: "base", 5599 }) 5600 si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 5601 gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget" 5602 snapstate.Set(st, "pc", &snapstate.SnapState{ 5603 Active: true, 5604 Sequence: []*snap.SideInfo{si2}, 5605 Current: snap.R(1), 5606 SnapType: "gadget", 5607 }) 5608 gadgetYaml := ` 5609 volumes: 5610 volume-id: 5611 bootloader: grub 5612 structure: 5613 - name: foo 5614 type: bare 5615 size: 1M 5616 content: 5617 - image: foo.img 5618 ` 5619 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{ 5620 {"meta/gadget.yaml", gadgetYaml}, 5621 {"foo.img", "foo"}, 5622 }) 5623 5624 // add new gadget "other-pc" snap to fake store 5625 const otherPcYaml = `name: other-pc 5626 type: gadget 5627 version: 2` 5628 s.prereqSnapAssertions(c, map[string]interface{}{ 5629 "snap-name": "other-pc", 5630 "publisher-id": "can0nical", 5631 }) 5632 otherGadgetYaml := ` 5633 volumes: 5634 volume-id: 5635 bootloader: grub 5636 structure: 5637 - name: foo 5638 type: bare 5639 size: 1M 5640 content: 5641 - image: new-foo.img 5642 ` 5643 snapPath, _ := s.makeStoreTestSnapWithFiles(c, otherPcYaml, "2", [][]string{ 5644 // use a compatible gadget YAML 5645 {"meta/gadget.yaml", otherGadgetYaml}, 5646 {"new-foo.img", "new foo"}, 5647 }) 5648 s.serveSnap(snapPath, "2") 5649 5650 updaterForStructureCalls := 0 5651 restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, rootDir, rollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 5652 updaterForStructureCalls++ 5653 c.Assert(ps.Name, Equals, "foo") 5654 return &mockUpdater{}, nil 5655 }) 5656 defer restore() 5657 5658 // create/set custom model assertion 5659 model := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5660 "gadget": "pc", 5661 }) 5662 5663 // setup model assertion 5664 devicestatetest.SetDevice(st, &auth.DeviceState{ 5665 Brand: "can0nical", 5666 Model: "my-model", 5667 Serial: "serialserialserial", 5668 }) 5669 err := assertstate.Add(st, model) 5670 c.Assert(err, IsNil) 5671 s.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 5672 5673 // create a new model 5674 newModel := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5675 "gadget": "other-pc=18", 5676 "revision": "1", 5677 }) 5678 5679 chg, err := devicestate.Remodel(st, newModel) 5680 c.Assert(err, IsNil) 5681 5682 st.Unlock() 5683 err = s.o.Settle(settleTimeout) 5684 st.Lock() 5685 c.Assert(err, IsNil) 5686 c.Assert(chg.Err(), IsNil) 5687 5688 // gadget updater was set up 5689 c.Check(updaterForStructureCalls, Equals, 1) 5690 5691 // gadget update requests a restart 5692 restarting, _ := st.Restarting() 5693 c.Check(restarting, Equals, true) 5694 5695 // simulate successful restart happened 5696 state.MockRestarting(st, state.RestartUnset) 5697 5698 st.Unlock() 5699 err = s.o.Settle(settleTimeout) 5700 st.Lock() 5701 c.Assert(err, IsNil) 5702 5703 // ensure tasks were run in the right order 5704 tasks := chg.Tasks() 5705 sort.Sort(byReadyTime(tasks)) 5706 5707 // first all downloads/checks 5708 var i int 5709 i += validateDownloadCheckTasks(c, tasks[i:], "other-pc", "2", "18") 5710 5711 // then all installs 5712 i += validateInstallTasks(c, tasks[i:], "other-pc", "2", isGadget) 5713 5714 // ensure that we only have the tasks we checked (plus the one 5715 // extra "set-model" task) 5716 c.Assert(tasks, HasLen, i+1) 5717 } 5718 5719 func (s *mgrsSuite) TestRemodelSwitchToIncompatibleGadget(c *C) { 5720 bloader := bootloadertest.Mock("mock", c.MkDir()) 5721 bootloader.Force(bloader) 5722 defer bootloader.Force(nil) 5723 restore := release.MockOnClassic(false) 5724 defer restore() 5725 5726 mockServer := s.mockStore(c) 5727 defer mockServer.Close() 5728 5729 st := s.o.State() 5730 st.Lock() 5731 defer st.Unlock() 5732 5733 si := &snap.SideInfo{RealName: "core18", SnapID: fakeSnapID("core18"), Revision: snap.R(1)} 5734 snapstate.Set(st, "core18", &snapstate.SnapState{ 5735 Active: true, 5736 Sequence: []*snap.SideInfo{si}, 5737 Current: snap.R(1), 5738 SnapType: "base", 5739 }) 5740 si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)} 5741 gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget" 5742 snapstate.Set(st, "pc", &snapstate.SnapState{ 5743 Active: true, 5744 Sequence: []*snap.SideInfo{si2}, 5745 Current: snap.R(1), 5746 SnapType: "gadget", 5747 }) 5748 gadgetYaml := ` 5749 volumes: 5750 volume-id: 5751 bootloader: grub 5752 structure: 5753 - name: foo 5754 type: 00000000-0000-0000-0000-0000deadcafe 5755 size: 10M 5756 ` 5757 snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{ 5758 {"meta/gadget.yaml", gadgetYaml}, 5759 }) 5760 5761 // add new gadget "other-pc" snap to fake store 5762 const otherPcYaml = `name: other-pc 5763 type: gadget 5764 version: 2` 5765 // new gadget layout is incompatible, a structure that exited before has 5766 // a different size now 5767 otherGadgetYaml := ` 5768 volumes: 5769 volume-id: 5770 bootloader: grub 5771 structure: 5772 - name: foo 5773 type: 00000000-0000-0000-0000-0000deadcafe 5774 size: 20M 5775 ` 5776 s.prereqSnapAssertions(c, map[string]interface{}{ 5777 "snap-name": "other-pc", 5778 "publisher-id": "can0nical", 5779 }) 5780 snapPath, _ := s.makeStoreTestSnapWithFiles(c, otherPcYaml, "2", [][]string{ 5781 {"meta/gadget.yaml", otherGadgetYaml}, 5782 }) 5783 s.serveSnap(snapPath, "2") 5784 5785 // create/set custom model assertion 5786 model := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5787 "gadget": "pc", 5788 }) 5789 5790 // setup model assertion 5791 devicestatetest.SetDevice(st, &auth.DeviceState{ 5792 Brand: "can0nical", 5793 Model: "my-model", 5794 Serial: "serialserialserial", 5795 }) 5796 err := assertstate.Add(st, model) 5797 c.Assert(err, IsNil) 5798 s.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 5799 5800 // create a new model 5801 newModel := s.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 5802 "gadget": "other-pc=18", 5803 "revision": "1", 5804 }) 5805 5806 chg, err := devicestate.Remodel(st, newModel) 5807 c.Assert(err, IsNil) 5808 5809 st.Unlock() 5810 err = s.o.Settle(settleTimeout) 5811 st.Lock() 5812 c.Assert(err, IsNil) 5813 c.Assert(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*cannot remodel to an incompatible gadget: .*cannot change structure size.*`) 5814 } 5815 5816 func (s *mgrsSuite) TestHappyDeviceRegistrationWithPrepareDeviceHook(c *C) { 5817 // just to 404 locally eager account-key requests 5818 mockStoreServer := s.mockStore(c) 5819 defer mockStoreServer.Close() 5820 5821 model := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 5822 "gadget": "gadget", 5823 }) 5824 5825 // reset as seeded but not registered 5826 // shortcut: have already device key 5827 kpMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir) 5828 c.Assert(err, IsNil) 5829 err = kpMgr.Put(deviceKey) 5830 c.Assert(err, IsNil) 5831 5832 st := s.o.State() 5833 st.Lock() 5834 defer st.Unlock() 5835 5836 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 5837 devicestatetest.SetDevice(st, &auth.DeviceState{ 5838 Brand: "my-brand", 5839 Model: "my-model", 5840 KeyID: deviceKey.PublicKey().ID(), 5841 }) 5842 err = assertstate.Add(st, model) 5843 c.Assert(err, IsNil) 5844 5845 signSerial := func(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (serial asserts.Assertion, ancillary []asserts.Assertion, err error) { 5846 brandID := headers["brand-id"].(string) 5847 model := headers["model"].(string) 5848 c.Check(brandID, Equals, "my-brand") 5849 c.Check(model, Equals, "my-model") 5850 headers["authority-id"] = brandID 5851 a, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, headers, body, "") 5852 return a, nil, err 5853 } 5854 5855 bhv := &devicestatetest.DeviceServiceBehavior{ 5856 ReqID: "REQID-1", 5857 RequestIDURLPath: "/svc/request-id", 5858 SerialURLPath: "/svc/serial", 5859 SignSerial: signSerial, 5860 } 5861 5862 mockServer := devicestatetest.MockDeviceService(c, bhv) 5863 defer mockServer.Close() 5864 5865 pDBhv := &devicestatetest.PrepareDeviceBehavior{ 5866 DeviceSvcURL: mockServer.URL + "/svc/", 5867 Headers: map[string]string{ 5868 "x-extra-header": "extra", 5869 }, 5870 RegBody: map[string]string{ 5871 "mac": "00:00:00:00:ff:00", 5872 }, 5873 ProposedSerial: "12000", 5874 } 5875 5876 r := devicestatetest.MockGadget(c, st, "gadget", snap.R(2), pDBhv) 5877 defer r() 5878 5879 // run the whole device registration process 5880 st.Unlock() 5881 err = s.o.Settle(settleTimeout) 5882 st.Lock() 5883 c.Assert(err, IsNil) 5884 5885 var becomeOperational *state.Change 5886 for _, chg := range st.Changes() { 5887 if chg.Kind() == "become-operational" { 5888 becomeOperational = chg 5889 break 5890 } 5891 } 5892 c.Assert(becomeOperational, NotNil) 5893 5894 c.Check(becomeOperational.Status().Ready(), Equals, true) 5895 c.Check(becomeOperational.Err(), IsNil) 5896 5897 device, err := devicestatetest.Device(st) 5898 c.Assert(err, IsNil) 5899 c.Check(device.Brand, Equals, "my-brand") 5900 c.Check(device.Model, Equals, "my-model") 5901 c.Check(device.Serial, Equals, "12000") 5902 5903 a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{ 5904 "brand-id": "my-brand", 5905 "model": "my-model", 5906 "serial": "12000", 5907 }) 5908 c.Assert(err, IsNil) 5909 serial := a.(*asserts.Serial) 5910 5911 var details map[string]interface{} 5912 err = yaml.Unmarshal(serial.Body(), &details) 5913 c.Assert(err, IsNil) 5914 5915 c.Check(details, DeepEquals, map[string]interface{}{ 5916 "mac": "00:00:00:00:ff:00", 5917 }) 5918 5919 c.Check(serial.DeviceKey().ID(), Equals, device.KeyID) 5920 } 5921 5922 func (s *mgrsSuite) TestRemodelReregistration(c *C) { 5923 s.prereqSnapAssertions(c, map[string]interface{}{ 5924 "snap-name": "foo", 5925 }) 5926 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 1.0}", "foo"), "1") 5927 s.serveSnap(snapPath, "1") 5928 5929 // track the creation of new DeviceAndAutContext (for new Store) 5930 newDAC := false 5931 5932 mockServer := s.mockStore(c) 5933 defer mockServer.Close() 5934 5935 st := s.o.State() 5936 st.Lock() 5937 defer st.Unlock() 5938 5939 s.checkDeviceAndAuthContext = func(dac store.DeviceAndAuthContext) { 5940 // the DeviceAndAuthContext assumes state is unlocked 5941 st.Unlock() 5942 defer st.Lock() 5943 c.Check(dac, NotNil) 5944 stoID, err := dac.StoreID("") 5945 c.Assert(err, IsNil) 5946 c.Check(stoID, Equals, "my-brand-substore") 5947 newDAC = true 5948 } 5949 5950 model := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 5951 "gadget": "gadget", 5952 }) 5953 5954 // setup initial device identity 5955 kpMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir) 5956 c.Assert(err, IsNil) 5957 err = kpMgr.Put(deviceKey) 5958 c.Assert(err, IsNil) 5959 5960 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 5961 devicestatetest.SetDevice(st, &auth.DeviceState{ 5962 Brand: "my-brand", 5963 Model: "my-model", 5964 KeyID: deviceKey.PublicKey().ID(), 5965 Serial: "orig-serial", 5966 }) 5967 err = assertstate.Add(st, model) 5968 c.Assert(err, IsNil) 5969 5970 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 5971 c.Assert(err, IsNil) 5972 serialHeaders := map[string]interface{}{ 5973 "brand-id": "my-brand", 5974 "model": "my-model", 5975 "serial": "orig-serial", 5976 "device-key": string(encDevKey), 5977 "device-key-sha3-384": deviceKey.PublicKey().ID(), 5978 "timestamp": time.Now().Format(time.RFC3339), 5979 } 5980 serialA, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, serialHeaders, nil, "") 5981 c.Assert(err, IsNil) 5982 serial := serialA.(*asserts.Serial) 5983 err = assertstate.Add(st, serial) 5984 c.Assert(err, IsNil) 5985 5986 signSerial := func(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (serial asserts.Assertion, ancillary []asserts.Assertion, err error) { 5987 brandID := headers["brand-id"].(string) 5988 model := headers["model"].(string) 5989 c.Check(brandID, Equals, "my-brand") 5990 c.Check(model, Equals, "other-model") 5991 headers["authority-id"] = brandID 5992 a, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, headers, body, "") 5993 return a, nil, err 5994 } 5995 5996 bhv := &devicestatetest.DeviceServiceBehavior{ 5997 ReqID: "REQID-1", 5998 RequestIDURLPath: "/svc/request-id", 5999 SerialURLPath: "/svc/serial", 6000 SignSerial: signSerial, 6001 } 6002 6003 mockDeviceService := devicestatetest.MockDeviceService(c, bhv) 6004 defer mockDeviceService.Close() 6005 6006 r := devicestatetest.MockGadget(c, st, "gadget", snap.R(2), nil) 6007 defer r() 6008 6009 // set registration config on gadget 6010 tr := config.NewTransaction(st) 6011 c.Assert(tr.Set("gadget", "device-service.url", mockDeviceService.URL+"/svc/"), IsNil) 6012 c.Assert(tr.Set("gadget", "registration.proposed-serial", "orig-serial"), IsNil) 6013 tr.Commit() 6014 6015 // run the remodel 6016 // create a new model 6017 newModel := s.brands.Model("my-brand", "other-model", modelDefaults, map[string]interface{}{ 6018 "store": "my-brand-substore", 6019 "gadget": "gadget", 6020 "required-snaps": []interface{}{"foo"}, 6021 }) 6022 6023 s.expectedSerial = "orig-serial" 6024 s.expectedStore = "my-brand-substore" 6025 s.sessionMacaroon = "other-store-session" 6026 6027 chg, err := devicestate.Remodel(st, newModel) 6028 c.Assert(err, IsNil) 6029 6030 st.Unlock() 6031 err = s.o.Settle(settleTimeout) 6032 st.Lock() 6033 c.Assert(err, IsNil) 6034 6035 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 6036 6037 device, err := devicestatetest.Device(st) 6038 c.Assert(err, IsNil) 6039 c.Check(device.Brand, Equals, "my-brand") 6040 c.Check(device.Model, Equals, "other-model") 6041 c.Check(device.Serial, Equals, "orig-serial") 6042 6043 a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{ 6044 "brand-id": "my-brand", 6045 "model": "other-model", 6046 "serial": "orig-serial", 6047 }) 6048 c.Assert(err, IsNil) 6049 serial = a.(*asserts.Serial) 6050 6051 c.Check(serial.Body(), HasLen, 0) 6052 c.Check(serial.DeviceKey().ID(), Equals, device.KeyID) 6053 6054 // the new required-snap "foo" is installed 6055 var snapst snapstate.SnapState 6056 err = snapstate.Get(st, "foo", &snapst) 6057 c.Assert(err, IsNil) 6058 6059 // and marked required 6060 c.Check(snapst.Required, Equals, true) 6061 6062 // a new store was made 6063 c.Check(newDAC, Equals, true) 6064 6065 // we have a session with the new store 6066 c.Check(device.SessionMacaroon, Equals, "other-store-session") 6067 } 6068 6069 const pcGadgetSnapYaml = ` 6070 version: 1.0 6071 name: pc 6072 type: gadget 6073 base: core20 6074 ` 6075 6076 const pcKernelSnapYaml = ` 6077 version: 1.0 6078 name: pc-kernel 6079 type: kernel 6080 ` 6081 6082 const core20SnapYaml = ` 6083 version: 1.0 6084 name: core20 6085 type: base 6086 ` 6087 6088 const snapdSnapYaml = ` 6089 version: 1.0 6090 name: snapd 6091 type: snapd 6092 ` 6093 6094 const grubBootConfig = "# Snapd-Boot-Config-Edition: 1\n" 6095 6096 var ( 6097 pcGadgetFiles = [][]string{ 6098 {"meta/gadget.yaml", pcGadgetYaml}, 6099 {"grub.conf", ""}, 6100 {"grub.conf", ""}, 6101 // SHA3-384: 21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848 6102 {"bootx64.efi", "content"}, 6103 {"grubx64.efi", "content"}, 6104 } 6105 pcKernelFiles = [][]string{ 6106 {"kernel.efi", "kernel-efi"}, 6107 } 6108 snapYamlsForRemodel = map[string]string{ 6109 "pc": pcGadgetSnapYaml, 6110 "pc-kernel": pcKernelSnapYaml, 6111 "core20": core20SnapYaml, 6112 "snapd": snapdSnapYaml, 6113 } 6114 snapFilesForRemodel = map[string][][]string{ 6115 "pc": pcGadgetFiles, 6116 "pc-kernel": pcKernelFiles, 6117 } 6118 ) 6119 6120 func (s *mgrsSuite) makeInstalledSnapInStateForRemodel(c *C, name string, rev snap.Revision) *snap.Info { 6121 si := &snap.SideInfo{ 6122 RealName: name, 6123 SnapID: fakeSnapID(name), 6124 Revision: rev, 6125 } 6126 snapInfo := snaptest.MakeSnapFileAndDir(c, snapYamlsForRemodel[name], 6127 snapFilesForRemodel[name], si) 6128 snapstate.Set(s.o.State(), name, &snapstate.SnapState{ 6129 SnapType: string(snapInfo.Type()), 6130 Active: true, 6131 Sequence: []*snap.SideInfo{si}, 6132 Current: si.Revision, 6133 }) 6134 sha3_384, size, err := asserts.SnapFileSHA3_384(snapInfo.MountFile()) 6135 c.Assert(err, IsNil) 6136 6137 snapRev := s.makeStoreSnapRevision(c, name, rev.String(), sha3_384, size) 6138 err = assertstate.Add(s.o.State(), snapRev) 6139 c.Assert(err, IsNil) 6140 return snapInfo 6141 } 6142 6143 func (s *mgrsSuite) testRemodelUC20WithRecoverySystem(c *C, encrypted bool) { 6144 restore := release.MockOnClassic(false) 6145 defer restore() 6146 restore = devicestate.AllowUC20RemodelTesting(true) 6147 defer restore() 6148 6149 // mock directories that need to be tweaked by the test 6150 c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil) 6151 c.Assert(os.MkdirAll(filepath.Base(dirs.SnapSeedDir), 0755), IsNil) 6152 // this is a bind mount in a real system 6153 c.Assert(os.Symlink(boot.InitramfsUbuntuSeedDir, dirs.SnapSeedDir), IsNil) 6154 6155 c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "proc"), 0755), IsNil) 6156 restore = osutil.MockProcCmdline(filepath.Join(dirs.GlobalRootDir, "proc/cmdline")) 6157 defer restore() 6158 6159 // mock state related to boot assets 6160 for _, env := range []string{ 6161 filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/ubuntu/grub.cfg"), 6162 filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/ubuntu/grubenv"), 6163 filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 6164 filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 6165 filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/ubuntu/grub.cfg"), 6166 filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/ubuntu/grubenv"), 6167 filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi"), 6168 filepath.Join(dirs.GlobalRootDir, "/boot/grub/grub.cfg"), 6169 filepath.Join(dirs.GlobalRootDir, "/boot/grub/grubenv"), 6170 } { 6171 c.Assert(os.MkdirAll(filepath.Dir(env), 0755), IsNil) 6172 switch filepath.Base(env) { 6173 case "grubenv": 6174 e := grubenv.NewEnv(env) 6175 c.Assert(e.Save(), IsNil) 6176 case "grub.cfg": 6177 c.Assert(ioutil.WriteFile(env, []byte(grubBootConfig), 0644), IsNil) 6178 case "grubx64.efi", "bootx64.efi": 6179 c.Assert(ioutil.WriteFile(env, []byte("content"), 0644), IsNil) 6180 default: 6181 c.Assert(ioutil.WriteFile(env, nil, 0644), IsNil) 6182 } 6183 } 6184 6185 if encrypted { 6186 // boot assets are measured on an encrypted system 6187 assetsCacheDir := filepath.Join(dirs.SnapBootAssetsDirUnder(dirs.GlobalRootDir), "grub") 6188 c.Assert(os.MkdirAll(assetsCacheDir, 0755), IsNil) 6189 for _, cachedAsset := range []string{ 6190 "grubx64.efi-21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848", 6191 "bootx64.efi-21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848", 6192 } { 6193 err := ioutil.WriteFile(filepath.Join(assetsCacheDir, cachedAsset), []byte("content"), 0644) 6194 c.Assert(err, IsNil) 6195 } 6196 } 6197 6198 // state of booted kernel 6199 c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "boot/grub/pc-kernel_2.snap"), 0755), IsNil) 6200 c.Assert(ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/boot/grub/pc-kernel_2.snap/kernel.efi"), 6201 []byte("kernel-efi"), 0755), IsNil) 6202 c.Assert(os.Symlink("pc-kernel_2.snap/kernel.efi", filepath.Join(dirs.GlobalRootDir, "boot/grub/kernel.efi")), IsNil) 6203 6204 if encrypted { 6205 stamp := filepath.Join(dirs.SnapFDEDir, "sealed-keys") 6206 c.Assert(os.MkdirAll(filepath.Dir(stamp), 0755), IsNil) 6207 c.Assert(ioutil.WriteFile(stamp, nil, 0644), IsNil) 6208 } 6209 6210 // new snaps from the store 6211 for _, name := range []string{"foo", "bar"} { 6212 s.prereqSnapAssertions(c, map[string]interface{}{ 6213 "snap-name": name, 6214 }) 6215 snapPath, _ := s.makeStoreTestSnap(c, fmt.Sprintf("{name: %s, version: 1.0, base: core20}", name), "1") 6216 s.serveSnap(snapPath, "1") 6217 } 6218 6219 mockServer := s.mockStore(c) 6220 defer mockServer.Close() 6221 6222 st := s.o.State() 6223 st.Lock() 6224 defer st.Unlock() 6225 6226 err := assertstate.Add(st, s.devAcct) 6227 c.Assert(err, IsNil) 6228 // snaps in state 6229 pcInfo := s.makeInstalledSnapInStateForRemodel(c, "pc", snap.R(1)) 6230 pcKernelInfo := s.makeInstalledSnapInStateForRemodel(c, "pc-kernel", snap.R(2)) 6231 coreInfo := s.makeInstalledSnapInStateForRemodel(c, "core20", snap.R(3)) 6232 snapdInfo := s.makeInstalledSnapInStateForRemodel(c, "snapd", snap.R(4)) 6233 6234 // state of the current model 6235 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755), IsNil) 6236 6237 // a regular UC20 model assertion 6238 uc20ModelDefaults := map[string]interface{}{ 6239 "architecture": "amd64", 6240 "base": "core20", 6241 "grade": "dangerous", 6242 "snaps": []interface{}{ 6243 map[string]interface{}{ 6244 "name": "pc-kernel", 6245 "id": fakeSnapID("pc-kernel"), 6246 "type": "kernel", 6247 "default-channel": "20", 6248 }, 6249 map[string]interface{}{ 6250 "name": "pc", 6251 "id": fakeSnapID("pc"), 6252 "type": "gadget", 6253 "default-channel": "20", 6254 }}, 6255 } 6256 6257 model := s.brands.Model("can0nical", "my-model", uc20ModelDefaults) 6258 6259 // setup model assertion 6260 devicestatetest.SetDevice(st, &auth.DeviceState{ 6261 Brand: "can0nical", 6262 Model: "my-model", 6263 Serial: "serialserialserial", 6264 }) 6265 err = assertstate.Add(st, model) 6266 c.Assert(err, IsNil) 6267 s.makeSerialAssertionInState(c, st, "can0nical", "my-model", "serialserialserial") 6268 6269 // now create a minimal uc20 seed dir with snaps/assertions 6270 seed20 := &seedtest.TestingSeed20{ 6271 SeedSnaps: seedtest.SeedSnaps{ 6272 StoreSigning: s.storeSigning, 6273 Brands: s.brands, 6274 }, 6275 SeedDir: dirs.SnapSeedDir, 6276 } 6277 restore = seed.MockTrusted(s.storeSigning.Trusted) 6278 defer restore() 6279 6280 seed20.MakeSeedWithModel(c, "1234", model, []*seedwriter.OptionsSnap{ 6281 {Path: pcInfo.MountFile()}, 6282 {Path: pcKernelInfo.MountFile()}, 6283 {Path: coreInfo.MountFile()}, 6284 {Path: snapdInfo.MountFile()}, 6285 }) 6286 6287 // create a new model 6288 newModel := s.brands.Model("can0nical", "my-model", uc20ModelDefaults, map[string]interface{}{ 6289 "snaps": []interface{}{ 6290 map[string]interface{}{ 6291 "name": "pc-kernel", 6292 "id": fakeSnapID("pc-kernel"), 6293 "type": "kernel", 6294 "default-channel": "20", 6295 }, 6296 map[string]interface{}{ 6297 "name": "pc", 6298 "id": fakeSnapID("pc"), 6299 "type": "gadget", 6300 "default-channel": "20", 6301 }, 6302 map[string]interface{}{ 6303 "name": "foo", 6304 "presence": "required", 6305 }, 6306 map[string]interface{}{ 6307 "name": "bar", 6308 "presence": "required", 6309 }}, 6310 "revision": "1", 6311 }) 6312 6313 // mock the modeenv file 6314 m := &boot.Modeenv{ 6315 Mode: "run", 6316 Base: "core20_3.snap", 6317 CurrentKernels: []string{"pc-kernel_2.snap"}, 6318 CurrentRecoverySystems: []string{"1234"}, 6319 GoodRecoverySystems: []string{"1234"}, 6320 CurrentKernelCommandLines: []string{"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"}, 6321 CurrentTrustedRecoveryBootAssets: nil, 6322 CurrentTrustedBootAssets: nil, 6323 6324 Model: model.Model(), 6325 BrandID: model.BrandID(), 6326 Grade: string(model.Grade()), 6327 ModelSignKeyID: model.SignKeyID(), 6328 } 6329 if encrypted { 6330 m.CurrentTrustedRecoveryBootAssets = map[string][]string{ 6331 // see gadget content 6332 "grubx64.efi": {"21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848"}, 6333 "bootx64.efi": {"21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848"}, 6334 } 6335 m.CurrentTrustedBootAssets = map[string][]string{ 6336 "grubx64.efi": {"21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848"}, 6337 } 6338 } 6339 6340 // make sure cmdline matches what we expect in the modeenv 6341 c.Assert(ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "proc/cmdline"), 6342 []byte("snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"), 0644), 6343 IsNil) 6344 6345 err = m.WriteTo("") 6346 c.Assert(err, IsNil) 6347 c.Assert(s.o.DeviceManager().ReloadModeenv(), IsNil) 6348 6349 bl, err := bootloader.Find(boot.InitramfsUbuntuSeedDir, &bootloader.Options{Role: bootloader.RoleRecovery}) 6350 c.Assert(err, IsNil) 6351 6352 err = bl.SetBootVars(map[string]string{ 6353 "snap_kernel": "pc-kernel_2.snap", 6354 }) 6355 c.Assert(err, IsNil) 6356 6357 secbootResealCalls := 0 6358 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 6359 secbootResealCalls++ 6360 if !encrypted { 6361 return fmt.Errorf("unexpected call") 6362 } 6363 return nil 6364 }) 6365 defer restore() 6366 6367 chg, err := devicestate.Remodel(st, newModel) 6368 c.Assert(err, IsNil) 6369 6370 c.Check(devicestate.Remodeling(st), Equals, true) 6371 6372 st.Unlock() 6373 err = s.o.Settle(settleTimeout) 6374 st.Lock() 6375 c.Assert(err, IsNil, Commentf(s.logbuf.String())) 6376 6377 c.Check(chg.Status(), Equals, state.DoingStatus, Commentf("remodel change failed: %v", chg.Err())) 6378 c.Check(devicestate.Remodeling(st), Equals, true) 6379 restarting, kind := st.Restarting() 6380 c.Check(restarting, Equals, true) 6381 c.Assert(kind, Equals, state.RestartSystemNow) 6382 6383 now := time.Now() 6384 expectedLabel := now.Format("20060102") 6385 6386 m, err = boot.ReadModeenv("") 6387 c.Assert(err, IsNil) 6388 c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"1234", expectedLabel}) 6389 c.Check(m.GoodRecoverySystems, DeepEquals, []string{"1234"}) 6390 6391 vars, err := bl.GetBootVars("try_recovery_system", "recovery_system_status") 6392 c.Assert(err, IsNil) 6393 c.Assert(vars, DeepEquals, map[string]string{ 6394 "try_recovery_system": expectedLabel, 6395 "recovery_system_status": "try", 6396 }) 6397 6398 // the new required-snap "foo" is not installed yet 6399 var snapst snapstate.SnapState 6400 err = snapstate.Get(st, "foo", &snapst) 6401 c.Assert(err, Equals, state.ErrNoState) 6402 6403 // simulate successful reboot to recovery and back 6404 state.MockRestarting(st, state.RestartUnset) 6405 // this would be done by snap-bootstrap in initramfs 6406 err = bl.SetBootVars(map[string]string{ 6407 "try_recovery_system": expectedLabel, 6408 "recovery_system_status": "tried", 6409 }) 6410 c.Assert(err, IsNil) 6411 6412 // reset, so that after-reboot handling of tried system is executed 6413 s.o.DeviceManager().ResetToPostBootState() 6414 st.Unlock() 6415 err = s.o.DeviceManager().Ensure() 6416 st.Lock() 6417 c.Assert(err, IsNil) 6418 6419 st.Unlock() 6420 err = s.o.Settle(settleTimeout) 6421 st.Lock() 6422 c.Assert(err, IsNil) 6423 6424 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("remodel change failed: %v", chg.Err())) 6425 // boot variables are cleared 6426 vars, err = bl.GetBootVars("try_recovery_system", "recovery_system_status") 6427 c.Assert(err, IsNil) 6428 c.Assert(vars, DeepEquals, map[string]string{ 6429 "try_recovery_system": "", 6430 "recovery_system_status": "", 6431 }) 6432 6433 for _, name := range []string{"core20", "pc-kernel", "pc", "snapd", "foo", "bar"} { 6434 var snapst snapstate.SnapState 6435 err = snapstate.Get(st, name, &snapst) 6436 c.Assert(err, IsNil) 6437 } 6438 6439 // ensure sorting is correct 6440 tasks := chg.Tasks() 6441 sort.Sort(byReadyTime(tasks)) 6442 6443 var i int 6444 // first all downloads/checks in sequential order 6445 for _, name := range []string{"foo", "bar"} { 6446 i += validateDownloadCheckTasks(c, tasks[i:], name, "1", "stable") 6447 } 6448 // then create recovery 6449 i += validateRecoverySystemTasks(c, tasks[i:], expectedLabel) 6450 // then all installs in sequential order 6451 for _, name := range []string{"foo", "bar"} { 6452 i += validateInstallTasks(c, tasks[i:], name, "1", 0) 6453 } 6454 // ensure that we only have the tasks we checked (plus the one 6455 // extra "set-model" task) 6456 c.Assert(tasks, HasLen, i+1) 6457 6458 const usesSnapd = true 6459 sd := seedtest.ValidateSeed(c, boot.InitramfsUbuntuSeedDir, expectedLabel, usesSnapd, s.storeSigning.Trusted) 6460 c.Assert(sd.Model(), DeepEquals, newModel) 6461 6462 m, err = boot.ReadModeenv("") 6463 c.Assert(err, IsNil) 6464 c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"1234", expectedLabel}) 6465 c.Check(m.GoodRecoverySystems, DeepEquals, []string{"1234", expectedLabel}) 6466 if encrypted { 6467 // boot assets are measured and tracked in modeenv in an encrypted system 6468 c.Check(m, testutil.JsonEquals, &boot.Modeenv{ 6469 Mode: "run", 6470 Base: "core20_3.snap", 6471 CurrentKernels: []string{"pc-kernel_2.snap"}, 6472 CurrentRecoverySystems: []string{"1234", expectedLabel}, 6473 GoodRecoverySystems: []string{"1234", expectedLabel}, 6474 CurrentKernelCommandLines: []string{"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"}, 6475 CurrentTrustedRecoveryBootAssets: map[string][]string{ 6476 "grubx64.efi": {"21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848"}, 6477 "bootx64.efi": {"21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848"}, 6478 }, 6479 CurrentTrustedBootAssets: map[string][]string{ 6480 "grubx64.efi": {"21e42a075b0d7bb6177c0eb3b3a1c8c6de6d4b4f902759eae5555e9cf3bebd21277a27102fd5426da989bde96c0cf848"}, 6481 }, 6482 6483 Model: newModel.Model(), 6484 BrandID: newModel.BrandID(), 6485 Grade: string(newModel.Grade()), 6486 ModelSignKeyID: newModel.SignKeyID(), 6487 }) 6488 } else { 6489 c.Check(m, testutil.JsonEquals, &boot.Modeenv{ 6490 Mode: "run", 6491 Base: "core20_3.snap", 6492 CurrentKernels: []string{"pc-kernel_2.snap"}, 6493 CurrentRecoverySystems: []string{"1234", expectedLabel}, 6494 GoodRecoverySystems: []string{"1234", expectedLabel}, 6495 CurrentKernelCommandLines: []string{"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"}, 6496 6497 Model: newModel.Model(), 6498 BrandID: newModel.BrandID(), 6499 Grade: string(newModel.Grade()), 6500 ModelSignKeyID: newModel.SignKeyID(), 6501 }) 6502 } 6503 6504 // new model has been written to ubuntu-boot/device/model 6505 var modelBytes bytes.Buffer 6506 c.Assert(asserts.NewEncoder(&modelBytes).Encode(newModel), IsNil) 6507 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, modelBytes.String()) 6508 if encrypted { 6509 // keys were resealed 6510 c.Assert(secbootResealCalls, Not(Equals), 0) 6511 } else { 6512 c.Assert(secbootResealCalls, Equals, 0) 6513 } 6514 6515 var seededSystems []map[string]interface{} 6516 err = st.Get("seeded-systems", &seededSystems) 6517 c.Assert(err, IsNil) 6518 c.Assert(seededSystems, HasLen, 1) 6519 // since we can't mock the seed timestamp, make sure it's within a 6520 // reasonable range, and then clear it 6521 c.Assert(seededSystems[0]["seed-time"], FitsTypeOf, "") 6522 ts, err := time.Parse(time.RFC3339Nano, seededSystems[0]["seed-time"].(string)) 6523 c.Assert(err, IsNil) 6524 // should be more than enough for the test to finish 6525 c.Check(ts.Before(now.Add(10*time.Minute)), Equals, true, Commentf("seed-time is too late: %v", ts)) 6526 seededSystems[0]["seed-time"] = "" 6527 c.Check(seededSystems, DeepEquals, []map[string]interface{}{ 6528 { 6529 "system": expectedLabel, 6530 "model": newModel.Model(), 6531 "brand-id": newModel.BrandID(), 6532 "revision": float64(newModel.Revision()), 6533 "timestamp": newModel.Timestamp().Format(time.RFC3339Nano), 6534 // cleared earlier 6535 "seed-time": "", 6536 }, 6537 }) 6538 } 6539 6540 func (s *mgrsSuite) TestRemodelUC20WithRecoverySystemEncrypted(c *C) { 6541 const encrypted bool = true 6542 s.testRemodelUC20WithRecoverySystem(c, encrypted) 6543 } 6544 6545 func (s *mgrsSuite) TestRemodelUC20WithRecoverySystemUnencrypted(c *C) { 6546 const encrypted bool = false 6547 s.testRemodelUC20WithRecoverySystem(c, encrypted) 6548 } 6549 6550 func (s *mgrsSuite) TestCheckRefreshFailureWithConcurrentRemoveOfConnectedSnap(c *C) { 6551 hookMgr := s.o.HookManager() 6552 c.Assert(hookMgr, NotNil) 6553 6554 // force configure hook failure for some-snap. 6555 hookMgr.RegisterHijack("configure", "some-snap", func(ctx *hookstate.Context) error { 6556 return fmt.Errorf("failing configure hook") 6557 }) 6558 6559 snapPath, _ := s.makeStoreTestSnap(c, someSnapYaml, "40") 6560 s.serveSnap(snapPath, "40") 6561 snapPath, _ = s.makeStoreTestSnap(c, otherSnapYaml, "50") 6562 s.serveSnap(snapPath, "50") 6563 6564 mockServer := s.mockStore(c) 6565 defer mockServer.Close() 6566 6567 st := s.o.State() 6568 st.Lock() 6569 defer st.Unlock() 6570 6571 st.Set("conns", map[string]interface{}{ 6572 "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": false}, 6573 }) 6574 6575 si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)} 6576 snapInfo := snaptest.MockSnap(c, someSnapYaml, si) 6577 6578 oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)} 6579 otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi) 6580 6581 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 6582 Active: true, 6583 Sequence: []*snap.SideInfo{si}, 6584 Current: snap.R(1), 6585 SnapType: "app", 6586 }) 6587 snapstate.Set(st, "other-snap", &snapstate.SnapState{ 6588 Active: true, 6589 Sequence: []*snap.SideInfo{oi}, 6590 Current: snap.R(1), 6591 SnapType: "app", 6592 }) 6593 6594 // add snaps to the repo and connect them 6595 repo := s.o.InterfaceManager().Repository() 6596 c.Assert(repo.AddSnap(snapInfo), IsNil) 6597 c.Assert(repo.AddSnap(otherInfo), IsNil) 6598 _, err := repo.Connect(&interfaces.ConnRef{ 6599 PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"}, 6600 SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"}, 6601 }, nil, nil, nil, nil, nil) 6602 c.Assert(err, IsNil) 6603 6604 // refresh all 6605 c.Assert(assertstate.RefreshSnapDeclarations(st, 0), IsNil) 6606 6607 ts, err := snapstate.Update(st, "some-snap", nil, 0, snapstate.Flags{}) 6608 c.Assert(err, IsNil) 6609 chg := st.NewChange("refresh", "...") 6610 chg.AddAll(ts) 6611 6612 // remove other-snap 6613 ts2, err := snapstate.Remove(st, "other-snap", snap.R(0), &snapstate.RemoveFlags{Purge: true}) 6614 c.Assert(err, IsNil) 6615 chg2 := st.NewChange("remove-snap", "...") 6616 chg2.AddAll(ts2) 6617 6618 st.Unlock() 6619 err = s.o.Settle(settleTimeout) 6620 st.Lock() 6621 6622 c.Check(err, IsNil) 6623 6624 // the refresh change has failed due to configure hook error 6625 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*failing configure hook.*`) 6626 c.Check(chg.Status(), Equals, state.ErrorStatus) 6627 6628 // download-snap is one of the first tasks in the refresh change, check that it was undone 6629 var downloadSnapStatus state.Status 6630 for _, t := range chg.Tasks() { 6631 if t.Kind() == "download-snap" { 6632 downloadSnapStatus = t.Status() 6633 break 6634 } 6635 } 6636 c.Check(downloadSnapStatus, Equals, state.UndoneStatus) 6637 6638 // the remove change succeeded 6639 c.Check(chg2.Err(), IsNil) 6640 c.Check(chg2.Status(), Equals, state.DoneStatus) 6641 } 6642 6643 func (s *mgrsSuite) TestInstallKernelSnapRollbackUpdatesBootloaderEnv(c *C) { 6644 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 6645 bootloader.Force(bloader) 6646 defer bootloader.Force(nil) 6647 6648 restore := release.MockOnClassic(false) 6649 defer restore() 6650 6651 model := s.brands.Model("my-brand", "my-model", modelDefaults) 6652 6653 const packageKernel = ` 6654 name: pc-kernel 6655 version: 4.0-1 6656 type: kernel` 6657 6658 files := [][]string{ 6659 {"kernel.img", "I'm a kernel"}, 6660 {"initrd.img", "...and I'm an initrd"}, 6661 {"meta/kernel.yaml", "version: 4.2"}, 6662 } 6663 snapPath := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 6664 6665 st := s.o.State() 6666 st.Lock() 6667 defer st.Unlock() 6668 6669 // pretend we have core18/pc-kernel 6670 bloader.BootVars = map[string]string{ 6671 "snap_core": "core18_2.snap", 6672 "snap_kernel": "pc-kernel_123.snap", 6673 "snap_mode": boot.DefaultStatus, 6674 } 6675 si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(123)} 6676 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 6677 SnapType: "kernel", 6678 Active: true, 6679 Sequence: []*snap.SideInfo{si1}, 6680 Current: si1.Revision, 6681 }) 6682 snaptest.MockSnapWithFiles(c, packageKernel, si1, [][]string{ 6683 {"meta/kernel.yaml", ""}, 6684 }) 6685 si2 := &snap.SideInfo{RealName: "core18", Revision: snap.R(2)} 6686 snapstate.Set(st, "core18", &snapstate.SnapState{ 6687 SnapType: "base", 6688 Active: true, 6689 Sequence: []*snap.SideInfo{si2}, 6690 Current: si2.Revision, 6691 }) 6692 6693 // setup model assertion 6694 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 6695 devicestatetest.SetDevice(st, &auth.DeviceState{ 6696 Brand: "my-brand", 6697 Model: "my-model", 6698 Serial: "serialserialserial", 6699 }) 6700 err := assertstate.Add(st, model) 6701 c.Assert(err, IsNil) 6702 6703 ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "pc-kernel"}, snapPath, "", "", snapstate.Flags{}) 6704 c.Assert(err, IsNil) 6705 6706 chg := st.NewChange("install-snap", "...") 6707 chg.AddAll(ts) 6708 6709 // run, this will trigger a wait for the restart 6710 st.Unlock() 6711 err = s.o.Settle(settleTimeout) 6712 st.Lock() 6713 c.Assert(err, IsNil) 6714 6715 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 6716 "snap_core": "core18_2.snap", 6717 "snap_try_core": "", 6718 "snap_kernel": "pc-kernel_123.snap", 6719 "snap_try_kernel": "pc-kernel_x1.snap", 6720 "snap_mode": boot.TryStatus, 6721 }) 6722 6723 // we are in restarting state and the change is not done yet 6724 restarting, _ := st.Restarting() 6725 c.Check(restarting, Equals, true) 6726 c.Check(chg.Status(), Equals, state.DoingStatus) 6727 s.mockRollbackAcrossReboot(c, bloader, []snap.Type{snap.TypeKernel}) 6728 6729 // the kernel revision got rolled back 6730 var snapst snapstate.SnapState 6731 snapstate.Get(st, "pc-kernel", &snapst) 6732 info, err := snapst.CurrentInfo() 6733 c.Assert(err, IsNil) 6734 c.Assert(info.Revision, Equals, snap.R(123)) 6735 6736 st.Unlock() 6737 err = s.o.Settle(settleTimeout) 6738 st.Lock() 6739 c.Assert(err, IsNil) 6740 6741 c.Assert(chg.Status(), Equals, state.ErrorStatus) 6742 c.Assert(chg.Err(), ErrorMatches, `(?ms).*cannot finish pc-kernel installation, there was a rollback across reboot\)`) 6743 6744 // and the bootvars are reset 6745 c.Check(bloader.BootVars, DeepEquals, map[string]string{ 6746 "snap_core": "core18_2.snap", 6747 "snap_kernel": "pc-kernel_123.snap", 6748 "snap_mode": boot.DefaultStatus, 6749 "snap_try_core": "", 6750 "snap_try_kernel": "", 6751 }) 6752 } 6753 6754 func (s *mgrsSuite) TestUC18SnapdRefreshUpdatesSnapServiceUnitsAndRestartsKilledUnits(c *C) { 6755 restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) 6756 defer restore() 6757 // reload directories 6758 dirs.SetRootDir(dirs.GlobalRootDir) 6759 restore = release.MockOnClassic(false) 6760 defer restore() 6761 bl := bootloadertest.Mock("mock", c.MkDir()) 6762 bootloader.Force(bl) 6763 defer bootloader.Force(nil) 6764 const snapdSnap = ` 6765 name: snapd 6766 version: 1.0 6767 type: snapd` 6768 snapPath := snaptest.MakeTestSnapWithFiles(c, snapdSnap, nil) 6769 si := &snap.SideInfo{RealName: "snapd"} 6770 6771 st := s.o.State() 6772 st.Lock() 6773 6774 // we must be seeded 6775 st.Set("seeded", true) 6776 6777 // add the test snap service 6778 testSnapSideInfo := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(42)} 6779 snapstate.Set(st, "test-snap", &snapstate.SnapState{ 6780 Sequence: []*snap.SideInfo{testSnapSideInfo}, 6781 Current: snap.R(42), 6782 Active: true, 6783 SnapType: "app", 6784 }) 6785 snaptest.MockSnapWithFiles(c, `name: test-snap 6786 version: v1 6787 apps: 6788 svc1: 6789 command: bin.sh 6790 daemon: simple 6791 `, testSnapSideInfo, nil) 6792 6793 // add the snap service unit with Requires= 6794 unitTempl := `[Unit] 6795 # Auto-generated, DO NOT EDIT 6796 Description=Service for snap application test-snap.svc1 6797 Requires=%[1]s 6798 Wants=network.target 6799 After=%[1]s network.target snapd.apparmor.service 6800 %[3]s=usr-lib-snapd.mount 6801 After=usr-lib-snapd.mount 6802 X-Snappy=yes 6803 6804 [Service] 6805 EnvironmentFile=-/etc/environment 6806 ExecStart=/usr/bin/snap run test-snap.svc1 6807 SyslogIdentifier=test-snap.svc1 6808 Restart=on-failure 6809 WorkingDirectory=%[2]s/var/snap/test-snap/42 6810 TimeoutStopSec=30 6811 Type=simple 6812 6813 [Install] 6814 WantedBy=multi-user.target 6815 ` 6816 6817 initialUnitFile := fmt.Sprintf(unitTempl, 6818 systemd.EscapeUnitNamePath(filepath.Join(dirs.SnapMountDir, "test-snap", "42.mount")), 6819 dirs.GlobalRootDir, 6820 "Requires", 6821 ) 6822 6823 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 6824 c.Assert(err, IsNil) 6825 err = ioutil.WriteFile(filepath.Join(dirs.SnapServicesDir, "snap.test-snap.svc1.service"), []byte(initialUnitFile), 0644) 6826 c.Assert(err, IsNil) 6827 6828 // we also need to setup the usr-lib-snapd.mount file too 6829 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 6830 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 6831 c.Assert(err, IsNil) 6832 6833 // the modification time of the usr-lib-snapd.mount file is the first 6834 // timestamp we use, then the stop time of the snap svc, then the stop time 6835 // of usr-lib-snapd.mount 6836 t0 := time.Now() 6837 t1 := t0.Add(1 * time.Hour) 6838 t2 := t0.Add(2 * time.Hour) 6839 6840 err = os.Chtimes(usrLibSnapdMountFile, t0, t0) 6841 c.Assert(err, IsNil) 6842 6843 systemctlCalls := 0 6844 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 6845 systemctlCalls++ 6846 6847 c.Logf("call: %v", systemctlCalls) 6848 switch systemctlCalls { 6849 // first 3 calls are for the snapd refresh itself 6850 case 1: 6851 c.Check(cmd, DeepEquals, []string{"daemon-reload"}) 6852 return nil, nil 6853 case 2: 6854 c.Check(cmd, DeepEquals, []string{"enable", "snap-snapd-x1.mount"}) 6855 return nil, nil 6856 case 3: 6857 c.Check(cmd, DeepEquals, []string{"start", "snap-snapd-x1.mount"}) 6858 return nil, nil 6859 // next we get the calls for the rewritten service files after snapd 6860 // restarts 6861 case 4: 6862 c.Check(cmd, DeepEquals, []string{"daemon-reload"}) 6863 return nil, nil 6864 case 5: 6865 c.Check(cmd, DeepEquals, []string{"enable", "usr-lib-snapd.mount"}) 6866 return nil, nil 6867 case 6: 6868 c.Check(cmd, DeepEquals, []string{"stop", "usr-lib-snapd.mount"}) 6869 return nil, nil 6870 case 7: 6871 c.Check(cmd, DeepEquals, []string{"show", "--property=ActiveState", "usr-lib-snapd.mount"}) 6872 return []byte("ActiveState=inactive"), nil 6873 case 8: 6874 c.Check(cmd, DeepEquals, []string{"start", "usr-lib-snapd.mount"}) 6875 return nil, nil 6876 case 9: 6877 c.Check(cmd, DeepEquals, []string{"daemon-reload"}) 6878 return nil, nil 6879 case 10: 6880 c.Check(cmd, DeepEquals, []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}) 6881 return []byte("InactiveEnterTimestamp=" + t2.Format("Mon 2006-01-02 15:04:05 MST")), nil 6882 case 11: 6883 c.Check(cmd, DeepEquals, []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}) 6884 return []byte("InactiveEnterTimestamp=" + t1.Format("Mon 2006-01-02 15:04:05 MST")), nil 6885 case 12: 6886 c.Check(cmd, DeepEquals, []string{"is-enabled", "snap.test-snap.svc1.service"}) 6887 return []byte("enabled"), nil 6888 case 13: 6889 c.Check(cmd, DeepEquals, []string{"start", "snap.test-snap.svc1.service"}) 6890 return nil, nil 6891 default: 6892 c.Errorf("unexpected call to systemctl: %+v", cmd) 6893 return nil, fmt.Errorf("broken test") 6894 } 6895 }) 6896 s.AddCleanup(r) 6897 // make sure that we get the expected number of systemctl calls 6898 s.AddCleanup(func() { c.Assert(systemctlCalls, Equals, 13) }) 6899 6900 // also add the snapd snap to state which we will refresh 6901 si1 := &snap.SideInfo{RealName: "snapd", Revision: snap.R(1)} 6902 snapstate.Set(st, "snapd", &snapstate.SnapState{ 6903 SnapType: "snapd", 6904 Active: true, 6905 Sequence: []*snap.SideInfo{si1}, 6906 Current: si1.Revision, 6907 }) 6908 snaptest.MockSnapWithFiles(c, "name: snapd\ntype: snapd\nversion: 123", si1, nil) 6909 6910 // setup model assertion 6911 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 6912 devicestatetest.SetDevice(st, &auth.DeviceState{ 6913 Brand: "my-brand", 6914 Model: "my-model", 6915 Serial: "serialserialserial", 6916 }) 6917 // model := s.brands.Model("my-brand", "my-model", modelDefaults) 6918 model := s.brands.Model("my-brand", "my-model", map[string]interface{}{ 6919 "type": "model", 6920 "authority-id": "my-brand", 6921 "series": "16", 6922 "brand-id": "my-brand", 6923 "model": "my-model", 6924 "gadget": "pc", 6925 "kernel": "kernel", 6926 "architecture": "amd64", 6927 "base": "core18", 6928 }) 6929 err = assertstate.Add(st, model) 6930 c.Assert(err, IsNil) 6931 6932 ts, _, err := snapstate.InstallPath(st, si, snapPath, "", "", snapstate.Flags{}) 6933 c.Assert(err, IsNil) 6934 6935 chg := st.NewChange("install-snap", "...") 6936 chg.AddAll(ts) 6937 6938 // make sure we don't try to ensure snap services before the restart 6939 r = servicestate.MockEnsuredSnapServices(s.o.ServiceManager(), true) 6940 defer r() 6941 6942 // run, this will trigger wait for restart 6943 st.Unlock() 6944 err = s.o.Settle(settleTimeout) 6945 st.Lock() 6946 c.Assert(err, IsNil) 6947 6948 // check the snapd task state 6949 c.Check(chg.Status(), Equals, state.DoingStatus) 6950 restarting, kind := st.Restarting() 6951 c.Check(restarting, Equals, true) 6952 c.Assert(kind, Equals, state.RestartDaemon) 6953 6954 // now we do want the ensure loop to run though 6955 r = servicestate.MockEnsuredSnapServices(s.o.ServiceManager(), false) 6956 defer r() 6957 6958 // mock a restart of snapd to progress with the change 6959 state.MockRestarting(st, state.RestartUnset) 6960 6961 // let the change run its course 6962 st.Unlock() 6963 err = s.o.Settle(settleTimeout) 6964 st.Lock() 6965 defer st.Unlock() 6966 c.Assert(err, IsNil) 6967 6968 c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("change failed: %v", chg.Err())) 6969 6970 // we don't restart since the unit file was just rewritten, no services were 6971 // killed 6972 restarting, _ = st.Restarting() 6973 c.Check(restarting, Equals, false) 6974 6975 // the unit file was rewritten to use Wants= now 6976 rewrittenUnitFile := fmt.Sprintf(unitTempl, 6977 systemd.EscapeUnitNamePath(filepath.Join(dirs.SnapMountDir, "test-snap", "42.mount")), 6978 dirs.GlobalRootDir, 6979 "Wants", 6980 ) 6981 c.Assert(filepath.Join(dirs.SnapServicesDir, "snap.test-snap.svc1.service"), testutil.FileEquals, rewrittenUnitFile) 6982 } 6983 6984 func (s *mgrsSuite) TestUC18SnapdRefreshUpdatesSnapServiceUnitsAndAttemptsToRestartsKilledUnitsButFails(c *C) { 6985 restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) 6986 defer restore() 6987 // reload directories 6988 dirs.SetRootDir(dirs.GlobalRootDir) 6989 restore = release.MockOnClassic(false) 6990 defer restore() 6991 bl := bootloadertest.Mock("mock", c.MkDir()) 6992 bootloader.Force(bl) 6993 defer bootloader.Force(nil) 6994 6995 const snapdSnap = ` 6996 name: snapd 6997 version: 1.0 6998 type: snapd` 6999 snapPath := snaptest.MakeTestSnapWithFiles(c, snapdSnap, nil) 7000 si := &snap.SideInfo{RealName: "snapd"} 7001 7002 st := s.o.State() 7003 st.Lock() 7004 7005 // we must be seeded 7006 st.Set("seeded", true) 7007 7008 // add the test snap service 7009 testSnapSideInfo := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(42)} 7010 snapstate.Set(st, "test-snap", &snapstate.SnapState{ 7011 Sequence: []*snap.SideInfo{testSnapSideInfo}, 7012 Current: snap.R(42), 7013 Active: true, 7014 SnapType: "app", 7015 }) 7016 snaptest.MockSnapWithFiles(c, `name: test-snap 7017 version: v1 7018 apps: 7019 svc1: 7020 command: bin.sh 7021 daemon: simple 7022 `, testSnapSideInfo, nil) 7023 7024 // add the snap service unit with Requires= 7025 unitTempl := `[Unit] 7026 # Auto-generated, DO NOT EDIT 7027 Description=Service for snap application test-snap.svc1 7028 Requires=%[1]s 7029 Wants=network.target 7030 After=%[1]s network.target snapd.apparmor.service 7031 %[3]s=usr-lib-snapd.mount 7032 After=usr-lib-snapd.mount 7033 X-Snappy=yes 7034 7035 [Service] 7036 EnvironmentFile=-/etc/environment 7037 ExecStart=/usr/bin/snap run test-snap.svc1 7038 SyslogIdentifier=test-snap.svc1 7039 Restart=on-failure 7040 WorkingDirectory=%[2]s/var/snap/test-snap/42 7041 TimeoutStopSec=30 7042 Type=simple 7043 7044 [Install] 7045 WantedBy=multi-user.target 7046 ` 7047 7048 initialUnitFile := fmt.Sprintf(unitTempl, 7049 systemd.EscapeUnitNamePath(filepath.Join(dirs.SnapMountDir, "test-snap", "42.mount")), 7050 dirs.GlobalRootDir, 7051 "Requires", 7052 ) 7053 7054 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 7055 c.Assert(err, IsNil) 7056 err = ioutil.WriteFile(filepath.Join(dirs.SnapServicesDir, "snap.test-snap.svc1.service"), []byte(initialUnitFile), 0644) 7057 c.Assert(err, IsNil) 7058 7059 // we also need to setup the usr-lib-snapd.mount file too 7060 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 7061 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 7062 c.Assert(err, IsNil) 7063 7064 // the modification time of the usr-lib-snapd.mount file is the first 7065 // timestamp we use, then the stop time of the snap svc, then the stop time 7066 // of usr-lib-snapd.mount 7067 t0 := time.Now() 7068 t1 := t0.Add(1 * time.Hour) 7069 t2 := t0.Add(2 * time.Hour) 7070 7071 err = os.Chtimes(usrLibSnapdMountFile, t0, t0) 7072 c.Assert(err, IsNil) 7073 7074 systemctlCalls := 0 7075 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 7076 systemctlCalls++ 7077 7078 switch systemctlCalls { 7079 // first 3 calls are for the snapd refresh itself 7080 case 1: 7081 c.Check(cmd, DeepEquals, []string{"daemon-reload"}) 7082 return nil, nil 7083 case 2: 7084 c.Check(cmd, DeepEquals, []string{"enable", "snap-snapd-x1.mount"}) 7085 return nil, nil 7086 case 3: 7087 c.Check(cmd, DeepEquals, []string{"start", "snap-snapd-x1.mount"}) 7088 return nil, nil 7089 // next we get the calls for the rewritten service files after snapd 7090 // restarts 7091 case 4: 7092 c.Check(cmd, DeepEquals, []string{"daemon-reload"}) 7093 return nil, nil 7094 case 5: 7095 c.Check(cmd, DeepEquals, []string{"enable", "usr-lib-snapd.mount"}) 7096 return nil, nil 7097 case 6: 7098 c.Check(cmd, DeepEquals, []string{"stop", "usr-lib-snapd.mount"}) 7099 return nil, nil 7100 case 7: 7101 c.Check(cmd, DeepEquals, []string{"show", "--property=ActiveState", "usr-lib-snapd.mount"}) 7102 return []byte("ActiveState=inactive"), nil 7103 case 8: 7104 c.Check(cmd, DeepEquals, []string{"start", "usr-lib-snapd.mount"}) 7105 return nil, nil 7106 case 9: 7107 c.Check(cmd, DeepEquals, []string{"daemon-reload"}) 7108 return nil, nil 7109 case 10: 7110 c.Check(cmd, DeepEquals, []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}) 7111 return []byte("InactiveEnterTimestamp=" + t2.Format("Mon 2006-01-02 15:04:05 MST")), nil 7112 case 11: 7113 c.Check(cmd, DeepEquals, []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}) 7114 return []byte("InactiveEnterTimestamp=" + t1.Format("Mon 2006-01-02 15:04:05 MST")), nil 7115 case 12: 7116 c.Check(cmd, DeepEquals, []string{"is-enabled", "snap.test-snap.svc1.service"}) 7117 return []byte("enabled"), nil 7118 case 13: 7119 // starting the snap fails 7120 c.Check(cmd, DeepEquals, []string{"start", "snap.test-snap.svc1.service"}) 7121 return nil, fmt.Errorf("the snap service is having a bad day") 7122 case 14: 7123 // because starting the snap fails, we will automatically try to 7124 // undo the starting of the snap by stopping it, hence the request 7125 // to stop it 7126 // TODO: is this desirable? in the field, what if stopping the 7127 // service also dies? 7128 c.Check(cmd, DeepEquals, []string{"stop", "snap.test-snap.svc1.service"}) 7129 return nil, fmt.Errorf("the snap service is still having a bad day") 7130 default: 7131 c.Errorf("unexpected call to systemctl: %+v", cmd) 7132 return nil, fmt.Errorf("broken test") 7133 } 7134 }) 7135 s.AddCleanup(r) 7136 // make sure that we get the expected number of systemctl calls 7137 s.AddCleanup(func() { c.Assert(systemctlCalls, Equals, 14) }) 7138 7139 // also add the snapd snap to state which we will refresh 7140 si1 := &snap.SideInfo{RealName: "snapd", Revision: snap.R(1)} 7141 snapstate.Set(st, "snapd", &snapstate.SnapState{ 7142 SnapType: "snapd", 7143 Active: true, 7144 Sequence: []*snap.SideInfo{si1}, 7145 Current: si1.Revision, 7146 }) 7147 snaptest.MockSnapWithFiles(c, "name: snapd\ntype: snapd\nversion: 123", si1, nil) 7148 7149 // setup model assertion 7150 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 7151 devicestatetest.SetDevice(st, &auth.DeviceState{ 7152 Brand: "my-brand", 7153 Model: "my-model", 7154 Serial: "serialserialserial", 7155 }) 7156 // model := s.brands.Model("my-brand", "my-model", modelDefaults) 7157 model := s.brands.Model("my-brand", "my-model", map[string]interface{}{ 7158 "type": "model", 7159 "authority-id": "my-brand", 7160 "series": "16", 7161 "brand-id": "my-brand", 7162 "model": "my-model", 7163 "gadget": "pc", 7164 "kernel": "kernel", 7165 "architecture": "amd64", 7166 "base": "core18", 7167 }) 7168 err = assertstate.Add(st, model) 7169 c.Assert(err, IsNil) 7170 7171 ts, _, err := snapstate.InstallPath(st, si, snapPath, "", "", snapstate.Flags{}) 7172 c.Assert(err, IsNil) 7173 7174 chg := st.NewChange("install-snap", "...") 7175 chg.AddAll(ts) 7176 7177 // make sure we don't try to ensure snap services before the restart 7178 r = servicestate.MockEnsuredSnapServices(s.o.ServiceManager(), true) 7179 defer r() 7180 7181 // run, this will trigger wait for restart 7182 st.Unlock() 7183 err = s.o.Settle(settleTimeout) 7184 st.Lock() 7185 c.Assert(err, IsNil) 7186 7187 // check the snapd task state 7188 c.Check(chg.Status(), Equals, state.DoingStatus) 7189 restarting, kind := st.Restarting() 7190 c.Check(restarting, Equals, true) 7191 c.Assert(kind, Equals, state.RestartDaemon) 7192 7193 // now we do want the ensure loop to run though 7194 r = servicestate.MockEnsuredSnapServices(s.o.ServiceManager(), false) 7195 defer r() 7196 7197 // mock a restart of snapd to progress with the change 7198 state.MockRestarting(st, state.RestartUnset) 7199 7200 // let the change try to run its course 7201 st.Unlock() 7202 err = s.o.Settle(settleTimeout) 7203 st.Lock() 7204 c.Assert(err, ErrorMatches, `state ensure errors: \[error trying to restart killed services, immediately rebooting: the snap service is having a bad day\]`) 7205 7206 // the change is still in doing status 7207 c.Check(chg.Status(), Equals, state.DoingStatus) 7208 7209 // we do end up restarting now, since we tried to restart the service but 7210 // failed and so to be safe as possible we reboot the system immediately 7211 restarting, kind = st.Restarting() 7212 c.Check(restarting, Equals, true) 7213 c.Assert(kind, Equals, state.RestartSystemNow) 7214 7215 // the unit file was rewritten to use Wants= now 7216 rewrittenUnitFile := fmt.Sprintf(unitTempl, 7217 systemd.EscapeUnitNamePath(filepath.Join(dirs.SnapMountDir, "test-snap", "42.mount")), 7218 dirs.GlobalRootDir, 7219 "Wants", 7220 ) 7221 c.Assert(filepath.Join(dirs.SnapServicesDir, "snap.test-snap.svc1.service"), testutil.FileEquals, rewrittenUnitFile) 7222 7223 // simulate a final restart to demonstrate that the change still finishes 7224 // properly - note that this isn't a fully honest test, since in reality it 7225 // should be done with a real overlord.RestartBehavior implemented like what 7226 // daemon actually provides and here we are using nil, but for the purposes 7227 // of this test it's enough to ensure that 1) a restart is requested and 2) 7228 // the manager ensure loop doesn't fail after we restart since the unit 7229 // files don't need to be rewritten 7230 state.MockRestarting(st, state.RestartUnset) 7231 7232 // we want the service ensure loop to run again to show it doesn't break 7233 // anything 7234 r = servicestate.MockEnsuredSnapServices(s.o.ServiceManager(), false) 7235 defer r() 7236 7237 st.Unlock() 7238 err = s.o.Settle(settleTimeout) 7239 st.Lock() 7240 defer st.Unlock() 7241 c.Assert(err, IsNil) 7242 7243 // the change is now fully done 7244 c.Check(chg.Status(), Equals, state.DoneStatus) 7245 } 7246 7247 func (s *mgrsSuite) testUC20RunUpdateManagedBootConfig(c *C, snapPath string, si *snap.SideInfo, bl bootloader.Bootloader, updated bool) { 7248 restore := release.MockOnClassic(false) 7249 defer restore() 7250 7251 // pretend we booted with the right kernel 7252 bl.SetBootVars(map[string]string{"snap_kernel": "pc-kernel_1.snap"}) 7253 7254 uc20ModelDefaults := map[string]interface{}{ 7255 "architecture": "amd64", 7256 "base": "core20", 7257 "store": "my-brand-store-id", 7258 "snaps": []interface{}{ 7259 map[string]interface{}{ 7260 "name": "pc-kernel", 7261 "id": snaptest.AssertedSnapID("pc-kernel"), 7262 "type": "kernel", 7263 "default-channel": "20", 7264 }, 7265 map[string]interface{}{ 7266 "name": "pc", 7267 "id": snaptest.AssertedSnapID("pc"), 7268 "type": "gadget", 7269 "default-channel": "20", 7270 }}, 7271 } 7272 7273 model := s.brands.Model("my-brand", "my-model", uc20ModelDefaults) 7274 7275 // mock the modeenv file 7276 m := boot.Modeenv{ 7277 Mode: "run", 7278 RecoverySystem: "20191127", 7279 Base: "core20_1.snap", 7280 CurrentKernelCommandLines: []string{ 7281 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 7282 }, 7283 } 7284 err := m.WriteTo("") 7285 c.Assert(err, IsNil) 7286 c.Assert(s.o.DeviceManager().ReloadModeenv(), IsNil) 7287 7288 st := s.o.State() 7289 st.Lock() 7290 // defer st.Unlock() 7291 st.Set("seeded", true) 7292 7293 si1 := &snap.SideInfo{RealName: "snapd", Revision: snap.R(1)} 7294 snapstate.Set(st, "snapd", &snapstate.SnapState{ 7295 SnapType: "snapd", 7296 Active: true, 7297 Sequence: []*snap.SideInfo{si1}, 7298 Current: si1.Revision, 7299 }) 7300 snaptest.MockSnapWithFiles(c, "name: snapd\ntype: snapd\nversion: 123", si1, nil) 7301 7302 si2 := &snap.SideInfo{RealName: "core20", Revision: snap.R(1)} 7303 snapstate.Set(st, "core20", &snapstate.SnapState{ 7304 SnapType: "base", 7305 7306 Active: true, 7307 Sequence: []*snap.SideInfo{si2}, 7308 Current: si2.Revision, 7309 }) 7310 si3 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(1)} 7311 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 7312 SnapType: "kernel", 7313 Active: true, 7314 Sequence: []*snap.SideInfo{si3}, 7315 Current: si3.Revision, 7316 }) 7317 si4 := &snap.SideInfo{RealName: "pc", Revision: snap.R(1)} 7318 snapstate.Set(st, "pc", &snapstate.SnapState{ 7319 SnapType: "gadget", 7320 Active: true, 7321 Sequence: []*snap.SideInfo{si4}, 7322 Current: si4.Revision, 7323 }) 7324 const pcGadget = ` 7325 name: pc 7326 type: gadget 7327 ` 7328 const pcGadgetYaml = ` 7329 volumes: 7330 pc: 7331 bootloader: grub 7332 ` 7333 snaptest.MockSnapWithFiles(c, pcGadget, si4, [][]string{ 7334 {"meta/gadget.yaml", pcGadgetYaml}, 7335 }) 7336 7337 // setup model assertion 7338 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 7339 devicestatetest.SetDevice(st, &auth.DeviceState{ 7340 Brand: "my-brand", 7341 Model: "my-model", 7342 Serial: "serialserialserial", 7343 }) 7344 err = assertstate.Add(st, model) 7345 c.Assert(err, IsNil) 7346 7347 ts, _, err := snapstate.InstallPath(st, si, snapPath, "", "", snapstate.Flags{}) 7348 c.Assert(err, IsNil) 7349 7350 chg := st.NewChange("install-snap", "...") 7351 chg.AddAll(ts) 7352 7353 // run, this will trigger wait for restart with snapd snap (or be done 7354 // with core) 7355 st.Unlock() 7356 err = s.o.Settle(settleTimeout) 7357 st.Lock() 7358 c.Assert(err, IsNil) 7359 7360 if si.RealName == "core" { 7361 // core on UC20 is done at this point 7362 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("failed: %v", chg.Err())) 7363 c.Assert(chg.Err(), IsNil) 7364 } else { 7365 // boot config is updated after link-snap, so first comes the 7366 // daemon restart 7367 c.Check(chg.Status(), Equals, state.DoingStatus) 7368 restarting, kind := st.Restarting() 7369 c.Check(restarting, Equals, true) 7370 c.Assert(kind, Equals, state.RestartDaemon) 7371 7372 // simulate successful daemon restart happened 7373 state.MockRestarting(st, state.RestartUnset) 7374 7375 // let the change run its course 7376 st.Unlock() 7377 err = s.o.Settle(settleTimeout) 7378 st.Lock() 7379 c.Assert(err, IsNil) 7380 7381 c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("change failed: %v", chg.Err())) 7382 restarting, kind = st.Restarting() 7383 if updated { 7384 // boot config updated, thus a system restart was 7385 // requested 7386 c.Check(restarting, Equals, true) 7387 c.Assert(kind, Equals, state.RestartSystem) 7388 } else { 7389 c.Check(restarting, Equals, false) 7390 } 7391 } 7392 } 7393 7394 func (s *mgrsSuite) TestUC20SnapdUpdatesManagedBootConfig(c *C) { 7395 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7396 bootloader.Force(mabloader) 7397 defer bootloader.Force(nil) 7398 7399 mabloader.Updated = true 7400 7401 const snapdSnap = ` 7402 name: snapd 7403 version: 1.0 7404 type: snapd` 7405 snapPath := snaptest.MakeTestSnapWithFiles(c, snapdSnap, nil) 7406 si := &snap.SideInfo{RealName: "snapd"} 7407 7408 const updated = true 7409 s.testUC20RunUpdateManagedBootConfig(c, snapPath, si, mabloader, updated) 7410 7411 c.Check(mabloader.UpdateCalls, Equals, 1) 7412 } 7413 7414 func (s *mgrsSuite) TestUC20SnapdUpdateManagedBootNotNeededConfig(c *C) { 7415 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7416 bootloader.Force(mabloader) 7417 defer bootloader.Force(nil) 7418 7419 // nothing was updated, eg. boot config editions are the same 7420 mabloader.Updated = false 7421 7422 const snapdSnap = ` 7423 name: snapd 7424 version: 1.0 7425 type: snapd` 7426 snapPath := snaptest.MakeTestSnapWithFiles(c, snapdSnap, nil) 7427 si := &snap.SideInfo{RealName: "snapd"} 7428 7429 const updated = false 7430 s.testUC20RunUpdateManagedBootConfig(c, snapPath, si, mabloader, updated) 7431 7432 c.Check(mabloader.UpdateCalls, Equals, 1) 7433 } 7434 7435 func (s *mgrsSuite) TestUC20CoreDoesNotUpdateManagedBootConfig(c *C) { 7436 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7437 bootloader.Force(mabloader) 7438 defer bootloader.Force(nil) 7439 7440 const coreSnap = ` 7441 name: core 7442 version: 1.0 7443 type: base` 7444 snapPath := snaptest.MakeTestSnapWithFiles(c, coreSnap, nil) 7445 si := &snap.SideInfo{RealName: "core"} 7446 7447 const updated = false 7448 s.testUC20RunUpdateManagedBootConfig(c, snapPath, si, mabloader, updated) 7449 c.Check(mabloader.UpdateCalls, Equals, 0) 7450 } 7451 7452 func (s *mgrsSuite) testNonUC20RunUpdateManagedBootConfig(c *C, snapPath string, si *snap.SideInfo, bl bootloader.Bootloader) { 7453 // non UC20 device model 7454 7455 restore := release.MockOnClassic(false) 7456 defer restore() 7457 7458 // pretend we booted with the right kernel & base 7459 bl.SetBootVars(map[string]string{ 7460 "snap_core": "core_1.snap", 7461 "snap_kernel": "pc-kernel_1.snap", 7462 }) 7463 7464 model := s.brands.Model("my-brand", "my-model", modelDefaults) 7465 7466 st := s.o.State() 7467 st.Lock() 7468 // defer st.Unlock() 7469 st.Set("seeded", true) 7470 7471 si1 := &snap.SideInfo{RealName: "snapd", Revision: snap.R(1)} 7472 snapstate.Set(st, "snapd", &snapstate.SnapState{ 7473 SnapType: "snapd", 7474 Active: true, 7475 Sequence: []*snap.SideInfo{si1}, 7476 Current: si1.Revision, 7477 }) 7478 si2 := &snap.SideInfo{RealName: "core", Revision: snap.R(1)} 7479 snapstate.Set(st, "core", &snapstate.SnapState{ 7480 SnapType: "base", 7481 Active: true, 7482 Sequence: []*snap.SideInfo{si2}, 7483 Current: si2.Revision, 7484 }) 7485 si3 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(1)} 7486 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 7487 SnapType: "kernel", 7488 Active: true, 7489 Sequence: []*snap.SideInfo{si3}, 7490 Current: si3.Revision, 7491 }) 7492 7493 // setup model assertion 7494 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 7495 devicestatetest.SetDevice(st, &auth.DeviceState{ 7496 Brand: "my-brand", 7497 Model: "my-model", 7498 Serial: "serialserialserial", 7499 }) 7500 err := assertstate.Add(st, model) 7501 c.Assert(err, IsNil) 7502 7503 ts, _, err := snapstate.InstallPath(st, si, snapPath, "", "", snapstate.Flags{}) 7504 c.Assert(err, IsNil) 7505 7506 chg := st.NewChange("install-snap", "...") 7507 chg.AddAll(ts) 7508 7509 // run, this will trigger a wait for the restart 7510 st.Unlock() 7511 err = s.o.Settle(settleTimeout) 7512 st.Lock() 7513 c.Assert(err, IsNil) 7514 7515 c.Check(chg.Status(), Equals, state.DoingStatus) 7516 restarting, _ := st.Restarting() 7517 c.Check(restarting, Equals, true) 7518 7519 // simulate successful restart happened 7520 state.MockRestarting(st, state.RestartUnset) 7521 if si.RealName == "core" { 7522 // pretend we switched to a new core 7523 bl.SetBootVars(map[string]string{ 7524 "snap_core": "core_x1.snap", 7525 "snap_kernel": "pc-kernel_1.snap", 7526 }) 7527 } 7528 7529 st.Unlock() 7530 err = s.o.Settle(settleTimeout) 7531 st.Lock() 7532 c.Assert(err, IsNil) 7533 7534 c.Assert(chg.Status(), Equals, state.DoneStatus) 7535 c.Assert(chg.Err(), IsNil) 7536 } 7537 7538 func (s *mgrsSuite) TestNonUC20DoesNotUpdateManagedBootConfig(c *C) { 7539 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7540 bootloader.Force(mabloader) 7541 defer bootloader.Force(nil) 7542 7543 const coreSnap = ` 7544 name: core 7545 version: 1.0 7546 type: base` 7547 snapPath := snaptest.MakeTestSnapWithFiles(c, coreSnap, nil) 7548 si := &snap.SideInfo{RealName: "core"} 7549 7550 s.testNonUC20RunUpdateManagedBootConfig(c, snapPath, si, mabloader) 7551 c.Check(mabloader.UpdateCalls, Equals, 0) 7552 } 7553 7554 func (s *mgrsSuite) TestNonUC20SnapdNoUpdateNotManagedBootConfig(c *C) { 7555 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7556 bootloader.Force(mabloader) 7557 defer bootloader.Force(nil) 7558 7559 const snapdSnap = ` 7560 name: snapd 7561 version: 1.0 7562 type: snapd` 7563 snapPath := snaptest.MakeTestSnapWithFiles(c, snapdSnap, nil) 7564 si := &snap.SideInfo{RealName: "snapd"} 7565 7566 s.testNonUC20RunUpdateManagedBootConfig(c, snapPath, si, mabloader) 7567 c.Check(mabloader.UpdateCalls, Equals, 0) 7568 } 7569 7570 const pcGadget = ` 7571 name: pc 7572 version: 1.0 7573 type: gadget 7574 ` 7575 const pcGadgetYaml = ` 7576 volumes: 7577 pc: 7578 bootloader: grub 7579 structure: 7580 - name: ubuntu-seed 7581 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 7582 role: system-seed 7583 filesystem: vfat 7584 size: 100M 7585 - name: ubuntu-boot 7586 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 7587 role: system-boot 7588 filesystem: ext4 7589 size: 100M 7590 - name: ubuntu-data 7591 role: system-data 7592 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 7593 filesystem: ext4 7594 size: 500M 7595 ` 7596 7597 func (s *mgrsSuite) testGadgetKernelCommandLine(c *C, gadgetPath string, gadgetSideInfo *snap.SideInfo, 7598 bl bootloader.Bootloader, currentFiles [][]string, currentModeenvCmdline string, 7599 commandLineAfterReboot string, update bool) { 7600 restore := release.MockOnClassic(false) 7601 defer restore() 7602 7603 cmdlineAfterRebootPath := filepath.Join(c.MkDir(), "mock-cmdline") 7604 c.Assert(ioutil.WriteFile(cmdlineAfterRebootPath, []byte(commandLineAfterReboot), 0644), IsNil) 7605 7606 // pretend we booted with the right kernel 7607 bl.SetBootVars(map[string]string{"snap_kernel": "pc-kernel_1.snap"}) 7608 7609 uc20ModelDefaults := map[string]interface{}{ 7610 "architecture": "amd64", 7611 "base": "core20", 7612 "store": "my-brand-store-id", 7613 "snaps": []interface{}{ 7614 map[string]interface{}{ 7615 "name": "pc-kernel", 7616 "id": snaptest.AssertedSnapID("pc-kernel"), 7617 "type": "kernel", 7618 "default-channel": "20", 7619 }, 7620 map[string]interface{}{ 7621 "name": "pc", 7622 "id": snaptest.AssertedSnapID("pc"), 7623 "type": "gadget", 7624 "default-channel": "20", 7625 }}, 7626 } 7627 7628 model := s.brands.Model("my-brand", "my-model", uc20ModelDefaults) 7629 7630 // mock the modeenv file 7631 m := boot.Modeenv{ 7632 Mode: "run", 7633 RecoverySystem: "20191127", 7634 Base: "core20_1.snap", 7635 // leave this line to keep gofmt 1.10 happy 7636 CurrentKernelCommandLines: []string{currentModeenvCmdline}, 7637 } 7638 err := m.WriteTo("") 7639 c.Assert(err, IsNil) 7640 c.Assert(s.o.DeviceManager().ReloadModeenv(), IsNil) 7641 7642 st := s.o.State() 7643 st.Lock() 7644 st.Set("seeded", true) 7645 7646 si1 := &snap.SideInfo{RealName: "snapd", Revision: snap.R(1)} 7647 snapstate.Set(st, "snapd", &snapstate.SnapState{ 7648 SnapType: "snapd", 7649 Active: true, 7650 Sequence: []*snap.SideInfo{si1}, 7651 Current: si1.Revision, 7652 }) 7653 snaptest.MockSnapWithFiles(c, "name: snapd\ntype: snapd\nversion: 123", si1, nil) 7654 7655 si2 := &snap.SideInfo{RealName: "core20", Revision: snap.R(1)} 7656 snapstate.Set(st, "core20", &snapstate.SnapState{ 7657 SnapType: "base", 7658 Active: true, 7659 Sequence: []*snap.SideInfo{si2}, 7660 Current: si2.Revision, 7661 }) 7662 si3 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(1)} 7663 snapstate.Set(st, "pc-kernel", &snapstate.SnapState{ 7664 SnapType: "kernel", 7665 Active: true, 7666 Sequence: []*snap.SideInfo{si3}, 7667 Current: si3.Revision, 7668 }) 7669 si4 := &snap.SideInfo{RealName: "pc", Revision: snap.R(1)} 7670 snapstate.Set(st, "pc", &snapstate.SnapState{ 7671 SnapType: "gadget", 7672 Active: true, 7673 Sequence: []*snap.SideInfo{si4}, 7674 Current: si4.Revision, 7675 }) 7676 snaptest.MockSnapWithFiles(c, pcGadget, si4, currentFiles) 7677 7678 // setup model assertion 7679 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 7680 devicestatetest.SetDevice(st, &auth.DeviceState{ 7681 Brand: "my-brand", 7682 Model: "my-model", 7683 Serial: "serialserialserial", 7684 }) 7685 err = assertstate.Add(st, model) 7686 c.Assert(err, IsNil) 7687 7688 ts, _, err := snapstate.InstallPath(st, gadgetSideInfo, gadgetPath, "", "", snapstate.Flags{}) 7689 c.Assert(err, IsNil) 7690 7691 chg := st.NewChange("install-snap", "...") 7692 chg.AddAll(ts) 7693 7694 st.Unlock() 7695 err = s.o.Settle(settleTimeout) 7696 st.Lock() 7697 c.Assert(err, IsNil) 7698 7699 if update { 7700 // when updated, a system restart will be requested 7701 c.Check(chg.Status(), Equals, state.DoingStatus, Commentf("change failed: %v", chg.Err())) 7702 restarting, kind := st.Restarting() 7703 c.Check(restarting, Equals, true) 7704 c.Assert(kind, Equals, state.RestartSystem) 7705 7706 // simulate successful system restart happened 7707 state.MockRestarting(st, state.RestartUnset) 7708 7709 m, err := boot.ReadModeenv("") 7710 c.Assert(err, IsNil) 7711 // old and pending command line 7712 c.Assert(m.CurrentKernelCommandLines, HasLen, 2) 7713 7714 restore := osutil.MockProcCmdline(cmdlineAfterRebootPath) 7715 defer restore() 7716 7717 // reset bootstate, so that after-reboot command line is 7718 // asserted 7719 st.Unlock() 7720 s.o.DeviceManager().ResetToPostBootState() 7721 err = s.o.DeviceManager().Ensure() 7722 st.Lock() 7723 c.Assert(err, IsNil) 7724 7725 // let the change run its course 7726 st.Unlock() 7727 err = s.o.Settle(settleTimeout) 7728 st.Lock() 7729 c.Assert(err, IsNil) 7730 } 7731 7732 c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("change failed: %v", chg.Err())) 7733 } 7734 7735 func (s *mgrsSuite) TestGadgetKernelCommandLineAddCmdline(c *C) { 7736 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7737 mabloader.StaticCommandLine = "mock static" 7738 bootloader.Force(mabloader) 7739 defer bootloader.Force(nil) 7740 7741 err := mabloader.SetBootVars(map[string]string{ 7742 "snapd_extra_cmdline_args": "", 7743 "snapd_full_cmdline_args": "", 7744 }) 7745 c.Assert(err, IsNil) 7746 7747 // add new gadget snap kernel command line drop-in file 7748 sf := snaptest.MakeTestSnapWithFiles(c, pcGadget, [][]string{ 7749 {"meta/gadget.yaml", pcGadgetYaml}, 7750 {"cmdline.extra", "args from gadget"}, 7751 }) 7752 7753 const currentCmdline = "snapd_recovery_mode=run mock static" 7754 const update = true 7755 currentFiles := [][]string{{"meta/gadget.yaml", pcGadgetYaml}} 7756 const cmdlineAfterReboot = "snapd_recovery_mode=run mock static args from gadget" 7757 s.testGadgetKernelCommandLine(c, sf, &snap.SideInfo{RealName: "pc"}, mabloader, 7758 currentFiles, currentCmdline, cmdlineAfterReboot, update) 7759 7760 m, err := boot.ReadModeenv("") 7761 c.Assert(err, IsNil) 7762 c.Assert([]string(m.CurrentKernelCommandLines), DeepEquals, []string{ 7763 "snapd_recovery_mode=run mock static args from gadget", 7764 }) 7765 vars, err := mabloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 7766 c.Assert(err, IsNil) 7767 c.Assert(vars, DeepEquals, map[string]string{ 7768 "snapd_extra_cmdline_args": "args from gadget", 7769 "snapd_full_cmdline_args": "", 7770 }) 7771 } 7772 7773 func (s *mgrsSuite) TestGadgetKernelCommandLineRemoveCmdline(c *C) { 7774 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7775 mabloader.StaticCommandLine = "mock static" 7776 bootloader.Force(mabloader) 7777 defer bootloader.Force(nil) 7778 7779 err := mabloader.SetBootVars(map[string]string{ 7780 "snapd_extra_cmdline_args": "args from gadget", 7781 "snapd_full_cmdline_args": "", 7782 }) 7783 c.Assert(err, IsNil) 7784 7785 // current gadget has the command line 7786 currentFiles := [][]string{ 7787 {"meta/gadget.yaml", pcGadgetYaml}, 7788 {"cmdline.extra", "args from old gadget"}, 7789 } 7790 // add new gadget snap kernel command line without the file 7791 sf := snaptest.MakeTestSnapWithFiles(c, pcGadget, [][]string{ 7792 {"meta/gadget.yaml", pcGadgetYaml}, 7793 }) 7794 7795 const currentCmdline = "snapd_recovery_mode=run mock static args from old gadget" 7796 const update = true 7797 const cmdlineAfterReboot = "snapd_recovery_mode=run mock static" 7798 s.testGadgetKernelCommandLine(c, sf, &snap.SideInfo{RealName: "pc"}, mabloader, 7799 currentFiles, currentCmdline, cmdlineAfterReboot, update) 7800 7801 m, err := boot.ReadModeenv("") 7802 c.Assert(err, IsNil) 7803 c.Assert([]string(m.CurrentKernelCommandLines), DeepEquals, []string{ 7804 "snapd_recovery_mode=run mock static", 7805 }) 7806 vars, err := mabloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 7807 c.Assert(err, IsNil) 7808 c.Assert(vars, DeepEquals, map[string]string{ 7809 "snapd_extra_cmdline_args": "", 7810 "snapd_full_cmdline_args": "", 7811 }) 7812 } 7813 7814 func (s *mgrsSuite) TestGadgetKernelCommandLineNoChange(c *C) { 7815 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7816 mabloader.StaticCommandLine = "mock static" 7817 bootloader.Force(mabloader) 7818 defer bootloader.Force(nil) 7819 7820 err := mabloader.SetBootVars(map[string]string{ 7821 "snapd_extra_cmdline_args": "args from gadget", 7822 "snapd_full_cmdline_args": "", 7823 }) 7824 c.Assert(err, IsNil) 7825 // current gadget has the command line 7826 currentFiles := [][]string{ 7827 {"meta/gadget.yaml", pcGadgetYaml}, 7828 {"cmdline.extra", "args from gadget"}, 7829 } 7830 // add new gadget snap kernel command line drop-in file 7831 sf := snaptest.MakeTestSnapWithFiles(c, pcGadget, [][]string{ 7832 {"meta/gadget.yaml", pcGadgetYaml}, 7833 {"cmdline.extra", "args from gadget"}, 7834 }) 7835 7836 const currentCmdline = "snapd_recovery_mode=run mock static args from gadget" 7837 const update = false 7838 const cmdlineAfterReboot = "snapd_recovery_mode=run mock static args from gadget" 7839 s.testGadgetKernelCommandLine(c, sf, &snap.SideInfo{RealName: "pc"}, mabloader, 7840 currentFiles, currentCmdline, cmdlineAfterReboot, update) 7841 7842 m, err := boot.ReadModeenv("") 7843 c.Assert(err, IsNil) 7844 c.Assert([]string(m.CurrentKernelCommandLines), DeepEquals, []string{ 7845 "snapd_recovery_mode=run mock static args from gadget", 7846 }) 7847 // bootenv is unchanged 7848 vars, err := mabloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 7849 c.Assert(err, IsNil) 7850 c.Assert(vars, DeepEquals, map[string]string{ 7851 "snapd_extra_cmdline_args": "args from gadget", 7852 "snapd_full_cmdline_args": "", 7853 }) 7854 } 7855 7856 func (s *mgrsSuite) TestGadgetKernelCommandLineTransitionExtraToFull(c *C) { 7857 mabloader := bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets() 7858 mabloader.StaticCommandLine = "mock static" 7859 bootloader.Force(mabloader) 7860 defer bootloader.Force(nil) 7861 7862 err := mabloader.SetBootVars(map[string]string{ 7863 "snapd_extra_cmdline_args": "extra args", 7864 "snapd_full_cmdline_args": "", 7865 }) 7866 c.Assert(err, IsNil) 7867 7868 // add new gadget snap kernel command line drop-in file 7869 sf := snaptest.MakeTestSnapWithFiles(c, pcGadget, [][]string{ 7870 {"meta/gadget.yaml", pcGadgetYaml}, 7871 {"cmdline.full", "full args"}, 7872 }) 7873 7874 const currentCmdline = "snapd_recovery_mode=run mock static extra args" 7875 const update = true 7876 currentFiles := [][]string{ 7877 {"meta/gadget.yaml", pcGadgetYaml}, 7878 {"cmdline.extra", "extra args"}, 7879 } 7880 const cmdlineAfterReboot = "snapd_recovery_mode=run full args" 7881 s.testGadgetKernelCommandLine(c, sf, &snap.SideInfo{RealName: "pc"}, mabloader, 7882 currentFiles, currentCmdline, cmdlineAfterReboot, update) 7883 7884 m, err := boot.ReadModeenv("") 7885 c.Assert(err, IsNil) 7886 c.Assert([]string(m.CurrentKernelCommandLines), DeepEquals, []string{ 7887 "snapd_recovery_mode=run full args", 7888 }) 7889 vars, err := mabloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 7890 c.Assert(err, IsNil) 7891 c.Assert(vars, DeepEquals, map[string]string{ 7892 "snapd_extra_cmdline_args": "", 7893 "snapd_full_cmdline_args": "full args", 7894 }) 7895 } 7896 7897 type gadgetUpdatesSuite struct { 7898 baseMgrsSuite 7899 7900 bloader *boottest.Bootenv16 7901 } 7902 7903 var _ = Suite(&gadgetUpdatesSuite{}) 7904 7905 func (ms *gadgetUpdatesSuite) SetUpTest(c *C) { 7906 ms.baseMgrsSuite.SetUpTest(c) 7907 7908 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 7909 bootloader.Force(bloader) 7910 ms.AddCleanup(func() { bootloader.Force(nil) }) 7911 bloader.BootVars = map[string]string{ 7912 "snap_core": "core18_2.snap", 7913 "snap_kernel": "pc-kernel_1.snap", 7914 "snap_mode": boot.DefaultStatus, 7915 } 7916 ms.bloader = bloader 7917 7918 restore := release.MockOnClassic(false) 7919 ms.AddCleanup(restore) 7920 7921 mockServer := ms.mockStore(c) 7922 ms.AddCleanup(mockServer.Close) 7923 7924 st := ms.o.State() 7925 st.Lock() 7926 defer st.Unlock() 7927 7928 // setup model assertion 7929 model := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{ 7930 "gadget": "pi", 7931 "kernel": "pi-kernel", 7932 }) 7933 devicestatetest.SetDevice(st, &auth.DeviceState{ 7934 Brand: "can0nical", 7935 Model: "my-model", 7936 Serial: "serialserial", 7937 }) 7938 err := assertstate.Add(st, model) 7939 c.Assert(err, IsNil) 7940 } 7941 7942 // makeMockDev mocks /dev/disk/by-label/{structureName} and the mount 7943 // point /run/mnt/{structureName} under the test rootdir and for 7944 // osutil.LoadMountInfo for use by gadget code for test gadgets using 7945 // structureName. This is useful for e.g. end-to-end testing of gadget 7946 // assets installs/updates. 7947 func (ms *gadgetUpdatesSuite) makeMockedDev(c *C, structureName string) { 7948 // mock /dev/disk/by-label/{structureName} 7949 byLabelDir := filepath.Join(dirs.GlobalRootDir, "/dev/disk/by-label/") 7950 err := os.MkdirAll(byLabelDir, 0755) 7951 c.Assert(err, IsNil) 7952 // create fakedevice node 7953 err = ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/dev/fakedevice0p1"), nil, 0644) 7954 c.Assert(err, IsNil) 7955 // and point the mocked by-label entry to the fakedevice node 7956 err = os.Symlink(filepath.Join(dirs.GlobalRootDir, "/dev/fakedevice0p1"), filepath.Join(byLabelDir, structureName)) 7957 c.Assert(err, IsNil) 7958 7959 // mock /proc/self/mountinfo with the above generated paths 7960 ms.AddCleanup(osutil.MockMountInfo(fmt.Sprintf("26 27 8:3 / %[1]s/run/mnt/%[2]s rw,relatime shared:7 - vfat %[1]s/dev/fakedevice0p1 rw", dirs.GlobalRootDir, structureName))) 7961 7962 // and mock the mount point 7963 err = os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName), 0755) 7964 c.Assert(err, IsNil) 7965 7966 } 7967 7968 // tsWithoutReRefresh removes the re-refresh task from the given taskset. 7969 // 7970 // It assumes that re-refresh is the last task and will fail if that is 7971 // not the case. 7972 // 7973 // This is needed because settle() will not converge with the re-refresh 7974 // task because re-refresh will always be in doing state. 7975 // 7976 // TODO: have variant of Settle() that ends if ensure next time is 7977 // stable or in the future by a value larger than some threshold, and 7978 // then we would mock the rerefresh interval to something large and 7979 // distinct from practical wait time even on slow systems. Once that 7980 // is done this function can be removed. 7981 func tsWithoutReRefresh(c *C, ts *state.TaskSet) *state.TaskSet { 7982 refreshIdx := len(ts.Tasks()) - 1 7983 c.Assert(ts.Tasks()[refreshIdx].Kind(), Equals, "check-rerefresh") 7984 ts = state.NewTaskSet(ts.Tasks()[:refreshIdx-1]...) 7985 return ts 7986 } 7987 7988 // mockSnapUpgradeWithFiles will put a "rev 2" of the given snapYaml/files 7989 // into the mock snapstore 7990 func (ms *gadgetUpdatesSuite) mockSnapUpgradeWithFiles(c *C, snapYaml string, files [][]string) { 7991 snapPath, _ := ms.makeStoreTestSnapWithFiles(c, snapYaml, "2", files) 7992 ms.serveSnap(snapPath, "2") 7993 } 7994 7995 func (ms *gadgetUpdatesSuite) TestRefreshGadgetUpdates(c *C) { 7996 structureName := "ubuntu-seed" 7997 gadgetYaml := fmt.Sprintf(` 7998 volumes: 7999 volume-id: 8000 schema: mbr 8001 bootloader: u-boot 8002 structure: 8003 - name: %s 8004 filesystem: vfat 8005 type: 0C 8006 size: 1200M 8007 content: 8008 - source: boot-assets/ 8009 target: / 8010 - source: foo.img 8011 target: /subdir/foo-renamed.img`, structureName) 8012 newGadgetYaml := gadgetYaml + ` 8013 update: 8014 edition: 2 8015 ` 8016 ms.makeMockedDev(c, structureName) 8017 8018 st := ms.o.State() 8019 st.Lock() 8020 defer st.Unlock() 8021 8022 // we have an installed gadget 8023 gadgetSnapYaml := "name: pi\nversion: 1.0\ntype: gadget" 8024 ms.mockInstalledSnapWithFiles(c, gadgetSnapYaml, [][]string{ 8025 {"meta/gadget.yaml", gadgetYaml}, 8026 }) 8027 8028 // add new gadget snap to fake store 8029 ms.mockSnapUpgradeWithFiles(c, gadgetSnapYaml, [][]string{ 8030 {"meta/gadget.yaml", newGadgetYaml}, 8031 {"boot-assets/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev2"}, 8032 {"boot-assets/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev2"}, 8033 {"boot-assets/overlays/uart0.dtbo", "uart0.dtbo rev2"}, 8034 {"foo.img", "foo rev2"}, 8035 }) 8036 8037 ts, err := snapstate.Update(st, "pi", nil, 0, snapstate.Flags{}) 8038 c.Assert(err, IsNil) 8039 // remove the re-refresh as it will prevent settle from converging 8040 ts = tsWithoutReRefresh(c, ts) 8041 8042 chg := st.NewChange("upgrade-gadget", "...") 8043 chg.AddAll(ts) 8044 8045 st.Unlock() 8046 err = ms.o.Settle(settleTimeout) 8047 st.Lock() 8048 c.Assert(err, IsNil) 8049 8050 // pretend we restarted 8051 t := findKind(chg, "auto-connect") 8052 c.Assert(t, NotNil) 8053 c.Assert(t.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err())) 8054 // simulate successful restart happened 8055 state.MockRestarting(st, state.RestartUnset) 8056 8057 // settle again 8058 st.Unlock() 8059 err = ms.o.Settle(settleTimeout) 8060 st.Lock() 8061 c.Assert(err, IsNil) 8062 8063 c.Assert(chg.Err(), IsNil) 8064 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 8065 8066 // check that files/dirs got updated and subdirs are correct 8067 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "subdir/foo-renamed.img"), testutil.FileContains, "foo rev2") 8068 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev2") 8069 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev2") 8070 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileContains, "uart0.dtbo rev2") 8071 } 8072 8073 func (ms *gadgetUpdatesSuite) TestGadgetWithKernelRefKernelRefresh(c *C) { 8074 kernelYaml := ` 8075 assets: 8076 pidtbs: 8077 update: true 8078 content: 8079 - dtbs/broadcom/ 8080 - dtbs/overlays/` 8081 8082 structureName := "ubuntu-seed" 8083 gadgetYaml := fmt.Sprintf(` 8084 volumes: 8085 volume-id: 8086 schema: mbr 8087 bootloader: u-boot 8088 structure: 8089 - name: %s 8090 filesystem: vfat 8091 type: 0C 8092 size: 1200M 8093 content: 8094 - source: boot-assets/ 8095 target: / 8096 - source: $kernel:pidtbs/dtbs/broadcom/ 8097 target: / 8098 - source: $kernel:pidtbs/dtbs/overlays/ 8099 target: /overlays`, structureName) 8100 ms.makeMockedDev(c, structureName) 8101 8102 st := ms.o.State() 8103 st.Lock() 8104 defer st.Unlock() 8105 8106 // we have an installed gadget with kernel refs 8107 gadgetSnapYaml := "name: pi\nversion: 1.0\ntype: gadget" 8108 ms.mockInstalledSnapWithFiles(c, gadgetSnapYaml, [][]string{ 8109 {"meta/gadget.yaml", gadgetYaml}, 8110 {"boot-assets/start.elf", "start.elf rev1"}, 8111 }) 8112 // we have an installed kernel with kernel.yaml 8113 kernelSnapYaml := "name: pi-kernel\nversion: 1.0\ntype: kernel" 8114 ms.mockInstalledSnapWithFiles(c, kernelSnapYaml, [][]string{ 8115 {"meta/kernel.yaml", kernelYaml}, 8116 }) 8117 8118 // add new kernel snap to fake store 8119 ms.mockSnapUpgradeWithFiles(c, kernelSnapYaml, [][]string{ 8120 {"meta/kernel.yaml", kernelYaml}, 8121 {"dtbs/broadcom/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev2"}, 8122 {"dtbs/broadcom/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev2"}, 8123 {"dtbs/overlays/uart0.dtbo", "uart0.dtbo rev2"}, 8124 }) 8125 8126 ts, err := snapstate.Update(st, "pi-kernel", nil, 0, snapstate.Flags{}) 8127 c.Assert(err, IsNil) 8128 // remove the re-refresh as it will prevent settle from converging 8129 ts = tsWithoutReRefresh(c, ts) 8130 8131 chg := st.NewChange("upgrade-kernel", "...") 8132 chg.AddAll(ts) 8133 8134 st.Unlock() 8135 err = ms.o.Settle(settleTimeout) 8136 st.Lock() 8137 c.Assert(err, IsNil) 8138 c.Assert(chg.Err(), IsNil) 8139 8140 // pretend we restarted 8141 t := findKind(chg, "auto-connect") 8142 c.Assert(t, NotNil) 8143 c.Assert(t.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err())) 8144 // pretend we restarted 8145 ms.mockSuccessfulReboot(c, ms.bloader, []snap.Type{snap.TypeKernel}) 8146 8147 // settle again 8148 st.Unlock() 8149 err = ms.o.Settle(settleTimeout) 8150 st.Lock() 8151 c.Assert(err, IsNil) 8152 c.Assert(chg.Err(), IsNil) 8153 8154 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 8155 8156 // check that files/dirs got updated and subdirs are correct 8157 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev2") 8158 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev2") 8159 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileContains, "uart0.dtbo rev2") 8160 // BUT the gadget content is ignored and not copied again 8161 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "start.elf"), testutil.FileAbsent) 8162 } 8163 8164 func (ms *gadgetUpdatesSuite) TestGadgetWithKernelRefGadgetRefresh(c *C) { 8165 kernelYaml := ` 8166 assets: 8167 pidtbs: 8168 update: true 8169 content: 8170 - dtbs/broadcom/ 8171 - dtbs/overlays/` 8172 8173 structureName := "ubuntu-seed" 8174 gadgetYaml := fmt.Sprintf(` 8175 volumes: 8176 volume-id: 8177 schema: mbr 8178 bootloader: u-boot 8179 structure: 8180 - name: %s 8181 filesystem: vfat 8182 type: 0C 8183 size: 1200M 8184 content: 8185 - source: boot-assets/ 8186 target: / 8187 - source: $kernel:pidtbs/dtbs/broadcom/ 8188 target: / 8189 - source: $kernel:pidtbs/dtbs/overlays/ 8190 target: /overlays`, structureName) 8191 newGadgetYaml := gadgetYaml + ` 8192 update: 8193 edition: 2 8194 ` 8195 ms.makeMockedDev(c, structureName) 8196 8197 st := ms.o.State() 8198 st.Lock() 8199 defer st.Unlock() 8200 8201 // we have an installed gadget with kernel refs 8202 gadgetSnapYaml := "name: pi\nversion: 1.0\ntype: gadget" 8203 ms.mockInstalledSnapWithFiles(c, gadgetSnapYaml, [][]string{ 8204 {"meta/gadget.yaml", gadgetYaml}, 8205 }) 8206 // we have an installed kernel with kernel.yaml 8207 kernelSnapYaml := "name: pi-kernel\nversion: 1.0\ntype: kernel" 8208 ms.mockInstalledSnapWithFiles(c, kernelSnapYaml, [][]string{ 8209 {"meta/kernel.yaml", kernelYaml}, 8210 {"dtbs/broadcom/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev2"}, 8211 {"dtbs/broadcom/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev2"}, 8212 {"dtbs/overlays/uart0.dtbo", "uart0.dtbo rev2"}, 8213 }) 8214 8215 // add new gadget snap to fake store that has an "update: true" 8216 // for the kernel ref structure 8217 ms.mockSnapUpgradeWithFiles(c, gadgetSnapYaml, [][]string{ 8218 {"meta/gadget.yaml", newGadgetYaml}, 8219 {"boot-assets/start.elf", "start.elf rev2"}, 8220 }) 8221 8222 ts, err := snapstate.Update(st, "pi", nil, 0, snapstate.Flags{}) 8223 c.Assert(err, IsNil) 8224 // remove the re-refresh as it will prevent settle from converging 8225 ts = tsWithoutReRefresh(c, ts) 8226 8227 chg := st.NewChange("upgrade-gadget", "...") 8228 chg.AddAll(ts) 8229 8230 st.Unlock() 8231 err = ms.o.Settle(settleTimeout) 8232 st.Lock() 8233 c.Assert(err, IsNil) 8234 c.Assert(chg.Err(), IsNil) 8235 8236 // pretend we restarted 8237 t := findKind(chg, "auto-connect") 8238 c.Assert(t, NotNil) 8239 c.Assert(t.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err())) 8240 // simulate successful restart happened after gadget update 8241 state.MockRestarting(st, state.RestartUnset) 8242 8243 // settle again 8244 st.Unlock() 8245 err = ms.o.Settle(settleTimeout) 8246 st.Lock() 8247 c.Assert(err, IsNil) 8248 c.Assert(chg.Err(), IsNil) 8249 8250 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err())) 8251 8252 // check that files/dirs got updated and subdirs are correct 8253 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev2") 8254 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev2") 8255 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileContains, "uart0.dtbo rev2") 8256 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "start.elf"), testutil.FileContains, "start.elf rev2") 8257 } 8258 8259 func (ms *gadgetUpdatesSuite) TestGadgetWithKernelRefUpgradeFromOld(c *C) { 8260 kernelYaml := ` 8261 assets: 8262 pidtbs: 8263 update: true 8264 content: 8265 - dtbs/broadcom/ 8266 - dtbs/overlays/` 8267 8268 structureName := "ubuntu-seed" 8269 oldGadgetYaml := fmt.Sprintf(` 8270 volumes: 8271 volume-id: 8272 schema: mbr 8273 bootloader: u-boot 8274 structure: 8275 - name: %s 8276 filesystem: vfat 8277 type: 0C 8278 size: 1200M 8279 content: 8280 - source: boot-assets/ 8281 target: /`, structureName) 8282 // Note that there is no "edition" jump here for the new "$kernel:ref" 8283 // content. This is driven by the kernel.yaml "update: true" value. 8284 newGadgetYaml := fmt.Sprintf(` 8285 volumes: 8286 volume-id: 8287 schema: mbr 8288 bootloader: u-boot 8289 structure: 8290 - name: %s 8291 filesystem: vfat 8292 type: 0C 8293 size: 1200M 8294 content: 8295 - source: boot-assets/ 8296 target: / 8297 - source: $kernel:pidtbs/dtbs/broadcom/ 8298 target: / 8299 - source: $kernel:pidtbs/dtbs/overlays/ 8300 target: /overlays`, structureName) 8301 ms.makeMockedDev(c, structureName) 8302 8303 st := ms.o.State() 8304 st.Lock() 8305 defer st.Unlock() 8306 8307 // we have an installed old style pi gadget 8308 gadgetSnapYaml := "name: pi\nversion: 1.0\ntype: gadget" 8309 ms.mockInstalledSnapWithFiles(c, gadgetSnapYaml, [][]string{ 8310 {"meta/gadget.yaml", oldGadgetYaml}, 8311 {"boot-assets/start.elf", "start.elf rev1"}, 8312 {"boot-assets/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev1"}, 8313 {"boot-assets/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev1"}, 8314 }) 8315 // we have old style boot asssets in the bootloader dir 8316 snaptest.PopulateDir(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName), [][]string{ 8317 {"start.elf", "start.elf rev1"}, 8318 {"bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev1"}, 8319 {"bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev1"}, 8320 }) 8321 8322 // we have an installed old-style kernel snap 8323 kernelSnapYaml := "name: pi-kernel\nversion: 1.0\ntype: kernel" 8324 ms.mockInstalledSnapWithFiles(c, kernelSnapYaml, nil) 8325 8326 // add new kernel snap with kernel-refs to fake store 8327 ms.mockSnapUpgradeWithFiles(c, kernelSnapYaml, [][]string{ 8328 {"meta/kernel.yaml", kernelYaml}, 8329 {"dtbs/broadcom/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev2-from-kernel"}, 8330 {"dtbs/broadcom/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev2-from-kernel"}, 8331 {"dtbs/overlays/uart0.dtbo", "uart0.dtbo rev2-from-kernel"}, 8332 }) 8333 8334 // add new gadget snap with kernel-refs to fake store 8335 ms.mockSnapUpgradeWithFiles(c, gadgetSnapYaml, [][]string{ 8336 {"meta/gadget.yaml", newGadgetYaml}, 8337 {"boot-assets/start.elf", "start.elf rev1"}, 8338 // notice: no dtbs anymore in the gadget 8339 }) 8340 8341 affected, tasksets, err := snapstate.UpdateMany(context.TODO(), st, nil, 0, &snapstate.Flags{}) 8342 c.Assert(err, IsNil) 8343 sort.Strings(affected) 8344 c.Check(affected, DeepEquals, []string{"pi", "pi-kernel"}) 8345 8346 chg := st.NewChange("upgrade-snaps", "...") 8347 for _, ts := range tasksets { 8348 // skip the taskset of UpdateMany that does the 8349 // check-rerefresh, see tsWithoutReRefresh for details 8350 if ts.Tasks()[0].Kind() == "check-rerefresh" { 8351 continue 8352 } 8353 chg.AddAll(ts) 8354 } 8355 8356 st.Unlock() 8357 err = ms.o.Settle(settleTimeout) 8358 st.Lock() 8359 c.Assert(err, IsNil) 8360 c.Assert(chg.Err(), IsNil) 8361 8362 // At this point the gadget and kernel are updated and the kernel 8363 // required a restart. Check that *before* this restart the DTB 8364 // files from the kernel are in place. 8365 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev2-from-kernel") 8366 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev2-from-kernel") 8367 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileContains, "uart0.dtbo rev2-from-kernel") 8368 // gadget content is not updated because there is no edition update 8369 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "start.elf"), testutil.FileContains, "start.elf rev1") 8370 8371 // pretend we restarted 8372 ms.mockSuccessfulReboot(c, ms.bloader, []snap.Type{snap.TypeKernel}) 8373 8374 // settle again 8375 st.Unlock() 8376 err = ms.o.Settle(settleTimeout) 8377 st.Lock() 8378 c.Assert(err, IsNil) 8379 c.Assert(chg.Err(), IsNil) 8380 } 8381 8382 func snapTaskStatusForChange(chg *state.Change) map[string]state.Status { 8383 taskStates := make(map[string]state.Status) 8384 for _, t := range chg.Tasks() { 8385 if snapsup, err := snapstate.TaskSnapSetup(t); err == nil { 8386 taskStates[snapsup.SnapName()+":"+t.Kind()] = t.Status() 8387 } 8388 } 8389 return taskStates 8390 } 8391 8392 func (ms *gadgetUpdatesSuite) TestGadgetWithKernelRefUpgradeFromOldErrorGadget(c *C) { 8393 kernelYaml := ` 8394 assets: 8395 pidtbs: 8396 update: true 8397 content: 8398 - dtbs/broadcom/ 8399 - dtbs/overlays/` 8400 8401 structureName := "ubuntu-seed" 8402 oldGadgetYaml := fmt.Sprintf(` 8403 volumes: 8404 volume-id: 8405 schema: mbr 8406 bootloader: u-boot 8407 structure: 8408 - name: %s 8409 filesystem: vfat 8410 type: 0C 8411 size: 1200M 8412 content: 8413 - source: boot-assets/ 8414 target: /`, structureName) 8415 // Note that there is no "edition" jump here for the new "$kernel:ref" 8416 // content. This is driven by the kernel.yaml "update: true" value. 8417 newGadgetYaml := fmt.Sprintf(` 8418 volumes: 8419 volume-id: 8420 schema: mbr 8421 bootloader: u-boot 8422 structure: 8423 - name: %s 8424 filesystem: vfat 8425 type: 0C 8426 size: 1200M 8427 content: 8428 - source: boot-assets/ 8429 target: / 8430 - source: $kernel:pidtbs/dtbs/broadcom/ 8431 target: / 8432 - source: $kernel:pidtbs/dtbs/overlays/ 8433 target: /overlays`, structureName) 8434 ms.makeMockedDev(c, structureName) 8435 8436 st := ms.o.State() 8437 st.Lock() 8438 defer st.Unlock() 8439 8440 // we have an installed old style pi gadget 8441 gadgetSnapYaml := "name: pi\nversion: 1.0\ntype: gadget" 8442 ms.mockInstalledSnapWithFiles(c, gadgetSnapYaml, [][]string{ 8443 {"meta/gadget.yaml", oldGadgetYaml}, 8444 {"boot-assets/start.elf", "start.elf rev1"}, 8445 {"boot-assets/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev1"}, 8446 {"boot-assets/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev1"}, 8447 }) 8448 // we have old style boot asssets in the bootloader dir 8449 snaptest.PopulateDir(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName), [][]string{ 8450 {"start.elf", "start.elf rev1"}, 8451 {"bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev1"}, 8452 {"bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev1"}, 8453 }) 8454 8455 // we have an installed old-style kernel snap 8456 kernelSnapYaml := "name: pi-kernel\nversion: 1.0\ntype: kernel" 8457 ms.mockInstalledSnapWithFiles(c, kernelSnapYaml, nil) 8458 8459 // add new kernel snap with kernel-refs to fake store 8460 ms.mockSnapUpgradeWithFiles(c, kernelSnapYaml, [][]string{ 8461 {"meta/kernel.yaml", kernelYaml}, 8462 {"dtbs/broadcom/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev2-from-kernel"}, 8463 {"dtbs/broadcom/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev2-from-kernel"}, 8464 {"dtbs/overlays/uart0.dtbo", "uart0.dtbo rev2-from-kernel"}, 8465 }) 8466 8467 // add new gadget snap with kernel-refs to fake store 8468 ms.mockSnapUpgradeWithFiles(c, gadgetSnapYaml, [][]string{ 8469 {"meta/gadget.yaml", newGadgetYaml}, 8470 {"boot-assets/start.elf", "start.elf rev1"}, 8471 // notice: no dtbs anymore in the gadget 8472 }) 8473 8474 affected, tasksets, err := snapstate.UpdateMany(context.TODO(), st, nil, 0, &snapstate.Flags{}) 8475 c.Assert(err, IsNil) 8476 sort.Strings(affected) 8477 c.Check(affected, DeepEquals, []string{"pi", "pi-kernel"}) 8478 8479 chg := st.NewChange("upgrade-snaps", "...") 8480 tError := st.NewTask("error-trigger", "gadget failed") 8481 for _, ts := range tasksets { 8482 // skip the taskset of UpdateMany that does the 8483 // check-rerefresh, see tsWithoutReRefresh for details 8484 tasks := ts.Tasks() 8485 if tasks[0].Kind() == "check-rerefresh" { 8486 continue 8487 } 8488 8489 snapsup, err := snapstate.TaskSnapSetup(tasks[0]) 8490 c.Assert(err, IsNil) 8491 // trigger an error as last operation of gadget refresh 8492 if snapsup.SnapName() == "pi" { 8493 last := tasks[len(tasks)-1] 8494 tError.WaitFor(last) 8495 // XXX: or just use "snap-setup" here? 8496 tError.Set("snap-setup-task", tasks[0].ID()) 8497 ts.AddTask(tError) 8498 // must be in the same lane as the gadget update 8499 lanes := last.Lanes() 8500 c.Assert(lanes, HasLen, 1) 8501 tError.JoinLane(lanes[0]) 8502 } 8503 8504 chg.AddAll(ts) 8505 } 8506 8507 st.Unlock() 8508 err = ms.o.Settle(settleTimeout) 8509 st.Lock() 8510 c.Assert(err, IsNil) 8511 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- gadget failed.*`) 8512 8513 // check that files/dirs from the kernel did *not* get updated or installed 8514 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev1") 8515 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev1") 8516 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileAbsent) 8517 8518 // Ensure that tasks states are valid 8519 taskStates := snapTaskStatusForChange(chg) 8520 // The pi gadget failed in error-trigger and got rolled back 8521 c.Check(taskStates["pi:error-trigger"], Equals, state.ErrorStatus) 8522 c.Check(taskStates["pi:mount-snap"], Equals, state.UndoneStatus) 8523 // And the pi-kernel did not even get started 8524 c.Check(taskStates["pi-kernel:download-snap"], Equals, state.HoldStatus) 8525 } 8526 8527 func (ms *gadgetUpdatesSuite) TestGadgetWithKernelRefUpgradeFromOldErrorKernel(c *C) { 8528 kernelYaml := ` 8529 assets: 8530 pidtbs: 8531 update: true 8532 content: 8533 - dtbs/broadcom/ 8534 - dtbs/overlays/` 8535 8536 structureName := "ubuntu-seed" 8537 oldGadgetYaml := fmt.Sprintf(` 8538 volumes: 8539 volume-id: 8540 schema: mbr 8541 bootloader: u-boot 8542 structure: 8543 - name: %s 8544 filesystem: vfat 8545 type: 0C 8546 size: 1200M 8547 content: 8548 - source: boot-assets/ 8549 target: /`, structureName) 8550 // Note that there is no "edition" jump here for the new "$kernel:ref" 8551 // content. This is driven by the kernel.yaml "update: true" value. 8552 newGadgetYaml := fmt.Sprintf(` 8553 volumes: 8554 volume-id: 8555 schema: mbr 8556 bootloader: u-boot 8557 structure: 8558 - name: %s 8559 filesystem: vfat 8560 type: 0C 8561 size: 1200M 8562 content: 8563 - source: boot-assets/ 8564 target: / 8565 - source: $kernel:pidtbs/dtbs/broadcom/ 8566 target: / 8567 - source: $kernel:pidtbs/dtbs/overlays/ 8568 target: /overlays`, structureName) 8569 ms.makeMockedDev(c, structureName) 8570 8571 st := ms.o.State() 8572 st.Lock() 8573 defer st.Unlock() 8574 8575 // we have an installed old style pi gadget 8576 gadgetSnapYaml := "name: pi\nversion: 1.0\ntype: gadget" 8577 ms.mockInstalledSnapWithFiles(c, gadgetSnapYaml, [][]string{ 8578 {"meta/gadget.yaml", oldGadgetYaml}, 8579 {"boot-assets/start.elf", "start.elf rev1"}, 8580 {"boot-assets/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev1"}, 8581 {"boot-assets/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev1"}, 8582 }) 8583 // we have old style boot asssets in the bootloader dir 8584 snaptest.PopulateDir(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName), [][]string{ 8585 {"start.elf", "start.elf rev1"}, 8586 {"bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev1"}, 8587 {"bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev1"}, 8588 }) 8589 8590 // we have an installed old-style kernel snap 8591 kernelSnapYaml := "name: pi-kernel\nversion: 1.0\ntype: kernel" 8592 ms.mockInstalledSnapWithFiles(c, kernelSnapYaml, nil) 8593 8594 // add new kernel snap with kernel-refs to fake store 8595 ms.mockSnapUpgradeWithFiles(c, kernelSnapYaml, [][]string{ 8596 {"meta/kernel.yaml", kernelYaml}, 8597 {"dtbs/broadcom/bcm2710-rpi-2-b.dtb", "bcm2710-rpi-2-b.dtb rev2-from-kernel"}, 8598 {"dtbs/broadcom/bcm2710-rpi-3-b.dtb", "bcm2710-rpi-3-b.dtb rev2-from-kernel"}, 8599 {"dtbs/overlays/uart0.dtbo", "uart0.dtbo rev2-from-kernel"}, 8600 }) 8601 8602 // add new gadget snap with kernel-refs to fake store 8603 ms.mockSnapUpgradeWithFiles(c, gadgetSnapYaml, [][]string{ 8604 {"meta/gadget.yaml", newGadgetYaml}, 8605 {"boot-assets/start.elf", "start.elf rev1"}, 8606 // notice: no dtbs anymore in the gadget 8607 }) 8608 8609 affected, tasksets, err := snapstate.UpdateMany(context.TODO(), st, nil, 0, &snapstate.Flags{}) 8610 c.Assert(err, IsNil) 8611 sort.Strings(affected) 8612 c.Check(affected, DeepEquals, []string{"pi", "pi-kernel"}) 8613 8614 chg := st.NewChange("upgrade-snaps", "...") 8615 tError := st.NewTask("error-trigger", "kernel failed") 8616 for _, ts := range tasksets { 8617 // skip the taskset of UpdateMany that does the 8618 // check-rerefresh, see tsWithoutReRefresh for details 8619 tasks := ts.Tasks() 8620 if tasks[0].Kind() == "check-rerefresh" { 8621 continue 8622 } 8623 8624 snapsup, err := snapstate.TaskSnapSetup(tasks[0]) 8625 c.Assert(err, IsNil) 8626 // trigger an error as last operation of gadget refresh 8627 if snapsup.SnapName() == "pi-kernel" { 8628 last := tasks[len(tasks)-1] 8629 tError.WaitFor(last) 8630 // XXX: or just use "snap-setup" here? 8631 tError.Set("snap-setup-task", tasks[0].ID()) 8632 ts.AddTask(tError) 8633 // must be in the same lane as the kernel update 8634 lanes := last.Lanes() 8635 c.Assert(lanes, HasLen, 1) 8636 tError.JoinLane(lanes[0]) 8637 } 8638 8639 chg.AddAll(ts) 8640 } 8641 8642 st.Unlock() 8643 err = ms.o.Settle(settleTimeout) 8644 st.Lock() 8645 c.Assert(err, IsNil) 8646 c.Check(chg.Err(), IsNil) 8647 8648 // At this point the gadget and kernel are updated and the kernel 8649 // required a restart. Check that *before* this restart the DTB 8650 // files from the kernel are in place. 8651 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev2-from-kernel") 8652 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev2-from-kernel") 8653 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileContains, "uart0.dtbo rev2-from-kernel") 8654 // gadget content is not updated because there is no edition update 8655 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "start.elf"), testutil.FileContains, "start.elf rev1") 8656 8657 // pretend we restarted 8658 ms.mockSuccessfulReboot(c, ms.bloader, []snap.Type{snap.TypeKernel}) 8659 8660 st.Unlock() 8661 err = ms.o.Settle(settleTimeout) 8662 st.Lock() 8663 c.Assert(err, IsNil) 8664 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- kernel failed.*`) 8665 8666 // Ensure that tasks states are what we expect 8667 taskStates := snapTaskStatusForChange(chg) 8668 // The pi-kernel failed in error-trigger and got rolled back 8669 c.Check(taskStates["pi-kernel:error-trigger"], Equals, state.ErrorStatus) 8670 c.Check(taskStates["pi-kernel:mount-snap"], Equals, state.UndoneStatus) 8671 // But the pi gadget was installed just fine 8672 c.Check(taskStates["pi:download-snap"], Equals, state.DoneStatus) 8673 c.Check(taskStates["pi:link-snap"], Equals, state.DoneStatus) 8674 8675 // Note that the undo of the kernel did *not* revert the DTBs on 8676 // disk. The reason is that we never undo asset updates on the 8677 // basis that if the system booted they are probably good enough. 8678 // A really broken DTB can brick the device if the new DTB is written 8679 // to disk, the system reboots and neither new kernel nor fallback 8680 // kernel will boot because there is no A/B DTB. This is a flaw 8681 // of the Pi and u-boot. 8682 // 8683 // In the future we will integrate with the "pi-boot" mechanism that 8684 // allows doing a A/B boot using the config.txt "os-prefix" dir. This 8685 // will allow us to write the DTBs to A/B locations. 8686 // 8687 // TODO:UC20: port this so that it integrates with pi-boot and the 8688 // A/B os-prefix mechanism there so that we can have 8689 // robust DTB updates. 8690 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-2-b.dtb"), testutil.FileContains, "bcm2710-rpi-2-b.dtb rev2-from-kernel") 8691 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "bcm2710-rpi-3-b.dtb"), testutil.FileContains, "bcm2710-rpi-3-b.dtb rev2-from-kernel") 8692 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "overlays/uart0.dtbo"), testutil.FileContains, "uart0.dtbo rev2-from-kernel") 8693 // gadget content is not updated because there is no edition update 8694 c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/", structureName, "start.elf"), testutil.FileContains, "start.elf rev1") 8695 }