github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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, toResolveSeq, err := assertQuery.ToResolve() 127 if err != nil { 128 return nil, nil, err 129 } 130 if len(toResolve) != 0 || len(toResolveSeq) != 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 st := d.overlord.State() 299 // mark as already seeded 300 st.Lock() 301 st.Set("seeded", true) 302 st.Unlock() 303 c.Assert(d.overlord.StartUp(), check.IsNil) 304 305 st.Lock() 306 defer st.Unlock() 307 snapstate.ReplaceStore(st, sto) 308 // mark as already seeded 309 st.Set("seeded", true) 310 // registered 311 s.MockModel(c, st, nil) 312 313 // don't actually try to talk to the store on snapstate.Ensure 314 // needs doing after the call to devicestate.Manager (which 315 // happens in daemon.New via overlord.New) 316 snapstate.CanAutoRefresh = nil 317 318 s.d = d 319 return d 320 } 321 322 func (s *APIBaseSuite) ResetDaemon() { 323 s.d = nil 324 } 325 326 func (s *APIBaseSuite) Daemon(c *check.C) *Daemon { 327 return s.daemon(c) 328 } 329 330 // XXX this one will go away 331 func (s *APIBaseSuite) daemon(c *check.C) *Daemon { 332 return s.DaemonWithStore(c, s) 333 } 334 335 // XXX this one will go away 336 func (s *APIBaseSuite) daemonWithOverlordMock(c *check.C) *Daemon { 337 return s.DaemonWithOverlordMock(c) 338 } 339 340 func (s *APIBaseSuite) DaemonWithOverlordMock(c *check.C) *Daemon { 341 if s.d != nil { 342 panic("called Daemon*() twice") 343 } 344 d, err := New() 345 c.Assert(err, check.IsNil) 346 d.addRoutes() 347 348 o := overlord.Mock() 349 d.overlord = o 350 351 st := d.overlord.State() 352 // adds an assertion db 353 assertstate.Manager(st, o.TaskRunner()) 354 st.Lock() 355 defer st.Unlock() 356 snapstate.ReplaceStore(st, s) 357 358 s.d = d 359 return d 360 } 361 362 type fakeSnapManager struct{} 363 364 func newFakeSnapManager(st *state.State, runner *state.TaskRunner) *fakeSnapManager { 365 runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error { 366 return nil 367 }, nil) 368 runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error { 369 return fmt.Errorf("fake-install-snap-error errored") 370 }, nil) 371 372 return &fakeSnapManager{} 373 } 374 375 func (m *fakeSnapManager) Ensure() error { 376 return nil 377 } 378 379 // sanity 380 var _ overlord.StateManager = (*fakeSnapManager)(nil) 381 382 func (s *APIBaseSuite) daemonWithFakeSnapManager(c *check.C) *Daemon { 383 d := s.daemonWithOverlordMock(c) 384 st := d.overlord.State() 385 runner := d.overlord.TaskRunner() 386 d.overlord.AddManager(newFakeSnapManager(st, runner)) 387 d.overlord.AddManager(runner) 388 c.Assert(d.overlord.StartUp(), check.IsNil) 389 return d 390 } 391 392 func (s *APIBaseSuite) waitTrivialChange(c *check.C, chg *state.Change) { 393 err := s.d.overlord.Settle(5 * time.Second) 394 c.Assert(err, check.IsNil) 395 c.Assert(chg.IsReady(), check.Equals, true) 396 } 397 398 func (s *APIBaseSuite) mkInstalledDesktopFile(c *check.C, name, content string) string { 399 df := filepath.Join(dirs.SnapDesktopFilesDir, name) 400 err := os.MkdirAll(filepath.Dir(df), 0755) 401 c.Assert(err, check.IsNil) 402 err = ioutil.WriteFile(df, []byte(content), 0644) 403 c.Assert(err, check.IsNil) 404 return df 405 } 406 407 // XXX this one will go away 408 func (s *APIBaseSuite) mkInstalledInState(c *check.C, daemon *Daemon, instanceName, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info { 409 return s.MkInstalledInState(c, daemon, instanceName, developer, version, revision, active, extraYaml) 410 } 411 412 func (s *APIBaseSuite) MockSnap(c *check.C, yamlText string) *snap.Info { 413 if s.d == nil { 414 panic("call s.Daemon(c) etc in your test first") 415 } 416 417 snapInfo := snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)}) 418 419 st := s.d.overlord.State() 420 421 st.Lock() 422 defer st.Unlock() 423 424 // Put a side info into the state 425 snapstate.Set(st, snapInfo.InstanceName(), &snapstate.SnapState{ 426 Active: true, 427 Sequence: []*snap.SideInfo{ 428 { 429 RealName: snapInfo.SnapName(), 430 Revision: snapInfo.Revision, 431 SnapID: "ididid", 432 }, 433 }, 434 Current: snapInfo.Revision, 435 SnapType: string(snapInfo.Type()), 436 }) 437 438 // Put the snap into the interface repository 439 repo := s.d.overlord.InterfaceManager().Repository() 440 err := repo.AddSnap(snapInfo) 441 c.Assert(err, check.IsNil) 442 return snapInfo 443 } 444 445 func (s *APIBaseSuite) MkInstalledInState(c *check.C, daemon *Daemon, instanceName, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info { 446 snapName, instanceKey := snap.SplitInstanceName(instanceName) 447 448 if revision.Local() && developer != "" { 449 panic("not supported") 450 } 451 452 var snapID string 453 if revision.Store() { 454 snapID = snapName + "-id" 455 } 456 // Collect arguments into a snap.SideInfo structure 457 sideInfo := &snap.SideInfo{ 458 SnapID: snapID, 459 RealName: snapName, 460 Revision: revision, 461 Channel: "stable", 462 } 463 464 // Collect other arguments into a yaml string 465 yamlText := fmt.Sprintf(` 466 name: %s 467 version: %s 468 %s`, snapName, version, extraYaml) 469 470 // Mock the snap on disk 471 snapInfo := snaptest.MockSnapInstance(c, instanceName, yamlText, sideInfo) 472 if active { 473 dir, rev := filepath.Split(snapInfo.MountDir()) 474 c.Assert(os.Symlink(rev, dir+"current"), check.IsNil) 475 } 476 c.Assert(snapInfo.InstanceName(), check.Equals, instanceName) 477 478 c.Assert(os.MkdirAll(snapInfo.DataDir(), 0755), check.IsNil) 479 metadir := filepath.Join(snapInfo.MountDir(), "meta") 480 guidir := filepath.Join(metadir, "gui") 481 c.Assert(os.MkdirAll(guidir, 0755), check.IsNil) 482 c.Check(ioutil.WriteFile(filepath.Join(guidir, "icon.svg"), []byte("yadda icon"), 0644), check.IsNil) 483 484 if daemon == nil { 485 return snapInfo 486 } 487 st := daemon.overlord.State() 488 st.Lock() 489 defer st.Unlock() 490 491 var snapst snapstate.SnapState 492 snapstate.Get(st, instanceName, &snapst) 493 snapst.Active = active 494 snapst.Sequence = append(snapst.Sequence, &snapInfo.SideInfo) 495 snapst.Current = snapInfo.SideInfo.Revision 496 snapst.TrackingChannel = "stable" 497 snapst.InstanceKey = instanceKey 498 499 snapstate.Set(st, instanceName, &snapst) 500 501 if developer == "" { 502 return snapInfo 503 } 504 505 devAcct := assertstest.NewAccount(s.StoreSigning, developer, map[string]interface{}{ 506 "account-id": developer + "-id", 507 }, "") 508 509 snapInfo.Publisher = snap.StoreAccount{ 510 ID: devAcct.AccountID(), 511 Username: devAcct.Username(), 512 DisplayName: devAcct.DisplayName(), 513 Validation: devAcct.Validation(), 514 } 515 516 snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ 517 "series": "16", 518 "snap-id": snapID, 519 "snap-name": snapName, 520 "publisher-id": devAcct.AccountID(), 521 "timestamp": time.Now().Format(time.RFC3339), 522 }, nil, "") 523 c.Assert(err, check.IsNil) 524 525 content, err := ioutil.ReadFile(snapInfo.MountFile()) 526 c.Assert(err, check.IsNil) 527 h := sha3.Sum384(content) 528 dgst, err := asserts.EncodeDigest(crypto.SHA3_384, h[:]) 529 c.Assert(err, check.IsNil) 530 snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ 531 "snap-sha3-384": string(dgst), 532 "snap-size": "999", 533 "snap-id": snapID, 534 "snap-revision": fmt.Sprintf("%s", revision), 535 "developer-id": devAcct.AccountID(), 536 "timestamp": time.Now().Format(time.RFC3339), 537 }, nil, "") 538 c.Assert(err, check.IsNil) 539 540 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), devAcct, snapDecl, snapRev) 541 542 return snapInfo 543 } 544 545 func handlerCommand(c *check.C, d *Daemon, req *http.Request) (cmd *Command, vars map[string]string) { 546 m := &mux.RouteMatch{} 547 if !d.router.Match(req, m) { 548 c.Fatalf("no command for URL %q", req.URL) 549 } 550 cmd, ok := m.Handler.(*Command) 551 if !ok { 552 c.Fatalf("no command for URL %q", req.URL) 553 } 554 return cmd, m.Vars 555 } 556 557 func (s *APIBaseSuite) CheckGetOnly(c *check.C, req *http.Request) { 558 if s.d == nil { 559 panic("call s.Daemon(c) etc in your test first") 560 } 561 562 cmd, _ := handlerCommand(c, s.d, req) 563 c.Check(cmd.POST, check.IsNil) 564 c.Check(cmd.PUT, check.IsNil) 565 c.Check(cmd.GET, check.NotNil) 566 } 567 568 func (s *APIBaseSuite) Req(c *check.C, req *http.Request, u *auth.UserState) Response { 569 if s.d == nil { 570 panic("call s.Daemon(c) etc in your test first") 571 } 572 573 cmd, vars := handlerCommand(c, s.d, req) 574 s.vars = vars 575 var f ResponseFunc 576 switch req.Method { 577 case "GET": 578 f = cmd.GET 579 case "POST": 580 f = cmd.POST 581 case "PUT": 582 f = cmd.PUT 583 default: 584 c.Fatalf("unsupported HTTP method %q", req.Method) 585 } 586 if f == nil { 587 c.Fatalf("no support for %q for %q", req.Method, req.URL) 588 } 589 return f(cmd, req, u) 590 } 591 592 func (s *APIBaseSuite) ServeHTTP(c *check.C, w http.ResponseWriter, req *http.Request) { 593 if s.d == nil { 594 panic("call s.Daemon(c) etc in your test first") 595 } 596 597 cmd, vars := handlerCommand(c, s.d, req) 598 s.vars = vars 599 600 cmd.ServeHTTP(w, req) 601 } 602 603 func (s *APIBaseSuite) SimulateConflict(name string) { 604 if s.d == nil { 605 panic("call s.Daemon(c) etc in your test first") 606 } 607 608 o := s.d.overlord 609 st := o.State() 610 st.Lock() 611 defer st.Unlock() 612 t := st.NewTask("link-snap", "...") 613 snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{ 614 RealName: name, 615 }} 616 t.Set("snap-setup", snapsup) 617 chg := st.NewChange("manip", "...") 618 chg.AddTask(t) 619 }