github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/snapstate/backend_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package snapstate_test 21 22 import ( 23 "context" 24 "errors" 25 "fmt" 26 "io" 27 "os" 28 "path/filepath" 29 "sort" 30 "strings" 31 "sync" 32 33 . "gopkg.in/check.v1" 34 35 "github.com/snapcore/snapd/boot" 36 "github.com/snapcore/snapd/cmd/snaplock/runinhibit" 37 "github.com/snapcore/snapd/osutil" 38 "github.com/snapcore/snapd/overlord/auth" 39 "github.com/snapcore/snapd/overlord/snapstate" 40 "github.com/snapcore/snapd/overlord/snapstate/backend" 41 "github.com/snapcore/snapd/overlord/state" 42 "github.com/snapcore/snapd/progress" 43 "github.com/snapcore/snapd/snap" 44 "github.com/snapcore/snapd/snap/snapfile" 45 "github.com/snapcore/snapd/store" 46 "github.com/snapcore/snapd/store/storetest" 47 "github.com/snapcore/snapd/strutil" 48 "github.com/snapcore/snapd/timings" 49 ) 50 51 type fakeOp struct { 52 op string 53 54 name string 55 channel string 56 path string 57 revno snap.Revision 58 sinfo snap.SideInfo 59 stype snap.Type 60 61 curSnaps []store.CurrentSnap 62 action store.SnapAction 63 64 old string 65 66 aliases []*backend.Alias 67 rmAliases []*backend.Alias 68 69 userID int 70 71 otherInstances bool 72 unlinkFirstInstallUndo bool 73 74 services []string 75 disabledServices []string 76 77 vitalityRank int 78 79 inhibitHint runinhibit.Hint 80 } 81 82 type fakeOps []fakeOp 83 84 func (ops fakeOps) MustFindOp(c *C, opName string) *fakeOp { 85 for _, op := range ops { 86 if op.op == opName { 87 return &op 88 } 89 } 90 c.Errorf("cannot find operation with op: %q, all ops: %v", opName, ops.Ops()) 91 c.FailNow() 92 return nil 93 } 94 95 func (ops fakeOps) Ops() []string { 96 opsOps := make([]string, len(ops)) 97 for i, op := range ops { 98 opsOps[i] = op.op 99 } 100 101 return opsOps 102 } 103 104 func (ops fakeOps) Count(op string) int { 105 n := 0 106 for i := range ops { 107 if ops[i].op == op { 108 n++ 109 } 110 } 111 return n 112 } 113 114 func (ops fakeOps) First(op string) *fakeOp { 115 for i := range ops { 116 if ops[i].op == op { 117 return &ops[i] 118 } 119 } 120 121 return nil 122 } 123 124 type fakeDownload struct { 125 name string 126 macaroon string 127 target string 128 opts *store.DownloadOptions 129 } 130 131 type byName []store.CurrentSnap 132 133 func (bna byName) Len() int { return len(bna) } 134 func (bna byName) Swap(i, j int) { bna[i], bna[j] = bna[j], bna[i] } 135 func (bna byName) Less(i, j int) bool { 136 return bna[i].InstanceName < bna[j].InstanceName 137 } 138 139 type byAction []*store.SnapAction 140 141 func (ba byAction) Len() int { return len(ba) } 142 func (ba byAction) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } 143 func (ba byAction) Less(i, j int) bool { 144 if ba[i].Action == ba[j].Action { 145 if ba[i].Action == "refresh" { 146 return ba[i].SnapID < ba[j].SnapID 147 } else { 148 return ba[i].InstanceName < ba[j].InstanceName 149 } 150 } 151 return ba[i].Action < ba[j].Action 152 } 153 154 type fakeStore struct { 155 storetest.Store 156 157 downloads []fakeDownload 158 refreshRevnos map[string]snap.Revision 159 fakeBackend *fakeSnappyBackend 160 fakeCurrentProgress int 161 fakeTotalProgress int 162 state *state.State 163 seenPrivacyKeys map[string]bool 164 } 165 166 func (f *fakeStore) pokeStateLock() { 167 // the store should be called without the state lock held. Try 168 // to acquire it. 169 f.state.Lock() 170 f.state.Unlock() 171 } 172 173 func (f *fakeStore) SnapInfo(ctx context.Context, spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) { 174 f.pokeStateLock() 175 176 _, instanceKey := snap.SplitInstanceName(spec.Name) 177 if instanceKey != "" { 178 return nil, fmt.Errorf("internal error: unexpected instance name: %q", spec.Name) 179 } 180 sspec := snapSpec{ 181 Name: spec.Name, 182 } 183 info, err := f.snap(sspec, user) 184 185 userID := 0 186 if user != nil { 187 userID = user.ID 188 } 189 f.fakeBackend.appendOp(&fakeOp{op: "storesvc-snap", name: spec.Name, revno: info.Revision, userID: userID}) 190 191 return info, err 192 } 193 194 type snapSpec struct { 195 Name string 196 Channel string 197 Revision snap.Revision 198 Cohort string 199 } 200 201 func (f *fakeStore) snap(spec snapSpec, user *auth.UserState) (*snap.Info, error) { 202 if spec.Revision.Unset() { 203 switch { 204 case spec.Cohort != "": 205 spec.Revision = snap.R(666) 206 case spec.Channel == "channel-for-7": 207 spec.Revision = snap.R(7) 208 default: 209 spec.Revision = snap.R(11) 210 } 211 } 212 213 confinement := snap.StrictConfinement 214 215 typ := snap.TypeApp 216 epoch := snap.E("1*") 217 switch spec.Name { 218 case "core", "core16", "ubuntu-core", "some-core": 219 typ = snap.TypeOS 220 case "some-base", "other-base", "some-other-base", "yet-another-base", "core18": 221 typ = snap.TypeBase 222 case "some-kernel": 223 typ = snap.TypeKernel 224 case "some-gadget", "brand-gadget": 225 typ = snap.TypeGadget 226 case "some-snapd": 227 typ = snap.TypeSnapd 228 case "snapd": 229 typ = snap.TypeSnapd 230 case "some-snap-now-classic": 231 confinement = "classic" 232 case "some-epoch-snap": 233 epoch = snap.E("42") 234 } 235 236 if spec.Name == "snap-unknown" { 237 return nil, store.ErrSnapNotFound 238 } 239 240 info := &snap.Info{ 241 Architectures: []string{"all"}, 242 SideInfo: snap.SideInfo{ 243 RealName: spec.Name, 244 Channel: spec.Channel, 245 SnapID: spec.Name + "-id", 246 Revision: spec.Revision, 247 }, 248 Version: spec.Name, 249 DownloadInfo: snap.DownloadInfo{ 250 DownloadURL: "https://some-server.com/some/path.snap", 251 Size: 5, 252 }, 253 Confinement: confinement, 254 SnapType: typ, 255 Epoch: epoch, 256 } 257 switch spec.Channel { 258 case "channel-no-revision": 259 return nil, &store.RevisionNotAvailableError{} 260 case "channel-for-devmode": 261 info.Confinement = snap.DevModeConfinement 262 case "channel-for-classic": 263 info.Confinement = snap.ClassicConfinement 264 case "channel-for-paid": 265 info.Prices = map[string]float64{"USD": 0.77} 266 info.SideInfo.Paid = true 267 case "channel-for-private": 268 info.SideInfo.Private = true 269 case "channel-for-layout": 270 info.Layout = map[string]*snap.Layout{ 271 "/usr": { 272 Snap: info, 273 Path: "/usr", 274 Symlink: "$SNAP/usr", 275 }, 276 } 277 case "channel-for-user-daemon": 278 info.Apps = map[string]*snap.AppInfo{ 279 "user-daemon": { 280 Snap: info, 281 Name: "user-daemon", 282 Daemon: "simple", 283 DaemonScope: "user", 284 }, 285 } 286 case "channel-for-dbus-activation": 287 slot := &snap.SlotInfo{ 288 Snap: info, 289 Name: "dbus-slot", 290 Interface: "dbus", 291 Attrs: map[string]interface{}{ 292 "bus": "system", 293 "name": "org.example.Foo", 294 }, 295 Apps: make(map[string]*snap.AppInfo), 296 } 297 info.Apps = map[string]*snap.AppInfo{ 298 "dbus-daemon": { 299 Snap: info, 300 Name: "dbus-daemon", 301 Daemon: "simple", 302 DaemonScope: snap.SystemDaemon, 303 ActivatesOn: []*snap.SlotInfo{slot}, 304 Slots: map[string]*snap.SlotInfo{ 305 slot.Name: slot, 306 }, 307 }, 308 } 309 slot.Apps["dbus-daemon"] = info.Apps["dbus-daemon"] 310 } 311 312 return info, nil 313 } 314 315 type refreshCand struct { 316 snapID string 317 channel string 318 revision snap.Revision 319 block []snap.Revision 320 ignoreValidation bool 321 } 322 323 func (f *fakeStore) lookupRefresh(cand refreshCand) (*snap.Info, error) { 324 var name string 325 326 typ := snap.TypeApp 327 epoch := snap.E("1*") 328 switch cand.snapID { 329 case "": 330 panic("store refresh APIs expect snap-ids") 331 case "other-snap-id": 332 return nil, store.ErrNoUpdateAvailable 333 case "fakestore-please-error-on-refresh": 334 return nil, fmt.Errorf("failing as requested") 335 case "services-snap-id": 336 name = "services-snap" 337 case "some-snap-id": 338 name = "some-snap" 339 case "some-other-snap-id": 340 name = "some-other-snap" 341 case "some-epoch-snap-id": 342 name = "some-epoch-snap" 343 epoch = snap.E("42") 344 case "some-snap-now-classic-id": 345 name = "some-snap-now-classic" 346 case "some-snap-was-classic-id": 347 name = "some-snap-was-classic" 348 case "core-snap-id": 349 name = "core" 350 typ = snap.TypeOS 351 case "core18-snap-id": 352 name = "core18" 353 typ = snap.TypeBase 354 case "snap-with-snapd-control-id": 355 name = "snap-with-snapd-control" 356 case "producer-id": 357 name = "producer" 358 case "consumer-id": 359 name = "consumer" 360 case "some-base-id": 361 name = "some-base" 362 typ = snap.TypeBase 363 case "snap-content-plug-id": 364 name = "snap-content-plug" 365 case "snap-content-slot-id": 366 name = "snap-content-slot" 367 case "snapd-snap-id": 368 name = "snapd" 369 typ = snap.TypeSnapd 370 case "kernel-id": 371 name = "kernel" 372 typ = snap.TypeKernel 373 case "brand-gadget-id": 374 name = "brand-gadget" 375 typ = snap.TypeGadget 376 case "alias-snap-id": 377 name = "snap-id" 378 default: 379 panic(fmt.Sprintf("refresh: unknown snap-id: %s", cand.snapID)) 380 } 381 382 revno := snap.R(11) 383 if r := f.refreshRevnos[cand.snapID]; !r.Unset() { 384 revno = r 385 } 386 confinement := snap.StrictConfinement 387 switch cand.channel { 388 case "channel-for-7/stable": 389 revno = snap.R(7) 390 case "channel-for-classic/stable": 391 confinement = snap.ClassicConfinement 392 case "channel-for-devmode/stable": 393 confinement = snap.DevModeConfinement 394 } 395 if name == "some-snap-now-classic" { 396 confinement = "classic" 397 } 398 399 info := &snap.Info{ 400 SnapType: typ, 401 SideInfo: snap.SideInfo{ 402 RealName: name, 403 Channel: cand.channel, 404 SnapID: cand.snapID, 405 Revision: revno, 406 }, 407 Version: name, 408 DownloadInfo: snap.DownloadInfo{ 409 DownloadURL: "https://some-server.com/some/path.snap", 410 }, 411 Confinement: confinement, 412 Architectures: []string{"all"}, 413 Epoch: epoch, 414 } 415 switch cand.channel { 416 case "channel-for-layout/stable": 417 info.Layout = map[string]*snap.Layout{ 418 "/usr": { 419 Snap: info, 420 Path: "/usr", 421 Symlink: "$SNAP/usr", 422 }, 423 } 424 case "channel-for-base/stable": 425 info.Base = "some-base" 426 } 427 428 var hit snap.Revision 429 if cand.revision != revno { 430 hit = revno 431 } 432 for _, blocked := range cand.block { 433 if blocked == revno { 434 hit = snap.Revision{} 435 break 436 } 437 } 438 439 if !hit.Unset() { 440 return info, nil 441 } 442 443 return nil, store.ErrNoUpdateAvailable 444 } 445 446 func (f *fakeStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 447 if ctx == nil { 448 panic("context required") 449 } 450 f.pokeStateLock() 451 if assertQuery != nil { 452 panic("no assertion query support") 453 } 454 455 if len(currentSnaps) == 0 && len(actions) == 0 { 456 return nil, nil, nil 457 } 458 if len(actions) > 4 { 459 panic("fake SnapAction unexpectedly called with more than 3 actions") 460 } 461 462 curByInstanceName := make(map[string]*store.CurrentSnap, len(currentSnaps)) 463 curSnaps := make(byName, len(currentSnaps)) 464 for i, cur := range currentSnaps { 465 if cur.InstanceName == "" || cur.SnapID == "" || cur.Revision.Unset() { 466 return nil, nil, fmt.Errorf("internal error: incomplete current snap info") 467 } 468 curByInstanceName[cur.InstanceName] = cur 469 curSnaps[i] = *cur 470 } 471 sort.Sort(curSnaps) 472 473 userID := 0 474 if user != nil { 475 userID = user.ID 476 } 477 if len(curSnaps) == 0 { 478 curSnaps = nil 479 } 480 f.fakeBackend.appendOp(&fakeOp{ 481 op: "storesvc-snap-action", 482 curSnaps: curSnaps, 483 userID: userID, 484 }) 485 486 if f.seenPrivacyKeys == nil { 487 // so that checks don't topple over this being uninitialized 488 f.seenPrivacyKeys = make(map[string]bool) 489 } 490 if opts != nil && opts.PrivacyKey != "" { 491 f.seenPrivacyKeys[opts.PrivacyKey] = true 492 } 493 494 sorted := make(byAction, len(actions)) 495 copy(sorted, actions) 496 sort.Sort(sorted) 497 498 refreshErrors := make(map[string]error) 499 installErrors := make(map[string]error) 500 var res []store.SnapActionResult 501 for _, a := range sorted { 502 if a.Action != "install" && a.Action != "refresh" { 503 panic("not supported") 504 } 505 if a.InstanceName == "" { 506 return nil, nil, fmt.Errorf("internal error: action without instance name") 507 } 508 509 snapName, instanceKey := snap.SplitInstanceName(a.InstanceName) 510 511 if a.Action == "install" { 512 spec := snapSpec{ 513 Name: snapName, 514 Channel: a.Channel, 515 Revision: a.Revision, 516 Cohort: a.CohortKey, 517 } 518 info, err := f.snap(spec, user) 519 if err != nil { 520 installErrors[a.InstanceName] = err 521 continue 522 } 523 f.fakeBackend.appendOp(&fakeOp{ 524 op: "storesvc-snap-action:action", 525 action: *a, 526 revno: info.Revision, 527 userID: userID, 528 }) 529 if !a.Revision.Unset() { 530 info.Channel = "" 531 } 532 info.InstanceKey = instanceKey 533 sar := store.SnapActionResult{Info: info} 534 if strings.HasSuffix(snapName, "-with-default-track") && strutil.ListContains([]string{"stable", "candidate", "beta", "edge"}, a.Channel) { 535 sar.RedirectChannel = "2.0/" + a.Channel 536 } 537 res = append(res, sar) 538 continue 539 } 540 541 // refresh 542 543 cur := curByInstanceName[a.InstanceName] 544 if cur == nil { 545 return nil, nil, fmt.Errorf("internal error: no matching current snap for %q", a.InstanceName) 546 } 547 channel := a.Channel 548 if channel == "" { 549 channel = cur.TrackingChannel 550 } 551 ignoreValidation := cur.IgnoreValidation 552 if a.Flags&store.SnapActionIgnoreValidation != 0 { 553 ignoreValidation = true 554 } else if a.Flags&store.SnapActionEnforceValidation != 0 { 555 ignoreValidation = false 556 } 557 cand := refreshCand{ 558 snapID: a.SnapID, 559 channel: channel, 560 revision: cur.Revision, 561 block: cur.Block, 562 ignoreValidation: ignoreValidation, 563 } 564 info, err := f.lookupRefresh(cand) 565 var hit snap.Revision 566 if info != nil { 567 if !a.Revision.Unset() { 568 info.Revision = a.Revision 569 } 570 hit = info.Revision 571 } 572 f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{ 573 op: "storesvc-snap-action:action", 574 action: *a, 575 revno: hit, 576 userID: userID, 577 }) 578 if err == store.ErrNoUpdateAvailable { 579 refreshErrors[cur.InstanceName] = err 580 continue 581 } 582 if err != nil { 583 return nil, nil, err 584 } 585 if !a.Revision.Unset() { 586 info.Channel = "" 587 } 588 info.InstanceKey = instanceKey 589 res = append(res, store.SnapActionResult{Info: info}) 590 } 591 592 if len(refreshErrors)+len(installErrors) > 0 || len(res) == 0 { 593 if len(refreshErrors) == 0 { 594 refreshErrors = nil 595 } 596 if len(installErrors) == 0 { 597 installErrors = nil 598 } 599 return res, nil, &store.SnapActionError{ 600 NoResults: len(refreshErrors)+len(installErrors)+len(res) == 0, 601 Refresh: refreshErrors, 602 Install: installErrors, 603 } 604 } 605 606 return res, nil, nil 607 } 608 609 func (f *fakeStore) SuggestedCurrency() string { 610 f.pokeStateLock() 611 612 return "XTS" 613 } 614 615 func (f *fakeStore) Download(ctx context.Context, name, targetFn string, snapInfo *snap.DownloadInfo, pb progress.Meter, user *auth.UserState, dlOpts *store.DownloadOptions) error { 616 f.pokeStateLock() 617 618 if _, key := snap.SplitInstanceName(name); key != "" { 619 return fmt.Errorf("internal error: unsupported download with instance name %q", name) 620 } 621 var macaroon string 622 if user != nil { 623 macaroon = user.StoreMacaroon 624 } 625 // only add the options if they contain anything interesting 626 if *dlOpts == (store.DownloadOptions{}) { 627 dlOpts = nil 628 } 629 f.downloads = append(f.downloads, fakeDownload{ 630 macaroon: macaroon, 631 name: name, 632 target: targetFn, 633 opts: dlOpts, 634 }) 635 f.fakeBackend.appendOp(&fakeOp{op: "storesvc-download", name: name}) 636 637 pb.SetTotal(float64(f.fakeTotalProgress)) 638 pb.Set(float64(f.fakeCurrentProgress)) 639 640 return nil 641 } 642 643 func (f *fakeStore) WriteCatalogs(ctx context.Context, _ io.Writer, _ store.SnapAdder) error { 644 if ctx == nil { 645 panic("context required") 646 } 647 f.pokeStateLock() 648 649 f.fakeBackend.appendOp(&fakeOp{ 650 op: "x-commands", 651 }) 652 653 return nil 654 } 655 656 func (f *fakeStore) Sections(ctx context.Context, _ *auth.UserState) ([]string, error) { 657 if ctx == nil { 658 panic("context required") 659 } 660 f.pokeStateLock() 661 662 f.fakeBackend.appendOp(&fakeOp{ 663 op: "x-sections", 664 }) 665 666 return nil, nil 667 } 668 669 type fakeSnappyBackend struct { 670 ops fakeOps 671 mu sync.Mutex 672 673 linkSnapWaitCh chan int 674 linkSnapWaitTrigger string 675 linkSnapFailTrigger string 676 linkSnapMaybeReboot bool 677 678 copySnapDataFailTrigger string 679 emptyContainer snap.Container 680 681 servicesCurrentlyDisabled []string 682 683 lockDir string 684 } 685 686 func (f *fakeSnappyBackend) OpenSnapFile(snapFilePath string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { 687 op := fakeOp{ 688 op: "open-snap-file", 689 path: snapFilePath, 690 } 691 692 if si != nil { 693 op.sinfo = *si 694 } 695 696 var info *snap.Info 697 if !osutil.IsDirectory(snapFilePath) { 698 name := filepath.Base(snapFilePath) 699 split := strings.Split(name, "_") 700 if len(split) >= 2 { 701 // <snap>_<rev>.snap 702 // <snap>_<instance-key>_<rev>.snap 703 name = split[0] 704 } 705 706 info = &snap.Info{SuggestedName: name, Architectures: []string{"all"}} 707 if name == "some-snap-now-classic" { 708 info.Confinement = "classic" 709 } 710 if name == "some-epoch-snap" { 711 info.Epoch = snap.E("42") 712 } else { 713 info.Epoch = snap.E("1*") 714 } 715 } else { 716 // for snap try only 717 snapf, err := snapfile.Open(snapFilePath) 718 if err != nil { 719 return nil, nil, err 720 } 721 722 info, err = snap.ReadInfoFromSnapFile(snapf, si) 723 if err != nil { 724 return nil, nil, err 725 } 726 } 727 728 if info == nil { 729 return nil, nil, fmt.Errorf("internal error: no mocked snap for %q", snapFilePath) 730 } 731 f.appendOp(&op) 732 return info, f.emptyContainer, nil 733 } 734 735 // XXX: this is now something that is overridden by tests that need a 736 // different service setup so it should be configurable and part 737 // of the fakeSnappyBackend? 738 var servicesSnapYaml = `name: services-snap 739 apps: 740 svc1: 741 daemon: simple 742 before: [svc3] 743 svc2: 744 daemon: simple 745 after: [svc1] 746 svc3: 747 daemon: simple 748 before: [svc2] 749 ` 750 751 func (f *fakeSnappyBackend) SetupSnap(snapFilePath, instanceName string, si *snap.SideInfo, dev boot.Device, p progress.Meter) (snap.Type, *backend.InstallRecord, error) { 752 p.Notify("setup-snap") 753 revno := snap.R(0) 754 if si != nil { 755 revno = si.Revision 756 } 757 f.appendOp(&fakeOp{ 758 op: "setup-snap", 759 name: instanceName, 760 path: snapFilePath, 761 revno: revno, 762 }) 763 snapType := snap.TypeApp 764 switch si.RealName { 765 case "core": 766 snapType = snap.TypeOS 767 case "gadget": 768 snapType = snap.TypeGadget 769 } 770 if instanceName == "borken-in-setup" { 771 return snapType, nil, fmt.Errorf("cannot install snap %q", instanceName) 772 } 773 if instanceName == "some-snap-no-install-record" { 774 return snapType, nil, nil 775 } 776 return snapType, &backend.InstallRecord{}, nil 777 } 778 779 func (f *fakeSnappyBackend) ReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) { 780 if name == "borken" && si.Revision == snap.R(2) { 781 return nil, errors.New(`cannot read info for "borken" snap`) 782 } 783 if name == "borken-undo-setup" && si.Revision == snap.R(2) { 784 return nil, errors.New(`cannot read info for "borken-undo-setup" snap`) 785 } 786 if name == "not-there" && si.Revision == snap.R(2) { 787 return nil, &snap.NotFoundError{Snap: name, Revision: si.Revision} 788 } 789 snapName, instanceKey := snap.SplitInstanceName(name) 790 // naive emulation for now, always works 791 info := &snap.Info{ 792 SuggestedName: snapName, 793 SideInfo: *si, 794 Architectures: []string{"all"}, 795 SnapType: snap.TypeApp, 796 Epoch: snap.E("1*"), 797 } 798 if strings.Contains(snapName, "alias-snap") { 799 // only for the switch below 800 snapName = "alias-snap" 801 } 802 switch snapName { 803 case "snap-with-empty-epoch": 804 info.Epoch = snap.Epoch{} 805 case "some-epoch-snap": 806 info.Epoch = snap.E("13") 807 case "some-snap-with-base": 808 info.Base = "core18" 809 case "gadget", "brand-gadget": 810 info.SnapType = snap.TypeGadget 811 case "core": 812 info.SnapType = snap.TypeOS 813 case "snapd": 814 info.SnapType = snap.TypeSnapd 815 case "services-snap": 816 var err error 817 // fix services after/before so that there is only one solution 818 // to dependency ordering 819 info, err = snap.InfoFromSnapYaml([]byte(servicesSnapYaml)) 820 if err != nil { 821 panic(err) 822 } 823 info.SideInfo = *si 824 case "alias-snap": 825 var err error 826 info, err = snap.InfoFromSnapYaml([]byte(`name: alias-snap 827 apps: 828 cmd1: 829 cmd2: 830 cmd3: 831 cmd4: 832 cmd5: 833 cmddaemon: 834 daemon: simple 835 `)) 836 if err != nil { 837 panic(err) 838 } 839 info.SideInfo = *si 840 } 841 842 info.InstanceKey = instanceKey 843 return info, nil 844 } 845 846 func (f *fakeSnappyBackend) ClearTrashedData(si *snap.Info) { 847 f.appendOp(&fakeOp{ 848 op: "cleanup-trash", 849 name: si.InstanceName(), 850 revno: si.Revision, 851 }) 852 } 853 854 func (f *fakeSnappyBackend) StoreInfo(st *state.State, name, channel string, userID int, flags snapstate.Flags) (*snap.Info, error) { 855 return f.ReadInfo(name, &snap.SideInfo{ 856 RealName: name, 857 }) 858 } 859 860 func (f *fakeSnappyBackend) CopySnapData(newInfo, oldInfo *snap.Info, p progress.Meter) error { 861 p.Notify("copy-data") 862 old := "<no-old>" 863 if oldInfo != nil { 864 old = oldInfo.MountDir() 865 } 866 867 if newInfo.MountDir() == f.copySnapDataFailTrigger { 868 f.appendOp(&fakeOp{ 869 op: "copy-data.failed", 870 path: newInfo.MountDir(), 871 old: old, 872 }) 873 return errors.New("fail") 874 } 875 876 f.appendOp(&fakeOp{ 877 op: "copy-data", 878 path: newInfo.MountDir(), 879 old: old, 880 }) 881 return nil 882 } 883 884 func (f *fakeSnappyBackend) LinkSnap(info *snap.Info, dev boot.Device, linkCtx backend.LinkContext, tm timings.Measurer) (rebootRequired bool, err error) { 885 if info.MountDir() == f.linkSnapWaitTrigger { 886 f.linkSnapWaitCh <- 1 887 <-f.linkSnapWaitCh 888 } 889 890 op := fakeOp{ 891 op: "link-snap", 892 path: info.MountDir(), 893 } 894 895 op.vitalityRank = linkCtx.VitalityRank 896 897 if info.MountDir() == f.linkSnapFailTrigger { 898 op.op = "link-snap.failed" 899 f.ops = append(f.ops, op) 900 return false, errors.New("fail") 901 } 902 903 f.appendOp(&op) 904 905 reboot := false 906 if f.linkSnapMaybeReboot { 907 reboot = info.InstanceName() == dev.Base() 908 } 909 910 return reboot, nil 911 } 912 913 func svcSnapMountDir(svcs []*snap.AppInfo) string { 914 if len(svcs) == 0 { 915 return "<no services>" 916 } 917 if svcs[0].Snap == nil { 918 return "<snapless service>" 919 } 920 return svcs[0].Snap.MountDir() 921 } 922 923 func (f *fakeSnappyBackend) StartServices(svcs []*snap.AppInfo, disabledSvcs []string, meter progress.Meter, tm timings.Measurer) error { 924 services := make([]string, 0, len(svcs)) 925 for _, svc := range svcs { 926 services = append(services, svc.Name) 927 } 928 op := fakeOp{ 929 op: "start-snap-services", 930 path: svcSnapMountDir(svcs), 931 services: services, 932 } 933 // only add the services to the op if there's something to add 934 if len(disabledSvcs) != 0 { 935 op.disabledServices = disabledSvcs 936 } 937 f.appendOp(&op) 938 return nil 939 } 940 941 func (f *fakeSnappyBackend) StopServices(svcs []*snap.AppInfo, reason snap.ServiceStopReason, meter progress.Meter, tm timings.Measurer) error { 942 f.appendOp(&fakeOp{ 943 op: fmt.Sprintf("stop-snap-services:%s", reason), 944 path: svcSnapMountDir(svcs), 945 }) 946 return nil 947 } 948 949 func (f *fakeSnappyBackend) ServicesEnableState(info *snap.Info, meter progress.Meter) (map[string]bool, error) { 950 // return the disabled services as disabled and nothing else 951 m := make(map[string]bool) 952 for _, svc := range f.servicesCurrentlyDisabled { 953 m[svc] = false 954 } 955 956 f.appendOp(&fakeOp{ 957 op: "current-snap-service-states", 958 disabledServices: f.servicesCurrentlyDisabled, 959 }) 960 961 return m, nil 962 } 963 964 func (f *fakeSnappyBackend) QueryDisabledServices(info *snap.Info, meter progress.Meter) ([]string, error) { 965 var l []string 966 967 m, err := f.ServicesEnableState(info, meter) 968 if err != nil { 969 return nil, err 970 } 971 for name, enabled := range m { 972 if !enabled { 973 l = append(l, name) 974 } 975 } 976 977 // XXX: add a fakeOp here? 978 979 return l, nil 980 } 981 982 func (f *fakeSnappyBackend) UndoSetupSnap(s snap.PlaceInfo, typ snap.Type, installRecord *backend.InstallRecord, dev boot.Device, p progress.Meter) error { 983 p.Notify("setup-snap") 984 f.appendOp(&fakeOp{ 985 op: "undo-setup-snap", 986 name: s.InstanceName(), 987 path: s.MountDir(), 988 stype: typ, 989 }) 990 if s.InstanceName() == "borken-undo-setup" { 991 return errors.New(`cannot undo setup of "borken-undo-setup" snap`) 992 } 993 return nil 994 } 995 996 func (f *fakeSnappyBackend) UndoCopySnapData(newInfo *snap.Info, oldInfo *snap.Info, p progress.Meter) error { 997 p.Notify("undo-copy-data") 998 old := "<no-old>" 999 if oldInfo != nil { 1000 old = oldInfo.MountDir() 1001 } 1002 f.appendOp(&fakeOp{ 1003 op: "undo-copy-snap-data", 1004 path: newInfo.MountDir(), 1005 old: old, 1006 }) 1007 return nil 1008 } 1009 1010 func (f *fakeSnappyBackend) UnlinkSnap(info *snap.Info, linkCtx backend.LinkContext, meter progress.Meter) error { 1011 meter.Notify("unlink") 1012 f.appendOp(&fakeOp{ 1013 op: "unlink-snap", 1014 path: info.MountDir(), 1015 1016 unlinkFirstInstallUndo: linkCtx.FirstInstall, 1017 }) 1018 return nil 1019 } 1020 1021 func (f *fakeSnappyBackend) RemoveSnapFiles(s snap.PlaceInfo, typ snap.Type, installRecord *backend.InstallRecord, dev boot.Device, meter progress.Meter) error { 1022 meter.Notify("remove-snap-files") 1023 f.appendOp(&fakeOp{ 1024 op: "remove-snap-files", 1025 path: s.MountDir(), 1026 stype: typ, 1027 }) 1028 return nil 1029 } 1030 1031 func (f *fakeSnappyBackend) RemoveSnapData(info *snap.Info) error { 1032 f.appendOp(&fakeOp{ 1033 op: "remove-snap-data", 1034 path: info.MountDir(), 1035 }) 1036 return nil 1037 } 1038 1039 func (f *fakeSnappyBackend) RemoveSnapCommonData(info *snap.Info) error { 1040 f.appendOp(&fakeOp{ 1041 op: "remove-snap-common-data", 1042 path: info.MountDir(), 1043 }) 1044 return nil 1045 } 1046 1047 func (f *fakeSnappyBackend) RemoveSnapDataDir(info *snap.Info, otherInstances bool) error { 1048 f.ops = append(f.ops, fakeOp{ 1049 op: "remove-snap-data-dir", 1050 name: info.InstanceName(), 1051 path: snap.BaseDataDir(info.SnapName()), 1052 otherInstances: otherInstances, 1053 }) 1054 return nil 1055 } 1056 1057 func (f *fakeSnappyBackend) RemoveSnapDir(s snap.PlaceInfo, otherInstances bool) error { 1058 f.ops = append(f.ops, fakeOp{ 1059 op: "remove-snap-dir", 1060 name: s.InstanceName(), 1061 path: snap.BaseDir(s.SnapName()), 1062 otherInstances: otherInstances, 1063 }) 1064 return nil 1065 } 1066 1067 func (f *fakeSnappyBackend) DiscardSnapNamespace(snapName string) error { 1068 f.appendOp(&fakeOp{ 1069 op: "discard-namespace", 1070 name: snapName, 1071 }) 1072 return nil 1073 } 1074 1075 func (f *fakeSnappyBackend) RemoveSnapInhibitLock(snapName string) error { 1076 f.appendOp(&fakeOp{ 1077 op: "remove-inhibit-lock", 1078 name: snapName, 1079 }) 1080 return nil 1081 } 1082 1083 func (f *fakeSnappyBackend) Candidate(sideInfo *snap.SideInfo) { 1084 var sinfo snap.SideInfo 1085 if sideInfo != nil { 1086 sinfo = *sideInfo 1087 } 1088 f.appendOp(&fakeOp{ 1089 op: "candidate", 1090 sinfo: sinfo, 1091 }) 1092 } 1093 1094 func (f *fakeSnappyBackend) CurrentInfo(curInfo *snap.Info) { 1095 old := "<no-current>" 1096 if curInfo != nil { 1097 old = curInfo.MountDir() 1098 } 1099 f.appendOp(&fakeOp{ 1100 op: "current", 1101 old: old, 1102 }) 1103 } 1104 1105 func (f *fakeSnappyBackend) ForeignTask(kind string, status state.Status, snapsup *snapstate.SnapSetup) { 1106 f.appendOp(&fakeOp{ 1107 op: kind + ":" + status.String(), 1108 name: snapsup.InstanceName(), 1109 revno: snapsup.Revision(), 1110 }) 1111 } 1112 1113 type byAlias []*backend.Alias 1114 1115 func (ba byAlias) Len() int { return len(ba) } 1116 func (ba byAlias) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } 1117 func (ba byAlias) Less(i, j int) bool { 1118 return ba[i].Name < ba[j].Name 1119 } 1120 1121 func (f *fakeSnappyBackend) UpdateAliases(add []*backend.Alias, remove []*backend.Alias) error { 1122 if len(add) != 0 { 1123 add = append([]*backend.Alias(nil), add...) 1124 sort.Sort(byAlias(add)) 1125 } 1126 if len(remove) != 0 { 1127 remove = append([]*backend.Alias(nil), remove...) 1128 sort.Sort(byAlias(remove)) 1129 } 1130 f.appendOp(&fakeOp{ 1131 op: "update-aliases", 1132 aliases: add, 1133 rmAliases: remove, 1134 }) 1135 return nil 1136 } 1137 1138 func (f *fakeSnappyBackend) RemoveSnapAliases(snapName string) error { 1139 f.appendOp(&fakeOp{ 1140 op: "remove-snap-aliases", 1141 name: snapName, 1142 }) 1143 return nil 1144 } 1145 1146 func (f *fakeSnappyBackend) RunInhibitSnapForUnlink(info *snap.Info, hint runinhibit.Hint, decision func() error) (lock *osutil.FileLock, err error) { 1147 f.appendOp(&fakeOp{ 1148 op: "run-inhibit-snap-for-unlink", 1149 name: info.InstanceName(), 1150 inhibitHint: hint, 1151 }) 1152 if err := decision(); err != nil { 1153 return nil, err 1154 } 1155 if f.lockDir == "" { 1156 f.lockDir = os.TempDir() 1157 } 1158 // XXX: returning a real lock is somewhat annoying 1159 return osutil.NewFileLock(filepath.Join(f.lockDir, info.InstanceName()+".lock")) 1160 } 1161 1162 func (f *fakeSnappyBackend) appendOp(op *fakeOp) { 1163 f.mu.Lock() 1164 defer f.mu.Unlock() 1165 f.ops = append(f.ops, *op) 1166 }