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