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