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