github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_base_d_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 daemon 21 22 import ( 23 "context" 24 "crypto" 25 "fmt" 26 "io/ioutil" 27 "net/http" 28 "os" 29 "path/filepath" 30 "time" 31 32 "github.com/gorilla/mux" 33 "golang.org/x/crypto/sha3" 34 "gopkg.in/check.v1" 35 "gopkg.in/tomb.v2" 36 37 "github.com/snapcore/snapd/asserts" 38 "github.com/snapcore/snapd/asserts/assertstest" 39 "github.com/snapcore/snapd/asserts/sysdb" 40 "github.com/snapcore/snapd/dirs" 41 "github.com/snapcore/snapd/osutil" 42 "github.com/snapcore/snapd/overlord" 43 "github.com/snapcore/snapd/overlord/assertstate" 44 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 45 "github.com/snapcore/snapd/overlord/auth" 46 "github.com/snapcore/snapd/overlord/devicestate" 47 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 48 "github.com/snapcore/snapd/overlord/ifacestate" 49 "github.com/snapcore/snapd/overlord/snapstate" 50 "github.com/snapcore/snapd/overlord/state" 51 "github.com/snapcore/snapd/sandbox" 52 "github.com/snapcore/snapd/snap" 53 "github.com/snapcore/snapd/snap/snaptest" 54 "github.com/snapcore/snapd/store" 55 "github.com/snapcore/snapd/store/storetest" 56 "github.com/snapcore/snapd/systemd" 57 "github.com/snapcore/snapd/testutil" 58 ) 59 60 // TODO: as we split api_test.go and move more tests to live in daemon_test 61 // instead of daemon, split out functionality from APIBaseSuite 62 // to only the relevant suite when possible 63 64 type APIBaseSuite struct { 65 testutil.BaseTest 66 67 storetest.Store 68 69 rsnaps []*snap.Info 70 err error 71 vars map[string]string 72 storeSearch store.Search 73 suggestedCurrency string 74 d *Daemon 75 user *auth.UserState 76 ctx context.Context 77 currentSnaps []*store.CurrentSnap 78 actions []*store.SnapAction 79 80 restoreRelease func() 81 82 StoreSigning *assertstest.StoreStack 83 Brands *assertstest.SigningAccounts 84 85 systemctlRestorer func() 86 SysctlBufs [][]byte 87 88 connectivityResult map[string]bool 89 loginUserStoreMacaroon string 90 loginUserDischarge string 91 92 restoreSanitize func() 93 } 94 95 func (s *APIBaseSuite) PokeStateLock() { 96 // the store should be called without the state lock held. Try 97 // to acquire it. 98 st := s.d.overlord.State() 99 st.Lock() 100 st.Unlock() 101 } 102 103 func (s *APIBaseSuite) SnapInfo(ctx context.Context, spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) { 104 s.PokeStateLock() 105 s.user = user 106 s.ctx = ctx 107 if len(s.rsnaps) > 0 { 108 return s.rsnaps[0], s.err 109 } 110 return nil, s.err 111 } 112 113 func (s *APIBaseSuite) Find(ctx context.Context, search *store.Search, user *auth.UserState) ([]*snap.Info, error) { 114 s.PokeStateLock() 115 116 s.storeSearch = *search 117 s.user = user 118 s.ctx = ctx 119 120 return s.rsnaps, s.err 121 } 122 123 func (s *APIBaseSuite) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 124 s.PokeStateLock() 125 if assertQuery != nil { 126 toResolve, err := assertQuery.ToResolve() 127 if err != nil { 128 return nil, nil, err 129 } 130 if len(toResolve) != 0 { 131 panic("no assertion query support") 132 } 133 } 134 135 if ctx == nil { 136 panic("context required") 137 } 138 s.currentSnaps = currentSnaps 139 s.actions = actions 140 s.user = user 141 142 sars := make([]store.SnapActionResult, len(s.rsnaps)) 143 for i, rsnap := range s.rsnaps { 144 sars[i] = store.SnapActionResult{Info: rsnap} 145 } 146 return sars, nil, s.err 147 } 148 149 func (s *APIBaseSuite) SuggestedCurrency() string { 150 s.PokeStateLock() 151 152 return s.suggestedCurrency 153 } 154 155 func (s *APIBaseSuite) ConnectivityCheck() (map[string]bool, error) { 156 s.PokeStateLock() 157 158 return s.connectivityResult, s.err 159 } 160 161 func (s *APIBaseSuite) LoginUser(username, password, otp string) (string, string, error) { 162 s.PokeStateLock() 163 164 return s.loginUserStoreMacaroon, s.loginUserDischarge, s.err 165 } 166 167 func (s *APIBaseSuite) muxVars(*http.Request) map[string]string { 168 return s.vars 169 } 170 171 func (s *APIBaseSuite) SetUpSuite(c *check.C) { 172 muxVars = s.muxVars 173 s.restoreRelease = sandbox.MockForceDevMode(false) 174 s.systemctlRestorer = systemd.MockSystemctl(s.systemctl) 175 s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}) 176 } 177 178 func (s *APIBaseSuite) TearDownSuite(c *check.C) { 179 muxVars = nil 180 s.restoreRelease() 181 s.systemctlRestorer() 182 s.restoreSanitize() 183 } 184 185 func (s *APIBaseSuite) systemctl(args ...string) (buf []byte, err error) { 186 if len(s.SysctlBufs) > 0 { 187 buf, s.SysctlBufs = s.SysctlBufs[0], s.SysctlBufs[1:] 188 } 189 return buf, err 190 } 191 192 var ( 193 BrandPrivKey, _ = assertstest.GenerateKey(752) 194 ) 195 196 func (s *APIBaseSuite) SetUpTest(c *check.C) { 197 s.BaseTest.SetUpTest(c) 198 199 ctlcmds := testutil.MockCommand(c, "systemctl", "").Also("journalctl", "") 200 s.AddCleanup(ctlcmds.Restore) 201 202 s.SysctlBufs = nil 203 204 dirs.SetRootDir(c.MkDir()) 205 err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) 206 restore := osutil.MockMountInfo("") 207 s.AddCleanup(restore) 208 209 c.Assert(err, check.IsNil) 210 c.Assert(os.MkdirAll(dirs.SnapMountDir, 0755), check.IsNil) 211 c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil) 212 213 s.rsnaps = nil 214 s.suggestedCurrency = "" 215 s.storeSearch = store.Search{} 216 s.err = nil 217 s.vars = nil 218 s.user = nil 219 s.d = nil 220 s.currentSnaps = nil 221 s.actions = nil 222 // Disable real security backends for all API tests 223 s.AddCleanup(ifacestate.MockSecurityBackends(nil)) 224 225 s.StoreSigning = assertstest.NewStoreStack("can0nical", nil) 226 s.AddCleanup(sysdb.InjectTrusted(s.StoreSigning.Trusted)) 227 228 s.Brands = assertstest.NewSigningAccounts(s.StoreSigning) 229 s.Brands.Register("my-brand", BrandPrivKey, nil) 230 231 assertstateRefreshSnapDeclarations = nil 232 snapstateInstall = nil 233 snapstateInstallMany = nil 234 snapstateInstallPath = nil 235 snapstateRefreshCandidates = nil 236 snapstateRemoveMany = nil 237 snapstateRevert = nil 238 snapstateRevertToRevision = nil 239 snapstateTryPath = nil 240 snapstateUpdate = nil 241 snapstateUpdateMany = nil 242 snapstateSwitch = nil 243 } 244 245 func (s *APIBaseSuite) TearDownTest(c *check.C) { 246 s.d = nil 247 s.ctx = nil 248 249 unsafeReadSnapInfo = unsafeReadSnapInfoImpl 250 ensureStateSoon = ensureStateSoonImpl 251 dirs.SetRootDir("") 252 253 assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations 254 snapstateInstall = snapstate.Install 255 snapstateInstallMany = snapstate.InstallMany 256 snapstateInstallPath = snapstate.InstallPath 257 snapstateRefreshCandidates = snapstate.RefreshCandidates 258 snapstateRemoveMany = snapstate.RemoveMany 259 snapstateRevert = snapstate.Revert 260 snapstateRevertToRevision = snapstate.RevertToRevision 261 snapstateTryPath = snapstate.TryPath 262 snapstateUpdate = snapstate.Update 263 snapstateUpdateMany = snapstate.UpdateMany 264 snapstateSwitch = snapstate.Switch 265 266 s.BaseTest.TearDownTest(c) 267 } 268 269 func (s *APIBaseSuite) MockModel(c *check.C, st *state.State, model *asserts.Model) { 270 // realistic model setup 271 if model == nil { 272 model = s.Brands.Model("can0nical", "pc", map[string]interface{}{ 273 "architecture": "amd64", 274 "gadget": "gadget", 275 "kernel": "kernel", 276 }) 277 } 278 279 snapstate.DeviceCtx = devicestate.DeviceCtx 280 281 assertstatetest.AddMany(st, model) 282 283 devicestatetest.SetDevice(st, &auth.DeviceState{ 284 Brand: model.BrandID(), 285 Model: model.Model(), 286 Serial: "serialserial", 287 }) 288 } 289 290 func (s *APIBaseSuite) DaemonWithStore(c *check.C, sto snapstate.StoreService) *Daemon { 291 if s.d != nil { 292 panic("called Daemon*() twice") 293 } 294 d, err := New() 295 c.Assert(err, check.IsNil) 296 d.addRoutes() 297 298 c.Assert(d.overlord.StartUp(), check.IsNil) 299 300 st := d.overlord.State() 301 st.Lock() 302 defer st.Unlock() 303 snapstate.ReplaceStore(st, sto) 304 // mark as already seeded 305 st.Set("seeded", true) 306 // registered 307 s.MockModel(c, st, nil) 308 309 // don't actually try to talk to the store on snapstate.Ensure 310 // needs doing after the call to devicestate.Manager (which 311 // happens in daemon.New via overlord.New) 312 snapstate.CanAutoRefresh = nil 313 314 s.d = d 315 return d 316 } 317 318 func (s *APIBaseSuite) ResetDaemon() { 319 s.d = nil 320 } 321 322 func (s *APIBaseSuite) Daemon(c *check.C) *Daemon { 323 return s.daemon(c) 324 } 325 326 // XXX this one will go away 327 func (s *APIBaseSuite) daemon(c *check.C) *Daemon { 328 return s.DaemonWithStore(c, s) 329 } 330 331 // XXX this one will go away 332 func (s *APIBaseSuite) daemonWithOverlordMock(c *check.C) *Daemon { 333 return s.DaemonWithOverlordMock(c) 334 } 335 336 func (s *APIBaseSuite) DaemonWithOverlordMock(c *check.C) *Daemon { 337 if s.d != nil { 338 panic("called Daemon*() twice") 339 } 340 d, err := New() 341 c.Assert(err, check.IsNil) 342 d.addRoutes() 343 344 o := overlord.Mock() 345 d.overlord = o 346 347 st := d.overlord.State() 348 // adds an assertion db 349 assertstate.Manager(st, o.TaskRunner()) 350 st.Lock() 351 defer st.Unlock() 352 snapstate.ReplaceStore(st, s) 353 354 s.d = d 355 return d 356 } 357 358 type fakeSnapManager struct{} 359 360 func newFakeSnapManager(st *state.State, runner *state.TaskRunner) *fakeSnapManager { 361 runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error { 362 return nil 363 }, nil) 364 runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error { 365 return fmt.Errorf("fake-install-snap-error errored") 366 }, nil) 367 368 return &fakeSnapManager{} 369 } 370 371 func (m *fakeSnapManager) Ensure() error { 372 return nil 373 } 374 375 // sanity 376 var _ overlord.StateManager = (*fakeSnapManager)(nil) 377 378 func (s *APIBaseSuite) daemonWithFakeSnapManager(c *check.C) *Daemon { 379 d := s.daemonWithOverlordMock(c) 380 st := d.overlord.State() 381 runner := d.overlord.TaskRunner() 382 d.overlord.AddManager(newFakeSnapManager(st, runner)) 383 d.overlord.AddManager(runner) 384 c.Assert(d.overlord.StartUp(), check.IsNil) 385 return d 386 } 387 388 func (s *APIBaseSuite) waitTrivialChange(c *check.C, chg *state.Change) { 389 err := s.d.overlord.Settle(5 * time.Second) 390 c.Assert(err, check.IsNil) 391 c.Assert(chg.IsReady(), check.Equals, true) 392 } 393 394 func (s *APIBaseSuite) mkInstalledDesktopFile(c *check.C, name, content string) string { 395 df := filepath.Join(dirs.SnapDesktopFilesDir, name) 396 err := os.MkdirAll(filepath.Dir(df), 0755) 397 c.Assert(err, check.IsNil) 398 err = ioutil.WriteFile(df, []byte(content), 0644) 399 c.Assert(err, check.IsNil) 400 return df 401 } 402 403 // XXX this one will go away 404 func (s *APIBaseSuite) mkInstalledInState(c *check.C, daemon *Daemon, instanceName, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info { 405 return s.MkInstalledInState(c, daemon, instanceName, developer, version, revision, active, extraYaml) 406 } 407 408 func (s *APIBaseSuite) MockSnap(c *check.C, yamlText string) *snap.Info { 409 if s.d == nil { 410 panic("call s.Daemon(c) etc in your test first") 411 } 412 413 snapInfo := snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)}) 414 415 st := s.d.overlord.State() 416 417 st.Lock() 418 defer st.Unlock() 419 420 // Put a side info into the state 421 snapstate.Set(st, snapInfo.InstanceName(), &snapstate.SnapState{ 422 Active: true, 423 Sequence: []*snap.SideInfo{ 424 { 425 RealName: snapInfo.SnapName(), 426 Revision: snapInfo.Revision, 427 SnapID: "ididid", 428 }, 429 }, 430 Current: snapInfo.Revision, 431 SnapType: string(snapInfo.Type()), 432 }) 433 434 // Put the snap into the interface repository 435 repo := s.d.overlord.InterfaceManager().Repository() 436 err := repo.AddSnap(snapInfo) 437 c.Assert(err, check.IsNil) 438 return snapInfo 439 } 440 441 func (s *APIBaseSuite) MkInstalledInState(c *check.C, daemon *Daemon, instanceName, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info { 442 snapName, instanceKey := snap.SplitInstanceName(instanceName) 443 444 if revision.Local() && developer != "" { 445 panic("not supported") 446 } 447 448 var snapID string 449 if revision.Store() { 450 snapID = snapName + "-id" 451 } 452 // Collect arguments into a snap.SideInfo structure 453 sideInfo := &snap.SideInfo{ 454 SnapID: snapID, 455 RealName: snapName, 456 Revision: revision, 457 Channel: "stable", 458 } 459 460 // Collect other arguments into a yaml string 461 yamlText := fmt.Sprintf(` 462 name: %s 463 version: %s 464 %s`, snapName, version, extraYaml) 465 466 // Mock the snap on disk 467 snapInfo := snaptest.MockSnapInstance(c, instanceName, yamlText, sideInfo) 468 if active { 469 dir, rev := filepath.Split(snapInfo.MountDir()) 470 c.Assert(os.Symlink(rev, dir+"current"), check.IsNil) 471 } 472 c.Assert(snapInfo.InstanceName(), check.Equals, instanceName) 473 474 c.Assert(os.MkdirAll(snapInfo.DataDir(), 0755), check.IsNil) 475 metadir := filepath.Join(snapInfo.MountDir(), "meta") 476 guidir := filepath.Join(metadir, "gui") 477 c.Assert(os.MkdirAll(guidir, 0755), check.IsNil) 478 c.Check(ioutil.WriteFile(filepath.Join(guidir, "icon.svg"), []byte("yadda icon"), 0644), check.IsNil) 479 480 if daemon == nil { 481 return snapInfo 482 } 483 st := daemon.overlord.State() 484 st.Lock() 485 defer st.Unlock() 486 487 var snapst snapstate.SnapState 488 snapstate.Get(st, instanceName, &snapst) 489 snapst.Active = active 490 snapst.Sequence = append(snapst.Sequence, &snapInfo.SideInfo) 491 snapst.Current = snapInfo.SideInfo.Revision 492 snapst.TrackingChannel = "stable" 493 snapst.InstanceKey = instanceKey 494 495 snapstate.Set(st, instanceName, &snapst) 496 497 if developer == "" { 498 return snapInfo 499 } 500 501 devAcct := assertstest.NewAccount(s.StoreSigning, developer, map[string]interface{}{ 502 "account-id": developer + "-id", 503 }, "") 504 505 snapInfo.Publisher = snap.StoreAccount{ 506 ID: devAcct.AccountID(), 507 Username: devAcct.Username(), 508 DisplayName: devAcct.DisplayName(), 509 Validation: devAcct.Validation(), 510 } 511 512 snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ 513 "series": "16", 514 "snap-id": snapID, 515 "snap-name": snapName, 516 "publisher-id": devAcct.AccountID(), 517 "timestamp": time.Now().Format(time.RFC3339), 518 }, nil, "") 519 c.Assert(err, check.IsNil) 520 521 content, err := ioutil.ReadFile(snapInfo.MountFile()) 522 c.Assert(err, check.IsNil) 523 h := sha3.Sum384(content) 524 dgst, err := asserts.EncodeDigest(crypto.SHA3_384, h[:]) 525 c.Assert(err, check.IsNil) 526 snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ 527 "snap-sha3-384": string(dgst), 528 "snap-size": "999", 529 "snap-id": snapID, 530 "snap-revision": fmt.Sprintf("%s", revision), 531 "developer-id": devAcct.AccountID(), 532 "timestamp": time.Now().Format(time.RFC3339), 533 }, nil, "") 534 c.Assert(err, check.IsNil) 535 536 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), devAcct, snapDecl, snapRev) 537 538 return snapInfo 539 } 540 541 func handlerCommand(c *check.C, d *Daemon, req *http.Request) (cmd *Command, vars map[string]string) { 542 m := &mux.RouteMatch{} 543 if !d.router.Match(req, m) { 544 c.Fatalf("no command for URL %q", req.URL) 545 } 546 cmd, ok := m.Handler.(*Command) 547 if !ok { 548 c.Fatalf("no command for URL %q", req.URL) 549 } 550 return cmd, m.Vars 551 } 552 553 func (s *APIBaseSuite) CheckGetOnly(c *check.C, req *http.Request) { 554 if s.d == nil { 555 panic("call s.Daemon(c) etc in your test first") 556 } 557 558 cmd, _ := handlerCommand(c, s.d, req) 559 c.Check(cmd.POST, check.IsNil) 560 c.Check(cmd.PUT, check.IsNil) 561 c.Check(cmd.GET, check.NotNil) 562 } 563 564 func (s *APIBaseSuite) Req(c *check.C, req *http.Request, u *auth.UserState) Response { 565 if s.d == nil { 566 panic("call s.Daemon(c) etc in your test first") 567 } 568 569 cmd, vars := handlerCommand(c, s.d, req) 570 s.vars = vars 571 var f ResponseFunc 572 switch req.Method { 573 case "GET": 574 f = cmd.GET 575 case "POST": 576 f = cmd.POST 577 case "PUT": 578 f = cmd.PUT 579 default: 580 c.Fatalf("unsupported HTTP method %q", req.Method) 581 } 582 if f == nil { 583 c.Fatalf("no support for %q for %q", req.Method, req.URL) 584 } 585 return f(cmd, req, u) 586 } 587 588 func (s *APIBaseSuite) ServeHTTP(c *check.C, w http.ResponseWriter, req *http.Request) { 589 if s.d == nil { 590 panic("call s.Daemon(c) etc in your test first") 591 } 592 593 cmd, vars := handlerCommand(c, s.d, req) 594 s.vars = vars 595 596 cmd.ServeHTTP(w, req) 597 } 598 599 func (s *APIBaseSuite) SimulateConflict(name string) { 600 if s.d == nil { 601 panic("call s.Daemon(c) etc in your test first") 602 } 603 604 o := s.d.overlord 605 st := o.State() 606 st.Lock() 607 defer st.Unlock() 608 t := st.NewTask("link-snap", "...") 609 snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{ 610 RealName: name, 611 }} 612 t.Set("snap-setup", snapsup) 613 chg := st.NewChange("manip", "...") 614 chg.AddTask(t) 615 }