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