github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/daemon/api_apps_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 "bytes" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io" 28 "io/ioutil" 29 "math" 30 "net/http" 31 "net/http/httptest" 32 "sort" 33 "strconv" 34 "strings" 35 36 "gopkg.in/check.v1" 37 38 "github.com/snapcore/snapd/client" 39 "github.com/snapcore/snapd/daemon" 40 "github.com/snapcore/snapd/overlord/hookstate" 41 "github.com/snapcore/snapd/overlord/servicestate" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/state" 44 "github.com/snapcore/snapd/snap" 45 "github.com/snapcore/snapd/systemd" 46 "github.com/snapcore/snapd/testutil" 47 ) 48 49 var _ = check.Suite(&appsSuite{}) 50 51 type appsSuite struct { 52 apiBaseSuite 53 54 journalctlRestorer func() 55 jctlSvcses [][]string 56 jctlNs []int 57 jctlFollows []bool 58 jctlRCs []io.ReadCloser 59 jctlErrs []error 60 61 serviceControlError error 62 serviceControlCalls []serviceControlArgs 63 64 infoA, infoB, infoC, infoD, infoE *snap.Info 65 } 66 67 func (s *appsSuite) journalctl(svcs []string, n int, follow bool) (rc io.ReadCloser, err error) { 68 s.jctlSvcses = append(s.jctlSvcses, svcs) 69 s.jctlNs = append(s.jctlNs, n) 70 s.jctlFollows = append(s.jctlFollows, follow) 71 72 if len(s.jctlErrs) > 0 { 73 err, s.jctlErrs = s.jctlErrs[0], s.jctlErrs[1:] 74 } 75 if len(s.jctlRCs) > 0 { 76 rc, s.jctlRCs = s.jctlRCs[0], s.jctlRCs[1:] 77 } 78 79 return rc, err 80 } 81 82 type serviceControlArgs struct { 83 action string 84 options string 85 names []string 86 } 87 88 func (s *appsSuite) fakeServiceControl(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, flags *servicestate.Flags, context *hookstate.Context) ([]*state.TaskSet, error) { 89 if flags != nil { 90 panic("flags are not expected") 91 } 92 93 if s.serviceControlError != nil { 94 return nil, s.serviceControlError 95 } 96 97 serviceCommand := serviceControlArgs{action: inst.Action} 98 if inst.RestartOptions.Reload { 99 serviceCommand.options = "reload" 100 } 101 // only one flag should ever be set (depending on Action), but appending 102 // them below acts as an extra sanity check. 103 if inst.StartOptions.Enable { 104 serviceCommand.options += "enable" 105 } 106 if inst.StopOptions.Disable { 107 serviceCommand.options += "disable" 108 } 109 for _, app := range appInfos { 110 serviceCommand.names = append(serviceCommand.names, fmt.Sprintf("%s.%s", app.Snap.InstanceName(), app.Name)) 111 } 112 s.serviceControlCalls = append(s.serviceControlCalls, serviceCommand) 113 114 t := st.NewTask("dummy", "") 115 ts := state.NewTaskSet(t) 116 return []*state.TaskSet{ts}, nil 117 } 118 119 func (s *appsSuite) SetUpSuite(c *check.C) { 120 s.apiBaseSuite.SetUpSuite(c) 121 s.journalctlRestorer = systemd.MockJournalctl(s.journalctl) 122 } 123 124 func (s *appsSuite) TearDownSuite(c *check.C) { 125 s.journalctlRestorer() 126 s.apiBaseSuite.TearDownSuite(c) 127 } 128 129 func (s *appsSuite) SetUpTest(c *check.C) { 130 s.apiBaseSuite.SetUpTest(c) 131 132 s.jctlSvcses = nil 133 s.jctlNs = nil 134 s.jctlFollows = nil 135 s.jctlRCs = nil 136 s.jctlErrs = nil 137 138 d := s.daemon(c) 139 140 s.serviceControlCalls = nil 141 s.serviceControlError = nil 142 restoreServicestateCtrl := daemon.MockServicestateControl(s.fakeServiceControl) 143 s.AddCleanup(restoreServicestateCtrl) 144 145 // turn off ensuring snap services which will call systemctl automatically 146 r := servicestate.MockEnsuredSnapServices(s.d.Overlord().ServiceManager(), true) 147 s.AddCleanup(r) 148 149 s.infoA = s.mkInstalledInState(c, s.d, "snap-a", "dev", "v1", snap.R(1), true, "apps: {svc1: {daemon: simple}, svc2: {daemon: simple, reload-command: x}}") 150 s.infoB = s.mkInstalledInState(c, s.d, "snap-b", "dev", "v1", snap.R(1), false, "apps: {svc3: {daemon: simple}, cmd1: {}}") 151 s.infoC = s.mkInstalledInState(c, s.d, "snap-c", "dev", "v1", snap.R(1), true, "") 152 s.infoD = s.mkInstalledInState(c, s.d, "snap-d", "dev", "v1", snap.R(1), true, "apps: {cmd2: {}, cmd3: {}}") 153 s.infoE = s.mkInstalledInState(c, s.d, "snap-e", "dev", "v1", snap.R(1), true, "apps: {svc4: {daemon: simple, daemon-scope: user}}") 154 155 d.Overlord().Loop() 156 s.AddCleanup(func() { d.Overlord().Stop() }) 157 } 158 159 func (s *appsSuite) TestSplitAppName(c *check.C) { 160 type T struct { 161 name string 162 snap string 163 app string 164 } 165 166 for _, x := range []T{ 167 {name: "foo.bar", snap: "foo", app: "bar"}, 168 {name: "foo", snap: "foo", app: ""}, 169 {name: "foo.bar.baz", snap: "foo", app: "bar.baz"}, 170 {name: ".", snap: "", app: ""}, // SISO 171 } { 172 snap, app := daemon.SplitAppName(x.name) 173 c.Check(x.snap, check.Equals, snap, check.Commentf(x.name)) 174 c.Check(x.app, check.Equals, app, check.Commentf(x.name)) 175 } 176 } 177 178 func (s *appsSuite) TestGetAppsInfo(c *check.C) { 179 // System services from active snaps 180 svcNames := []string{"snap-a.svc1", "snap-a.svc2"} 181 for _, name := range svcNames { 182 s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(` 183 Id=snap.%s.service 184 Type=simple 185 ActiveState=active 186 UnitFileState=enabled 187 `[1:], name))) 188 } 189 // System services from inactive snaps 190 svcNames = append(svcNames, "snap-b.svc3") 191 // User services from active snaps 192 svcNames = append(svcNames, "snap-e.svc4") 193 s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n")) 194 195 req, err := http.NewRequest("GET", "/v2/apps", nil) 196 c.Assert(err, check.IsNil) 197 198 rsp := s.syncReq(c, req, nil) 199 c.Assert(rsp.Status, check.Equals, 200) 200 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 201 apps := rsp.Result.([]client.AppInfo) 202 c.Assert(apps, check.HasLen, 7) 203 204 for _, name := range svcNames { 205 snapName, app := daemon.SplitAppName(name) 206 needle := client.AppInfo{ 207 Snap: snapName, 208 Name: app, 209 Daemon: "simple", 210 DaemonScope: snap.SystemDaemon, 211 } 212 if snapName != "snap-b" { 213 // snap-b is not active (all the others are) 214 needle.Active = true 215 needle.Enabled = true 216 } 217 if snapName == "snap-e" { 218 // snap-e contains user services 219 needle.DaemonScope = snap.UserDaemon 220 needle.Active = false 221 } 222 c.Check(apps, testutil.DeepContains, needle) 223 } 224 225 for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} { 226 snap, app := daemon.SplitAppName(name) 227 c.Check(apps, testutil.DeepContains, client.AppInfo{ 228 Snap: snap, 229 Name: app, 230 }) 231 } 232 233 appNames := make([]string, len(apps)) 234 for i, app := range apps { 235 appNames[i] = app.Snap + "." + app.Name 236 } 237 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 238 } 239 240 func (s *appsSuite) TestGetAppsInfoNames(c *check.C) { 241 242 req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil) 243 c.Assert(err, check.IsNil) 244 245 rsp := s.syncReq(c, req, nil) 246 c.Assert(rsp.Status, check.Equals, 200) 247 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 248 apps := rsp.Result.([]client.AppInfo) 249 c.Assert(apps, check.HasLen, 2) 250 251 for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} { 252 snap, app := daemon.SplitAppName(name) 253 c.Check(apps, testutil.DeepContains, client.AppInfo{ 254 Snap: snap, 255 Name: app, 256 }) 257 } 258 259 appNames := make([]string, len(apps)) 260 for i, app := range apps { 261 appNames[i] = app.Snap + "." + app.Name 262 } 263 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 264 } 265 266 func (s *appsSuite) TestGetAppsInfoServices(c *check.C) { 267 // System services from active snaps 268 svcNames := []string{"snap-a.svc1", "snap-a.svc2"} 269 for _, name := range svcNames { 270 s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(` 271 Id=snap.%s.service 272 Type=simple 273 ActiveState=active 274 UnitFileState=enabled 275 `[1:], name))) 276 } 277 // System services from inactive snaps 278 svcNames = append(svcNames, "snap-b.svc3") 279 // User services from active snaps 280 svcNames = append(svcNames, "snap-e.svc4") 281 s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n")) 282 283 req, err := http.NewRequest("GET", "/v2/apps?select=service", nil) 284 c.Assert(err, check.IsNil) 285 286 rsp := s.syncReq(c, req, nil) 287 c.Assert(rsp.Status, check.Equals, 200) 288 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 289 svcs := rsp.Result.([]client.AppInfo) 290 c.Assert(svcs, check.HasLen, 4) 291 292 for _, name := range svcNames { 293 snapName, app := daemon.SplitAppName(name) 294 needle := client.AppInfo{ 295 Snap: snapName, 296 Name: app, 297 Daemon: "simple", 298 DaemonScope: snap.SystemDaemon, 299 } 300 if snapName != "snap-b" { 301 // snap-b is not active (all the others are) 302 needle.Active = true 303 needle.Enabled = true 304 } 305 if snapName == "snap-e" { 306 // snap-e contains user services 307 needle.DaemonScope = snap.UserDaemon 308 needle.Active = false 309 } 310 c.Check(svcs, testutil.DeepContains, needle) 311 } 312 313 appNames := make([]string, len(svcs)) 314 for i, svc := range svcs { 315 appNames[i] = svc.Snap + "." + svc.Name 316 } 317 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 318 } 319 320 func (s *appsSuite) TestGetAppsInfoBadSelect(c *check.C) { 321 req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil) 322 c.Assert(err, check.IsNil) 323 324 rspe := s.errorReq(c, req, nil) 325 c.Assert(rspe.Status, check.Equals, 400) 326 } 327 328 func (s *appsSuite) TestGetAppsInfoBadName(c *check.C) { 329 req, err := http.NewRequest("GET", "/v2/apps?names=potato", nil) 330 c.Assert(err, check.IsNil) 331 332 rspe := s.errorReq(c, req, nil) 333 c.Assert(rspe.Status, check.Equals, 404) 334 } 335 336 func (s *appsSuite) TestAppInfosForOne(c *check.C) { 337 st := s.d.Overlord().State() 338 appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-a.svc1"}, daemon.AppInfoServiceTrue) 339 c.Assert(rspe, check.IsNil) 340 c.Assert(appInfos, check.HasLen, 1) 341 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 342 c.Check(appInfos[0].Name, check.Equals, "svc1") 343 } 344 345 func (s *appsSuite) TestAppInfosForAll(c *check.C) { 346 type T struct { 347 opts daemon.AppInfoOptions 348 snaps []*snap.Info 349 names []string 350 } 351 352 for _, t := range []T{ 353 { 354 opts: daemon.AppInfoServiceTrue, 355 names: []string{"svc1", "svc2", "svc3", "svc4"}, 356 snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoE}, 357 }, 358 { 359 opts: daemon.AppInfoServiceFalse, 360 names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3", "svc4"}, 361 snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD, s.infoE}, 362 }, 363 } { 364 c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts)) 365 366 st := s.d.Overlord().State() 367 appInfos, rspe := daemon.AppInfosFor(st, nil, t.opts) 368 c.Assert(rspe, check.IsNil, check.Commentf("%s", t.opts)) 369 names := make([]string, len(appInfos)) 370 for i, appInfo := range appInfos { 371 names[i] = appInfo.Name 372 } 373 c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts)) 374 375 for i := range appInfos { 376 c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i])) 377 } 378 } 379 } 380 381 func (s *appsSuite) TestAppInfosForOneSnap(c *check.C) { 382 st := s.d.Overlord().State() 383 appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-a"}, daemon.AppInfoServiceTrue) 384 c.Assert(rspe, check.IsNil) 385 c.Assert(appInfos, check.HasLen, 2) 386 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 387 388 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 389 c.Check(appInfos[0].Name, check.Equals, "svc1") 390 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 391 c.Check(appInfos[1].Name, check.Equals, "svc2") 392 } 393 394 func (s *appsSuite) TestAppInfosForMixedArgs(c *check.C) { 395 st := s.d.Overlord().State() 396 appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-a", "snap-a.svc1"}, daemon.AppInfoServiceTrue) 397 c.Assert(rspe, check.IsNil) 398 c.Assert(appInfos, check.HasLen, 2) 399 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 400 401 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 402 c.Check(appInfos[0].Name, check.Equals, "svc1") 403 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 404 c.Check(appInfos[1].Name, check.Equals, "svc2") 405 } 406 407 func (s *appsSuite) TestAppInfosCleanupAndSorted(c *check.C) { 408 st := s.d.Overlord().State() 409 appInfos, rspe := daemon.AppInfosFor(st, []string{ 410 "snap-b.svc3", 411 "snap-a.svc2", 412 "snap-a.svc1", 413 "snap-a.svc2", 414 "snap-b.svc3", 415 "snap-a.svc1", 416 "snap-b", 417 "snap-a", 418 }, daemon.AppInfoServiceTrue) 419 c.Assert(rspe, check.IsNil) 420 c.Assert(appInfos, check.HasLen, 3) 421 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 422 423 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 424 c.Check(appInfos[0].Name, check.Equals, "svc1") 425 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 426 c.Check(appInfos[1].Name, check.Equals, "svc2") 427 c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB) 428 c.Check(appInfos[2].Name, check.Equals, "svc3") 429 } 430 431 func (s *appsSuite) TestAppInfosForAppless(c *check.C) { 432 st := s.d.Overlord().State() 433 appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-c"}, daemon.AppInfoServiceTrue) 434 c.Assert(rspe, check.NotNil) 435 c.Check(rspe.Status, check.Equals, 404) 436 c.Check(rspe.Kind, check.Equals, client.ErrorKindAppNotFound) 437 c.Assert(appInfos, check.IsNil) 438 } 439 440 func (s *appsSuite) TestAppInfosForMissingApp(c *check.C) { 441 st := s.d.Overlord().State() 442 appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-c.whatever"}, daemon.AppInfoServiceTrue) 443 c.Assert(rspe, check.NotNil) 444 c.Check(rspe.Status, check.Equals, 404) 445 c.Check(rspe.Kind, check.Equals, client.ErrorKindAppNotFound) 446 c.Assert(appInfos, check.IsNil) 447 } 448 449 func (s *appsSuite) TestAppInfosForMissingSnap(c *check.C) { 450 st := s.d.Overlord().State() 451 appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-x"}, daemon.AppInfoServiceTrue) 452 c.Assert(rspe, check.NotNil) 453 c.Check(rspe.Status, check.Equals, 404) 454 c.Check(rspe.Kind, check.Equals, client.ErrorKindSnapNotFound) 455 c.Assert(appInfos, check.IsNil) 456 } 457 458 func (s *appsSuite) testPostApps(c *check.C, inst servicestate.Instruction, servicecmds []serviceControlArgs) *state.Change { 459 postBody, err := json.Marshal(inst) 460 c.Assert(err, check.IsNil) 461 462 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody)) 463 c.Assert(err, check.IsNil) 464 465 rsp := s.asyncReq(c, req, nil) 466 c.Assert(rsp.Status, check.Equals, 202) 467 c.Check(rsp.Change, check.Matches, `[0-9]+`) 468 469 st := s.d.Overlord().State() 470 st.Lock() 471 defer st.Unlock() 472 chg := st.Change(rsp.Change) 473 c.Assert(chg, check.NotNil) 474 c.Check(chg.Tasks(), check.HasLen, len(servicecmds)) 475 476 st.Unlock() 477 <-chg.Ready() 478 st.Lock() 479 480 c.Check(s.serviceControlCalls, check.DeepEquals, servicecmds) 481 return chg 482 } 483 484 func (s *appsSuite) TestPostAppsStartOne(c *check.C) { 485 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}} 486 expected := []serviceControlArgs{ 487 {action: "start", names: []string{"snap-a.svc2"}}, 488 } 489 chg := s.testPostApps(c, inst, expected) 490 chg.State().Lock() 491 defer chg.State().Unlock() 492 493 var names []string 494 err := chg.Get("snap-names", &names) 495 c.Assert(err, check.IsNil) 496 c.Assert(names, check.DeepEquals, []string{"snap-a"}) 497 } 498 499 func (s *appsSuite) TestPostAppsStartTwo(c *check.C) { 500 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a"}} 501 expected := []serviceControlArgs{ 502 {action: "start", names: []string{"snap-a.svc1", "snap-a.svc2"}}, 503 } 504 chg := s.testPostApps(c, inst, expected) 505 506 chg.State().Lock() 507 defer chg.State().Unlock() 508 509 var names []string 510 err := chg.Get("snap-names", &names) 511 c.Assert(err, check.IsNil) 512 c.Assert(names, check.DeepEquals, []string{"snap-a"}) 513 } 514 515 func (s *appsSuite) TestPostAppsStartThree(c *check.C) { 516 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a", "snap-b"}} 517 expected := []serviceControlArgs{ 518 {action: "start", names: []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}}, 519 } 520 chg := s.testPostApps(c, inst, expected) 521 // check the summary expands the snap into actual apps 522 c.Check(chg.Summary(), check.Equals, "Running service command") 523 chg.State().Lock() 524 defer chg.State().Unlock() 525 526 var names []string 527 err := chg.Get("snap-names", &names) 528 c.Assert(err, check.IsNil) 529 c.Assert(names, check.DeepEquals, []string{"snap-a", "snap-b"}) 530 } 531 532 func (s *appsSuite) TestPostAppsStop(c *check.C) { 533 inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}} 534 expected := []serviceControlArgs{ 535 {action: "stop", names: []string{"snap-a.svc2"}}, 536 } 537 s.testPostApps(c, inst, expected) 538 } 539 540 func (s *appsSuite) TestPostAppsRestart(c *check.C) { 541 inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}} 542 expected := []serviceControlArgs{ 543 {action: "restart", names: []string{"snap-a.svc2"}}, 544 } 545 s.testPostApps(c, inst, expected) 546 } 547 548 func (s *appsSuite) TestPostAppsReload(c *check.C) { 549 inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}} 550 inst.Reload = true 551 expected := []serviceControlArgs{ 552 {action: "restart", options: "reload", names: []string{"snap-a.svc2"}}, 553 } 554 s.testPostApps(c, inst, expected) 555 } 556 557 func (s *appsSuite) TestPostAppsEnableNow(c *check.C) { 558 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}} 559 inst.Enable = true 560 expected := []serviceControlArgs{ 561 {action: "start", options: "enable", names: []string{"snap-a.svc2"}}, 562 } 563 s.testPostApps(c, inst, expected) 564 } 565 566 func (s *appsSuite) TestPostAppsDisableNow(c *check.C) { 567 inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}} 568 inst.Disable = true 569 expected := []serviceControlArgs{ 570 {action: "stop", options: "disable", names: []string{"snap-a.svc2"}}, 571 } 572 s.testPostApps(c, inst, expected) 573 } 574 575 func (s *appsSuite) TestPostAppsBadJSON(c *check.C) { 576 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`)) 577 c.Assert(err, check.IsNil) 578 rspe := s.errorReq(c, req, nil) 579 c.Check(rspe.Status, check.Equals, 400) 580 c.Check(rspe.Message, check.Matches, ".*cannot decode request body.*") 581 } 582 583 func (s *appsSuite) TestPostAppsBadOp(c *check.C) { 584 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`)) 585 c.Assert(err, check.IsNil) 586 rspe := s.errorReq(c, req, nil) 587 c.Check(rspe.Status, check.Equals, 400) 588 c.Check(rspe.Message, check.Matches, ".*cannot perform operation on services without a list of services.*") 589 } 590 591 func (s *appsSuite) TestPostAppsBadSnap(c *check.C) { 592 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`)) 593 c.Assert(err, check.IsNil) 594 rspe := s.errorReq(c, req, nil) 595 c.Check(rspe.Status, check.Equals, 404) 596 c.Check(rspe.Message, check.Equals, `snap "snap-c" has no services`) 597 } 598 599 func (s *appsSuite) TestPostAppsBadApp(c *check.C) { 600 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`)) 601 c.Assert(err, check.IsNil) 602 rspe := s.errorReq(c, req, nil) 603 c.Check(rspe.Status, check.Equals, 404) 604 c.Check(rspe.Message, check.Equals, `snap "snap-a" has no service "what"`) 605 } 606 607 func (s *appsSuite) TestPostAppsServiceControlError(c *check.C) { 608 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) 609 c.Assert(err, check.IsNil) 610 s.serviceControlError = fmt.Errorf("total failure") 611 rspe := s.errorReq(c, req, nil) 612 c.Check(rspe.Status, check.Equals, 400) 613 c.Check(rspe.Message, check.Equals, `total failure`) 614 } 615 616 func (s *appsSuite) TestPostAppsConflict(c *check.C) { 617 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) 618 c.Assert(err, check.IsNil) 619 s.serviceControlError = &snapstate.ChangeConflictError{Snap: "snap-a", ChangeKind: "enable"} 620 rspe := s.errorReq(c, req, nil) 621 c.Check(rspe.Status, check.Equals, 400) 622 c.Check(rspe.Message, check.Equals, `snap "snap-a" has "enable" change in progress`) 623 } 624 625 func (s *appsSuite) expectLogsAccess() { 626 s.expectReadAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage"}) 627 } 628 629 func (s *appsSuite) TestLogs(c *check.C) { 630 s.expectLogsAccess() 631 632 s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(` 633 {"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"} 634 {"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"} 635 {"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"} 636 {"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"} 637 {"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"} 638 `))} 639 640 req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil) 641 c.Assert(err, check.IsNil) 642 643 rec := httptest.NewRecorder() 644 s.req(c, req, nil).ServeHTTP(rec, req) 645 646 c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}}) 647 c.Check(s.jctlNs, check.DeepEquals, []int{42}) 648 c.Check(s.jctlFollows, check.DeepEquals, []bool{false}) 649 650 c.Check(rec.Code, check.Equals, 200) 651 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq") 652 c.Check(rec.Body.String(), check.Equals, ` 653 {"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"} 654 {"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"} 655 {"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"} 656 {"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"} 657 {"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"} 658 `[1:]) 659 } 660 661 func (s *appsSuite) TestLogsN(c *check.C) { 662 s.expectLogsAccess() 663 664 type T struct { 665 in string 666 out int 667 } 668 669 for _, t := range []T{ 670 {in: "", out: 10}, 671 {in: "0", out: 0}, 672 {in: "-1", out: -1}, 673 {in: strconv.Itoa(math.MinInt32), out: math.MinInt32}, 674 {in: strconv.Itoa(math.MaxInt32), out: math.MaxInt32}, 675 } { 676 677 s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(""))} 678 s.jctlNs = nil 679 680 req, err := http.NewRequest("GET", "/v2/logs?n="+t.in, nil) 681 c.Assert(err, check.IsNil) 682 683 rec := httptest.NewRecorder() 684 s.req(c, req, nil).ServeHTTP(rec, req) 685 686 c.Check(s.jctlNs, check.DeepEquals, []int{t.out}) 687 } 688 } 689 690 func (s *appsSuite) TestLogsBadN(c *check.C) { 691 s.expectLogsAccess() 692 693 req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil) 694 c.Assert(err, check.IsNil) 695 696 rspe := s.errorReq(c, req, nil) 697 c.Assert(rspe.Status, check.Equals, 400) 698 } 699 700 func (s *appsSuite) TestLogsFollow(c *check.C) { 701 s.expectLogsAccess() 702 703 s.jctlRCs = []io.ReadCloser{ 704 ioutil.NopCloser(strings.NewReader("")), 705 ioutil.NopCloser(strings.NewReader("")), 706 ioutil.NopCloser(strings.NewReader("")), 707 } 708 709 reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil) 710 c.Assert(err, check.IsNil) 711 reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil) 712 c.Assert(err, check.IsNil) 713 reqN, err := http.NewRequest("GET", "/v2/logs", nil) 714 c.Assert(err, check.IsNil) 715 716 rec := httptest.NewRecorder() 717 s.req(c, reqT, nil).ServeHTTP(rec, reqT) 718 s.req(c, reqF, nil).ServeHTTP(rec, reqF) 719 s.req(c, reqN, nil).ServeHTTP(rec, reqN) 720 721 c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false}) 722 } 723 724 func (s *appsSuite) TestLogsBadFollow(c *check.C) { 725 s.expectLogsAccess() 726 727 req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil) 728 c.Assert(err, check.IsNil) 729 730 rspe := s.errorReq(c, req, nil) 731 c.Assert(rspe.Status, check.Equals, 400) 732 } 733 734 func (s *appsSuite) TestLogsBadName(c *check.C) { 735 s.expectLogsAccess() 736 737 req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil) 738 c.Assert(err, check.IsNil) 739 740 rspe := s.errorReq(c, req, nil) 741 c.Assert(rspe.Status, check.Equals, 404) 742 } 743 744 func (s *appsSuite) TestLogsSad(c *check.C) { 745 s.expectLogsAccess() 746 747 s.jctlErrs = []error{errors.New("potato")} 748 req, err := http.NewRequest("GET", "/v2/logs", nil) 749 c.Assert(err, check.IsNil) 750 751 rspe := s.errorReq(c, req, nil) 752 c.Assert(rspe.Status, check.Equals, 500) 753 } 754 755 func (s *appsSuite) TestLogsNoServices(c *check.C) { 756 s.expectLogsAccess() 757 758 // no installed snaps with services 759 st := s.d.Overlord().State() 760 st.Lock() 761 st.Set("snaps", nil) 762 st.Unlock() 763 764 req, err := http.NewRequest("GET", "/v2/logs", nil) 765 c.Assert(err, check.IsNil) 766 767 rspe := s.errorReq(c, req, nil) 768 c.Assert(rspe.Status, check.Equals, 404) 769 }