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